Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

208 changes: 208 additions & 0 deletions crates/common/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,30 @@ pub struct OnboardingRequest {
pub threshold: Option<i32>,
}

/// Request to onboard a decentrally-hosted external party.
///
/// The party's Ed25519 namespace key is generated client-side by DPM; the party
/// is hosted with Confirmation permission across the coordinator node and
/// `hosting_peers`, at the given confirmation threshold.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct ExternalPartyRequest {
/// Human-readable hint forming the party id's identifier segment
/// (`{party_hint}::{namespace_fingerprint}`).
pub party_hint: String,
/// The other participants that will host the party (beyond this
/// coordinator node). Each runs DPM and authorizes hosting on its own
/// participant. A decentrally-hosted party needs at least one.
#[serde(default)]
pub hosting_peers: Vec<CantonId>,
/// Confirmation threshold for the hosting participant set (how many hosts
/// must confirm a transaction). Optional: when omitted Canton defaults it
/// to the number of hosting participants.
#[serde(default)]
pub confirmation_threshold: Option<u32>,
}

/// Why a directed edge was reported missing. The frontend renders different
/// remediation hints depending on which kind it sees: `MeshHole` is a true
/// peer↔peer config gap ("on `from`, add `to` to the network config"), while
Expand Down Expand Up @@ -302,6 +326,170 @@ pub struct WorkflowRunsResponse {
pub runs: Vec<WorkflowRun>,
}

/// One external party this node has onboarded. Derived from an allocated
/// `ExternalParty` workflow run. Runs still onboarding (no party id yet) or that
/// failed before allocation are not listed here — they show in `/workflows`.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct ExternalPartyInfo {
/// The onboarding run's `instance_name` (its primary key).
pub instance_name: String,
/// The allocated party id (`{hint}::{fingerprint}`).
pub party_id: String,
/// The party's namespace fingerprint (the `{fingerprint}` half of the id).
pub fingerprint: String,
/// Unix-seconds creation time of the run.
pub created_at: i64,
}

/// Response wrapper for `GET /external-parties`.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct ExternalPartiesResponse {
pub parties: Vec<ExternalPartyInfo>,
}

// ============================================================================
// Tenant API DTOs (wallet-facing `/v0/tenant/*`)
//
// Wallet-driven flow: the wallet generates and holds the Ed25519 key and DPM
// relays. Every binary field on the wire is base64 (STANDARD engine).
// ============================================================================

/// Request to prepare the onboarding topology for a wallet-held external party.
/// The wallet sends only its public key; DPM relays it to Canton and returns the
/// unsigned topology for the wallet to sign.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantPrepareRequest {
/// Human-readable hint forming the party id's identifier segment.
pub party_hint: String,
/// The party's raw 32-byte Ed25519 public key, base64-encoded.
pub public_key: String,
/// Participants that will host the party (beyond this coordinator node).
#[serde(default)]
pub hosting_peers: Vec<CantonId>,
/// Confirmation threshold for the hosting set; `None` defaults to the number
/// of hosting participants.
#[serde(default)]
pub confirmation_threshold: Option<u32>,
}

/// The unsigned onboarding topology for the wallet to sign. `multi_hash` and
/// each entry of `topology_transactions` are base64-encoded.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantPrepareResponse {
/// The party id Canton derived (`{party_hint}::{fingerprint}`).
pub party_id: String,
/// The combined multi-hash the wallet signs, base64-encoded.
pub multi_hash: String,
/// The serialized topology transactions, each base64-encoded.
pub topology_transactions: Vec<String>,
}

/// Request to onboard a wallet-held external party from its signed topology.
/// `public_key`, each `topology_transactions` entry, and `multi_hash_signature`
/// are base64-encoded.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantOnboardRequest {
pub party_hint: String,
/// The party's raw Ed25519 public key, base64-encoded.
pub public_key: String,
#[serde(default)]
pub hosting_peers: Vec<CantonId>,
#[serde(default)]
pub confirmation_threshold: Option<u32>,
/// The (unchanged) topology transactions from `TenantPrepareResponse`.
pub topology_transactions: Vec<String>,
/// The wallet's Ed25519 signature over the multi-hash, base64-encoded.
pub multi_hash_signature: String,
/// Fingerprint of the signing key (the `{fingerprint}` party-id segment).
pub signed_by: String,
}

/// Response to a wallet onboarding request.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantOnboardResponse {
pub status: WorkflowProgress,
pub party_id: String,
pub instance_name: String,
}

/// A Daml template identifier for a tenant create command.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantTemplateId {
pub package_id: String,
pub module_name: String,
pub entity_name: String,
}

/// Request to prepare an interactive submission for a wallet-held party. Only a
/// single CREATE command is supported; `create_arguments` is JSON that maps to
/// the template's Daml record.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantPrepareSubmissionRequest {
pub template_id: TenantTemplateId,
#[cfg_attr(feature = "typegen", ts(type = "any"))]
pub create_arguments: serde_json::Value,
}

/// The prepared transaction for the wallet to sign. `prepared_transaction` and
/// `prepared_transaction_hash` are base64-encoded.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantPrepareSubmissionResponse {
pub prepared_transaction: String,
pub prepared_transaction_hash: String,
pub hashing_scheme_version: i32,
}

/// Request to execute a wallet-signed interactive submission.
/// `prepared_transaction` and `signature` are base64-encoded.
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantExecuteSubmissionRequest {
pub prepared_transaction: String,
pub signature: String,
pub signed_by: String,
pub hashing_scheme_version: i32,
}

/// One active contract owned by a tenant party.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantContract {
pub contract_id: String,
/// `package_id:module_name:entity_name`.
pub template_id: String,
/// The contract's payload rendered as JSON.
#[cfg_attr(feature = "typegen", ts(type = "any"))]
pub create_arguments: serde_json::Value,
}

/// Response wrapper for `GET /v0/tenant/{party}/acs`.
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct TenantAcsResponse {
pub contracts: Vec<TenantContract>,
}

/// Response for key status check
#[derive(Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
Expand Down Expand Up @@ -330,6 +518,26 @@ pub struct OnboardingInvitePayload {
pub workflow_instance: Option<String>,
}

/// Payload sent inside an `InviteExternalParty` Noise message. The coordinator
/// invites each hosting participant to authorize hosting the external party on
/// its own node.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "typegen", derive(ts_rs::TS), ts(optional_fields))]
pub struct ExternalPartyInvitePayload {
/// The party hint (identifier segment), so the invite card can show it.
pub party_hint: String,
/// The full hosting participant set (coordinator + peers).
pub participants: Vec<CantonId>,
/// Confirmation threshold the coordinator chose, for the invite/run cards.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confirmation_threshold: Option<u32>,
/// The coordinator's `workflow_runs` instance name for this run, echoed
/// back on decline so the coordinator only fails the matching run.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workflow_instance: Option<String>,
}

/// Payload sent inside a `DeclineInvitation` Noise message — peer telling
/// the coordinator that it has rejected an outstanding invitation so the
/// coordinator can fail its matching in-progress run with a clear error.
Expand Down
7 changes: 7 additions & 0 deletions crates/common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ pub enum WorkflowKind {
Dars,
AddParty,
ChangeThreshold,
ExternalParty,
}

impl WorkflowKind {
Expand All @@ -278,6 +279,7 @@ impl WorkflowKind {
Self::Dars => "Dars",
Self::AddParty => "AddParty",
Self::ChangeThreshold => "ChangeThreshold",
Self::ExternalParty => "ExternalParty",
}
}
}
Expand All @@ -298,6 +300,7 @@ impl std::str::FromStr for WorkflowKind {
"Dars" => Ok(Self::Dars),
"AddParty" => Ok(Self::AddParty),
"ChangeThreshold" => Ok(Self::ChangeThreshold),
"ExternalParty" => Ok(Self::ExternalParty),
other => Err(anyhow::anyhow!("unknown workflow kind: {other}")),
}
}
Expand All @@ -312,6 +315,7 @@ impl From<InvitationType> for WorkflowKind {
InvitationType::Dars => Self::Dars,
InvitationType::AddParty => Self::AddParty,
InvitationType::ChangeThreshold => Self::ChangeThreshold,
InvitationType::ExternalParty => Self::ExternalParty,
}
}
}
Expand Down Expand Up @@ -438,6 +442,7 @@ pub enum InvitationType {
Dars,
AddParty,
ChangeThreshold,
ExternalParty,
}

impl InvitationType {
Expand All @@ -450,6 +455,7 @@ impl InvitationType {
Self::Dars => "Dars",
Self::AddParty => "AddParty",
Self::ChangeThreshold => "ChangeThreshold",
Self::ExternalParty => "ExternalParty",
}
}
}
Expand All @@ -471,6 +477,7 @@ impl std::str::FromStr for InvitationType {
"Dars" => Ok(Self::Dars),
"AddParty" => Ok(Self::AddParty),
"ChangeThreshold" => Ok(Self::ChangeThreshold),
"ExternalParty" => Ok(Self::ExternalParty),
other => Err(anyhow::anyhow!("unknown invitation type: {other}")),
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/decman-cli/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ impl DecmanClient {
WorkflowKind::Dars => "/dars/cancel",
WorkflowKind::AddParty => "/add-party/cancel",
WorkflowKind::ChangeThreshold => "/change-threshold/cancel",
WorkflowKind::ExternalParty => "/external-party/cancel",
};
self.post(path, None)
}
Expand Down
3 changes: 2 additions & 1 deletion crates/decman/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "decman"
version = "1.4.3"
version = "1.5.0"
edition = "2024"
license = "Apache-2.0"

Expand Down Expand Up @@ -57,6 +57,7 @@ jsonwebtoken = { workspace = true }
mime_guess = { workspace = true }
prost = { workspace = true }
prost-types = { workspace = true }
rand = { workspace = true }
reqwest = { version = "0.13", features = ["form", "json"] }
rust-embed = { workspace = true }
secp256k1 = { workspace = true }
Expand Down
Loading
Loading