@ftptech/x402-canton-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fetch.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * wrapFetchWithCantonPayment — automate the x402 v2 "detect 402 →
3
+ * pay → retry" flow against any Canton-aware facilitator.
4
+ *
5
+ * Usage:
6
+ *
7
+ * const fetchWithPay = wrapFetchWithCantonPayment(globalThis.fetch, signer);
8
+ * const res = await fetchWithPay("https://api.example.com/data");
9
+ * const settle = readPaymentResponseHeader(res);
10
+ *
11
+ * Wire format: x402 v2 only. v1 fallback is tracked in BACKLOG.md
12
+ * and lands when an older facilitator demands it.
13
+ *
14
+ * Limitations:
15
+ * - The original request body must be replayable (e.g. JSON string,
16
+ * Uint8Array, FormData). Streaming bodies will fail on retry.
17
+ * - Idempotency: callers should treat the wrapped fetch as
18
+ * at-most-twice. If the second response is still 402 we throw —
19
+ * no infinite-loop retries.
20
+ */
21
+ import { HEADER_PAYMENT_REQUIRED_V2, HEADER_PAYMENT_SIGNATURE_V2, HEADER_PAYMENT_RESPONSE_V2, encodeBase64Json, decodeBase64Json, } from "@ftptech/x402-canton-core";
22
+ import { ExactCantonScheme, SchemeMethodMismatchError } from "./scheme.js";
23
+ /** Which on-ledger transfer methods a given signer can actually produce. */
24
+ function signerMethods(signer) {
25
+ const m = new Set();
26
+ if (signer.signTransferCommand)
27
+ m.add("external-party-amulet-rules");
28
+ if (signer.signCip56Transfer)
29
+ m.add("cip56-transfer-factory");
30
+ return m;
31
+ }
32
+ export class X402PaymentError extends Error {
33
+ code;
34
+ constructor(message, code) {
35
+ super(message);
36
+ this.code = code;
37
+ this.name = "X402PaymentError";
38
+ }
39
+ }
40
+ export function wrapFetchWithCantonPayment(fetch, signer, options = {}) {
41
+ const scheme = new ExactCantonScheme(signer);
42
+ return async function fetchWithPayment(input, init) {
43
+ // If the caller is already attempting payment (has the signature
44
+ // header) just pass through — don't recurse.
45
+ const initHeaders = new Headers(init?.headers);
46
+ if (initHeaders.has(HEADER_PAYMENT_SIGNATURE_V2)) {
47
+ return fetch(input, init);
48
+ }
49
+ const first = await fetch(input, init);
50
+ if (first.status !== 402)
51
+ return first;
52
+ const requiredHeader = first.headers.get(HEADER_PAYMENT_REQUIRED_V2);
53
+ if (!requiredHeader) {
54
+ throw new X402PaymentError("402 response missing PAYMENT-REQUIRED header (v1 wire format not supported in client v0.1)", "MISSING_PAYMENT_REQUIRED_HEADER");
55
+ }
56
+ // The PAYMENT-REQUIRED header is server-controlled and untrusted:
57
+ // bad base64, non-JSON, or JSON missing `accepts[]` must surface as a
58
+ // discriminated X402PaymentError, not leak a raw SyntaxError/TypeError
59
+ // out of the wrapped fetch (the whole contract of this wrapper is that
60
+ // it only ever throws X402PaymentError).
61
+ let required;
62
+ try {
63
+ required = decodeBase64Json(requiredHeader);
64
+ }
65
+ catch {
66
+ throw new X402PaymentError("402 PAYMENT-REQUIRED header is not valid base64-encoded JSON", "MALFORMED_PAYMENT_REQUIRED");
67
+ }
68
+ if (!required || !Array.isArray(required.accepts)) {
69
+ throw new X402PaymentError("402 PAYMENT-REQUIRED payload missing a valid accepts[] array", "MALFORMED_PAYMENT_REQUIRED");
70
+ }
71
+ const candidates = required.accepts.filter((a) => {
72
+ if (a.scheme !== "exact-canton")
73
+ return false;
74
+ if (options.networkFilter && !options.networkFilter(a.network))
75
+ return false;
76
+ return true;
77
+ });
78
+ if (candidates.length === 0) {
79
+ throw new X402PaymentError("no exact-canton entry in 402 accepts[] (or networkFilter excluded all)", "NO_ACCEPTABLE_SCHEME");
80
+ }
81
+ // Narrow to entries whose transferMethod THIS signer can actually produce.
82
+ // A merchant may advertise both v1 (external-party-amulet-rules) and cip56;
83
+ // a CIP-56-only wallet (e.g. the agent-wallet relay signer) must pick the
84
+ // cip56 entry rather than blindly take accepts[0] and fail. If the merchant
85
+ // advertised exact-canton entries but NONE match this wallet, raise a clear,
86
+ // named mismatch error instead of a cryptic downstream failure (plan §4.1/3).
87
+ const methods = signerMethods(signer);
88
+ const compatible = candidates.filter((a) => methods.has(a.extra.transferMethod));
89
+ if (compatible.length === 0) {
90
+ const required_ = [
91
+ ...new Set(candidates.map((a) => a.extra.transferMethod)),
92
+ ].join(", ");
93
+ const supported = [...methods].join(", ") || "(none)";
94
+ throw new SchemeMethodMismatchError(required_, supported);
95
+ }
96
+ const chosen = options.selectRequirements?.(compatible) ?? compatible[0];
97
+ const envelope = await scheme.createPaymentPayload(chosen, required.resource);
98
+ const retryHeaders = new Headers(init?.headers);
99
+ retryHeaders.set(HEADER_PAYMENT_SIGNATURE_V2, encodeBase64Json(envelope));
100
+ const retryInit = { ...init, headers: retryHeaders };
101
+ const second = await fetch(input, retryInit);
102
+ if (second.status === 402) {
103
+ throw new X402PaymentError("payment retry still returned 402 — facilitator rejected the payment", "PAYMENT_REJECTED");
104
+ }
105
+ return second;
106
+ };
107
+ }
108
+ /**
109
+ * Decode the `PAYMENT-RESPONSE` header (x402 v2) attached to a
110
+ * response that came back through wrapFetchWithCantonPayment.
111
+ * Returns null when the header is absent (e.g. response unrelated to
112
+ * x402).
113
+ */
114
+ export function readPaymentResponseHeader(response) {
115
+ const header = response.headers.get(HEADER_PAYMENT_RESPONSE_V2);
116
+ if (!header)
117
+ return null;
118
+ return decodeBase64Json(header);
119
+ }
120
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from "./signer.js";
2
+ export * from "./scheme.js";
3
+ export * from "./fetch.js";
4
+ export * from "./keyfile-signer.js";
5
+ export * from "./cip56-signer.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./signer.js";
2
+ export * from "./scheme.js";
3
+ export * from "./fetch.js";
4
+ export * from "./keyfile-signer.js";
5
+ export * from "./cip56-signer.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * KeyfileSigner — production `CantonSigner` implementation backed by
3
+ * a local Ed25519 PEM key, the JSON Ledger API v2's interactive-
4
+ * submission flow, and the Scan API for nonce + DSO lookups.
5
+ *
6
+ * Flow per `signTransferCommand`:
7
+ *
8
+ * 1. Read sender's next-expected nonce from
9
+ * `ScanClient.getTransferCommandCounter(party)`. First payment
10
+ * from a new party falls back to nonce 0.
11
+ * 2. Prepare an ExerciseCommand for
12
+ * `ExternalPartyAmuletRules_CreateTransferCommand` with the
13
+ * caller-supplied receiver / delegate / amount / description and
14
+ * our just-resolved nonce. The `ExternalPartyAmuletRules`
15
+ * contract id + template id + createdEventBlob must be supplied
16
+ * via `deps.externalPartyAmuletRules` (caller fetches it from
17
+ * Scan once at startup or via `loadExternalPartyAmuletRules`).
18
+ * 3. `CantonExternalPartySigner.prepareSignAndExecute` lands the
19
+ * transaction. The execute endpoint returns `updateId` but NOT
20
+ * events.
21
+ * 4. Query ACS via `CantonClient.queryActiveContracts` and filter
22
+ * to TransferCommands matching `(sender, nonce)`. The
23
+ * `TransferCommandCounter` ensures (sender, nonce) is unique,
24
+ * so the match is the cid we just created.
25
+ * 5. Return `{transferCommandCid, payerParty, nonce}`.
26
+ *
27
+ * Open question (logged in CRON-RUNBOOK.md):
28
+ * - The exact choice argument shape for
29
+ * `ExternalPartyAmuletRules_CreateTransferCommand` is inferred
30
+ * from the Splice source comment ("sender, receiver, delegate,
31
+ * amount, expiresAt, nonce, description, expectedDso"). Confirm
32
+ * against a live participant; adjust if the participant rejects.
33
+ */
34
+ import { CantonClient, CantonExternalPartySigner, ScanClient, type ExternalPartyKey, type ScanFlavor } from "@ftptech/x402-canton-ledger";
35
+ import type { CantonSigner, SignedTransferCommand, SignTransferCommandInput } from "./signer.js";
36
+ export interface ExternalPartyAmuletRulesRef {
37
+ contractId: string;
38
+ /**
39
+ * RESOLVED package-id form of the ExternalPartyAmuletRules template, taken
40
+ * verbatim from Scan's `template_id`
41
+ * (e.g. `<pkgHash>:Splice.ExternalPartyAmuletRules:ExternalPartyAmuletRules`).
42
+ *
43
+ * MUST NOT be the `#package-name` form: the create flows through
44
+ * interactive-submission/prepare, which cannot parse a leading `#`
45
+ * (the participant rejects it with "non expected character 0x23").
46
+ * Use `loadExternalPartyAmuletRules(scan)` to fetch a correct ref.
47
+ *
48
+ * This is the EPAR's OWN package (its creation version), used ONLY for the
49
+ * disclosed-contract entry — it must match `createdEventBlob`'s package or the
50
+ * participant rejects the prepare (DisclosedContract.template_id mismatch).
51
+ * The exercise itself targets `exerciseTemplateId`.
52
+ */
53
+ templateId: string;
54
+ /** Base64-encoded `created_event_blob` from Scan. Required for the
55
+ * disclosedContracts entry. */
56
+ createdEventBlob: string;
57
+ /**
58
+ * Template id for the `ExternalPartyAmuletRules_CreateTransferCommand`
59
+ * EXERCISE: `<currentSpliceAmuletPkg>:Splice.ExternalPartyAmuletRules:ExternalPartyAmuletRules`,
60
+ * where the package is the CURRENT splice-amulet (what `#splice-amulet`
61
+ * resolves to), taken from the live AmuletRules contract.
62
+ *
63
+ * Separate from `templateId` because the EPAR singleton can sit on an OLDER
64
+ * package whose `CreateTransferCommand` lacks `description`/`expectedDso`,
65
+ * while the current package has them (verified live on mainnet). Canton
66
+ * smart-contract-upgrade reconciles the older disclosed contract with this
67
+ * newer exercise package. Must also be the resolved package-id form (no `#`).
68
+ */
69
+ exerciseTemplateId: string;
70
+ }
71
+ export interface KeyfileSignerDeps {
72
+ /** Wraps interactive-submission/prepare + execute. */
73
+ signer: CantonExternalPartySigner;
74
+ /** For ACS lookup of the newly-created TransferCommand cid. */
75
+ client: CantonClient;
76
+ /** For TransferCommandCounter (nonce). */
77
+ scan: ScanClient;
78
+ /** The Ed25519 keypair this signer signs with. */
79
+ key: ExternalPartyKey;
80
+ /** The agent's Canton party id. */
81
+ party: string;
82
+ /** The ExternalPartyAmuletRules contract reference (disclosed). */
83
+ externalPartyAmuletRules: ExternalPartyAmuletRulesRef;
84
+ /** DSO party id for the `expectedDso` field of
85
+ * `ExternalPartyAmuletRules_CreateTransferCommand`. If omitted, it's
86
+ * derived from the per-call synchronizerId (`global-domain::<hash>` →
87
+ * `DSO::<hash>`). Set explicitly if your network uses a
88
+ * non-conventional DSO/synchronizer naming. */
89
+ dso?: string;
90
+ /** Ledger user id submitted in the JsCommands envelope. */
91
+ userId?: string;
92
+ /** Default TransferCommand expiry window in seconds. Default 60. */
93
+ defaultExpirySeconds?: number;
94
+ /** Post-execute ACS poll attempts. interactive-submission/execute is async
95
+ * (the create commits on the completion stream AFTER execute returns), so
96
+ * the created TransferCommand is not visible immediately. Default 20. */
97
+ acsLookupAttempts?: number;
98
+ /** Delay between ACS poll attempts, ms. Default 1000. */
99
+ acsLookupDelayMs?: number;
100
+ }
101
+ /**
102
+ * Derive the DSO party id from a Global Synchronizer id.
103
+ *
104
+ * On Canton the DSO party and the global synchronizer share the same
105
+ * namespace fingerprint; the synchronizer id is `global-domain::<hash>`
106
+ * and the DSO party is `DSO::<hash>`. Verified live on DevNet:
107
+ * synchronizer `global-domain::1220be58c…` ↔ dso `DSO::1220be58c…`
108
+ * (the EPAR + AmuletRules payloads both report this dso). Returns null
109
+ * if the input doesn't have the expected `global-domain::` prefix.
110
+ */
111
+ export declare function deriveDsoFromSynchronizer(synchronizerId: string): string | null;
112
+ /**
113
+ * Fetch the live ExternalPartyAmuletRules + AmuletRules contracts from Scan and
114
+ * return a ready-to-use `ExternalPartyAmuletRulesRef`. `templateId` is the
115
+ * EPAR's own resolved package (for the disclosed contract); `exerciseTemplateId`
116
+ * is the CURRENT splice-amulet package (derived from AmuletRules) for the
117
+ * exercise. Both are the resolved package-id form — what
118
+ * interactive-submission/prepare requires, never `#package-name`. Call once at
119
+ * startup and pass the result as `KeyfileSignerDeps.externalPartyAmuletRules`.
120
+ */
121
+ export declare function loadExternalPartyAmuletRules(scan: ScanClient): Promise<ExternalPartyAmuletRulesRef>;
122
+ export declare class KeyfileSigner implements CantonSigner {
123
+ private readonly deps;
124
+ readonly party: string;
125
+ constructor(deps: KeyfileSignerDeps);
126
+ signTransferCommand(input: SignTransferCommandInput): Promise<SignedTransferCommand>;
127
+ }
128
+ /**
129
+ * Load an Ed25519 private key from a PEM file and derive the matching
130
+ * public key + canonical fingerprint via
131
+ * `ed25519KeyFromNodeKeyPair`.
132
+ *
133
+ * Either `keyPath` or `keyPem` must be supplied.
134
+ */
135
+ export declare function loadEd25519KeyFromPem(args: {
136
+ keyPath?: string;
137
+ keyPem?: string;
138
+ fingerprint?: string;
139
+ }): ExternalPartyKey;
140
+ export interface MakeKeyfileSignerConfig {
141
+ /** Path to an Ed25519 PEM file. Mutually exclusive with `keyPem`. */
142
+ keyPath?: string;
143
+ /** Ed25519 PEM string (e.g. from an env var). */
144
+ keyPem?: string;
145
+ /** Optional fingerprint override (when participant canonicalizes
146
+ * the public key differently than SPKI-DER-SHA256). */
147
+ fingerprint?: string;
148
+ /** Agent's Canton party id (sender of TransferCommands). */
149
+ party: string;
150
+ /** JSON Ledger API v2 base URL for the agent's participant. */
151
+ participantUrl: string;
152
+ /** Bearer JWT for the agent's participant. */
153
+ participantToken: string;
154
+ /** Daml package name used in `templateRef`. */
155
+ packageName?: string;
156
+ /** Scan API base URL. */
157
+ scanUrl: string;
158
+ /** Optional Scan bearer token. */
159
+ scanToken?: string;
160
+ /** Scan flavor — "validator" (validator-local proxy) or "sv"
161
+ * (public SV Scan). Default "validator". */
162
+ scanFlavor?: ScanFlavor;
163
+ /** ExternalPartyAmuletRules disclosed-contract reference (operator
164
+ * fetches once at startup from Scan). */
165
+ externalPartyAmuletRules: ExternalPartyAmuletRulesRef;
166
+ /** Optional explicit DSO party id for `expectedDso`. If omitted,
167
+ * derived from the synchronizerId (`global-domain::<hash>` →
168
+ * `DSO::<hash>`). */
169
+ dso?: string;
170
+ /** Optional ledger user id passed in JsCommands. Default
171
+ * "x402-agent". */
172
+ userId?: string;
173
+ /** Optional default TransferCommand expiry seconds. Default 60. */
174
+ defaultExpirySeconds?: number;
175
+ /** Optional HTTP timeout for both participant + Scan. */
176
+ timeoutMs?: number;
177
+ }
178
+ /**
179
+ * Convenience builder that wires up `CantonClient`, `ScanClient`,
180
+ * `CantonExternalPartySigner`, and `loadEd25519KeyFromPem` from a
181
+ * flat config object so callers don't have to construct each
182
+ * dependency manually.
183
+ */
184
+ export declare function makeKeyfileSigner(config: MakeKeyfileSignerConfig): KeyfileSigner;
185
+ //# sourceMappingURL=keyfile-signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyfile-signer.d.ts","sourceRoot":"","sources":["../src/keyfile-signer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * KeyfileSigner — production `CantonSigner` implementation backed by
3
+ * a local Ed25519 PEM key, the JSON Ledger API v2's interactive-
4
+ * submission flow, and the Scan API for nonce + DSO lookups.
5
+ *
6
+ * Flow per `signTransferCommand`:
7
+ *
8
+ * 1. Read sender's next-expected nonce from
9
+ * `ScanClient.getTransferCommandCounter(party)`. First payment
10
+ * from a new party falls back to nonce 0.
11
+ * 2. Prepare an ExerciseCommand for
12
+ * `ExternalPartyAmuletRules_CreateTransferCommand` with the
13
+ * caller-supplied receiver / delegate / amount / description and
14
+ * our just-resolved nonce. The `ExternalPartyAmuletRules`
15
+ * contract id + template id + createdEventBlob must be supplied
16
+ * via `deps.externalPartyAmuletRules` (caller fetches it from
17
+ * Scan once at startup or via `loadExternalPartyAmuletRules`).
18
+ * 3. `CantonExternalPartySigner.prepareSignAndExecute` lands the
19
+ * transaction. The execute endpoint returns `updateId` but NOT
20
+ * events.
21
+ * 4. Query ACS via `CantonClient.queryActiveContracts` and filter
22
+ * to TransferCommands matching `(sender, nonce)`. The
23
+ * `TransferCommandCounter` ensures (sender, nonce) is unique,
24
+ * so the match is the cid we just created.
25
+ * 5. Return `{transferCommandCid, payerParty, nonce}`.
26
+ *
27
+ * Open question (logged in CRON-RUNBOOK.md):
28
+ * - The exact choice argument shape for
29
+ * `ExternalPartyAmuletRules_CreateTransferCommand` is inferred
30
+ * from the Splice source comment ("sender, receiver, delegate,
31
+ * amount, expiresAt, nonce, description, expectedDso"). Confirm
32
+ * against a live participant; adjust if the participant rejects.
33
+ */
34
+ import { readFileSync } from "node:fs";
35
+ import { createPrivateKey, createPublicKey, } from "node:crypto";
36
+ import { CantonClient, CantonExternalPartySigner, ScanClient, ed25519KeyFromNodeKeyPair, } from "@ftptech/x402-canton-ledger";
37
+ const TRANSFER_COMMAND_TEMPLATE_SUFFIX = ":Splice.ExternalPartyAmuletRules:TransferCommand";
38
+ const CREATE_TRANSFER_COMMAND_CHOICE = "ExternalPartyAmuletRules_CreateTransferCommand";
39
+ /**
40
+ * Derive the DSO party id from a Global Synchronizer id.
41
+ *
42
+ * On Canton the DSO party and the global synchronizer share the same
43
+ * namespace fingerprint; the synchronizer id is `global-domain::<hash>`
44
+ * and the DSO party is `DSO::<hash>`. Verified live on DevNet:
45
+ * synchronizer `global-domain::1220be58c…` ↔ dso `DSO::1220be58c…`
46
+ * (the EPAR + AmuletRules payloads both report this dso). Returns null
47
+ * if the input doesn't have the expected `global-domain::` prefix.
48
+ */
49
+ export function deriveDsoFromSynchronizer(synchronizerId) {
50
+ const prefix = "global-domain::";
51
+ if (!synchronizerId.startsWith(prefix))
52
+ return null;
53
+ return `DSO::${synchronizerId.slice(prefix.length)}`;
54
+ }
55
+ /**
56
+ * Fetch the live ExternalPartyAmuletRules + AmuletRules contracts from Scan and
57
+ * return a ready-to-use `ExternalPartyAmuletRulesRef`. `templateId` is the
58
+ * EPAR's own resolved package (for the disclosed contract); `exerciseTemplateId`
59
+ * is the CURRENT splice-amulet package (derived from AmuletRules) for the
60
+ * exercise. Both are the resolved package-id form — what
61
+ * interactive-submission/prepare requires, never `#package-name`. Call once at
62
+ * startup and pass the result as `KeyfileSignerDeps.externalPartyAmuletRules`.
63
+ */
64
+ export async function loadExternalPartyAmuletRules(scan) {
65
+ const [eparRes, amuletRulesRes] = await Promise.all([
66
+ scan.getExternalPartyAmuletRules(),
67
+ scan.getAmuletRules(),
68
+ ]);
69
+ const c = eparRes.external_party_amulet_rules.contract;
70
+ // The EXERCISE must target the CURRENT splice-amulet package (what
71
+ // `#splice-amulet` resolves to), which carries the full CreateTransferCommand
72
+ // signature (description/expectedDso). The EPAR singleton itself may sit on an
73
+ // older package, so derive the current package from the live AmuletRules
74
+ // contract (same splice-amulet DAR — both modules ship together).
75
+ const currentPkg = amuletRulesRes.amulet_rules.contract.template_id.split(":")[0];
76
+ return {
77
+ contractId: c.contract_id,
78
+ templateId: c.template_id,
79
+ createdEventBlob: c.created_event_blob,
80
+ exerciseTemplateId: `${currentPkg}:Splice.ExternalPartyAmuletRules:ExternalPartyAmuletRules`,
81
+ };
82
+ }
83
+ export class KeyfileSigner {
84
+ deps;
85
+ party;
86
+ constructor(deps) {
87
+ this.deps = deps;
88
+ this.party = deps.party;
89
+ }
90
+ async signTransferCommand(input) {
91
+ // The create flows through interactive-submission/prepare, which cannot
92
+ // parse the `#package-name` templateId form (participant rejects it with
93
+ // "non expected character 0x23"). submit-and-wait tolerates `#name`, so a
94
+ // local-party smoke would not surface this. Fail fast before any I/O.
95
+ const eparRef = this.deps.externalPartyAmuletRules;
96
+ for (const [field, value] of [
97
+ ["templateId", eparRef.templateId],
98
+ ["exerciseTemplateId", eparRef.exerciseTemplateId],
99
+ ]) {
100
+ if (value.startsWith("#")) {
101
+ throw new Error(`KeyfileSigner: externalPartyAmuletRules.${field} must be the ` +
102
+ 'resolved package-id form (from Scan), not the "#package-name" ' +
103
+ "form — interactive-submission/prepare cannot parse a leading `#`. " +
104
+ `Use loadExternalPartyAmuletRules(scan). Got: ${value}`);
105
+ }
106
+ }
107
+ // 1. Resolve nonce.
108
+ let nextNonce = 0n;
109
+ try {
110
+ const counter = await this.deps.scan.getTransferCommandCounter(this.party);
111
+ nextNonce = BigInt(counter.transfer_command_counter.contract.payload.nextNonce);
112
+ }
113
+ catch {
114
+ // First payment from this party — counter does not exist yet.
115
+ }
116
+ // 2. Build choice argument.
117
+ const defaultExpirySeconds = this.deps.defaultExpirySeconds ?? 60;
118
+ const expiresAtMs = input.expiresAtMs ?? Date.now() + defaultExpirySeconds * 1000;
119
+ const epar = this.deps.externalPartyAmuletRules;
120
+ // `ExternalPartyAmuletRules_CreateTransferCommand` REQUIRES
121
+ // `expectedDso` (the DSO party id) — the participant rejects the
122
+ // prepare otherwise. Prefer an explicitly configured dso; else derive
123
+ // it from the synchronizer id, which on Canton is the same namespace
124
+ // with a `DSO` prefix instead of `global-domain` (verified live on
125
+ // DevNet: synchronizer `global-domain::1220<hash>` ↔ dso
126
+ // `DSO::1220<hash>`, and the cn-quickstart reference flow does the
127
+ // same swap). Fail loudly if neither is available rather than send an
128
+ // argument the ledger will reject.
129
+ const expectedDso = this.deps.dso ?? deriveDsoFromSynchronizer(input.synchronizerId);
130
+ if (!expectedDso) {
131
+ throw new Error("KeyfileSigner: cannot determine expectedDso — pass deps.dso, or a " +
132
+ `synchronizerId of the form "global-domain::<hash>" (got "${input.synchronizerId}")`);
133
+ }
134
+ const choiceArgument = {
135
+ sender: this.party,
136
+ receiver: input.receiver,
137
+ delegate: input.delegate,
138
+ amount: input.amount,
139
+ expiresAt: new Date(expiresAtMs).toISOString(),
140
+ nonce: nextNonce.toString(),
141
+ description: input.description,
142
+ expectedDso,
143
+ };
144
+ // 3. Prepare → sign → execute.
145
+ const userId = this.deps.userId ?? "x402-agent";
146
+ const commandId = `xfer-cmd-${Date.now()}-${Math.random()
147
+ .toString(36)
148
+ .slice(2, 10)}`;
149
+ await this.deps.signer.prepareSignAndExecute({
150
+ userId,
151
+ commandId,
152
+ actAs: [this.party],
153
+ synchronizerId: input.synchronizerId,
154
+ disclosedContracts: [
155
+ {
156
+ templateId: epar.templateId,
157
+ contractId: epar.contractId,
158
+ createdEventBlob: epar.createdEventBlob,
159
+ synchronizerId: input.synchronizerId,
160
+ },
161
+ ],
162
+ commands: [
163
+ {
164
+ ExerciseCommand: {
165
+ templateId: epar.exerciseTemplateId,
166
+ contractId: epar.contractId,
167
+ choice: CREATE_TRANSFER_COMMAND_CHOICE,
168
+ choiceArgument,
169
+ },
170
+ },
171
+ ],
172
+ }, this.deps.key);
173
+ // 4. Look up the created TransferCommand cid via ACS, matching
174
+ // (sender, nonce). interactive-submission/execute is ASYNC — it returns
175
+ // before the transaction commits (the commit lands on the completion
176
+ // stream), so poll the ACS until the contract appears. The
177
+ // TransferCommandCounter enforces nonce monotonicity per sender, so the
178
+ // (sender, nonce) match is unique.
179
+ const nonceStr = nextNonce.toString();
180
+ const attempts = this.deps.acsLookupAttempts ?? 20;
181
+ const delayMs = this.deps.acsLookupDelayMs ?? 1000;
182
+ let foundCid;
183
+ for (let attempt = 0; attempt < attempts && !foundCid; attempt++) {
184
+ if (attempt > 0)
185
+ await new Promise((r) => setTimeout(r, delayMs));
186
+ const events = await this.deps.client.queryActiveContracts({
187
+ filtersByParty: {
188
+ [this.party]: {
189
+ cumulative: [
190
+ {
191
+ identifierFilter: {
192
+ WildcardFilter: {
193
+ value: { includeCreatedEventBlob: false },
194
+ },
195
+ },
196
+ },
197
+ ],
198
+ },
199
+ },
200
+ });
201
+ for (const e of events) {
202
+ if (!e.templateId.endsWith(TRANSFER_COMMAND_TEMPLATE_SUFFIX))
203
+ continue;
204
+ const p = e.createArgument;
205
+ if (p.sender === this.party && p.nonce === nonceStr) {
206
+ foundCid = e.contractId;
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ if (!foundCid) {
212
+ throw new Error(`KeyfileSigner: created TransferCommand not found in ACS after execute (party=${this.party}, nonce=${nonceStr}, polled ${attempts}x)`);
213
+ }
214
+ return {
215
+ transferCommandCid: foundCid,
216
+ payerParty: this.party,
217
+ nonce: Number(nextNonce),
218
+ };
219
+ }
220
+ }
221
+ /**
222
+ * Load an Ed25519 private key from a PEM file and derive the matching
223
+ * public key + canonical fingerprint via
224
+ * `ed25519KeyFromNodeKeyPair`.
225
+ *
226
+ * Either `keyPath` or `keyPem` must be supplied.
227
+ */
228
+ export function loadEd25519KeyFromPem(args) {
229
+ const pem = args.keyPem ??
230
+ (args.keyPath ? readFileSync(args.keyPath, "utf8") : null);
231
+ if (!pem) {
232
+ throw new Error("loadEd25519KeyFromPem: provide keyPath or keyPem");
233
+ }
234
+ const privateKey = createPrivateKey({ key: pem, format: "pem" });
235
+ const publicKey = createPublicKey(privateKey);
236
+ return ed25519KeyFromNodeKeyPair({
237
+ privateKey,
238
+ publicKey,
239
+ ...(args.fingerprint !== undefined ? { fingerprint: args.fingerprint } : {}),
240
+ });
241
+ }
242
+ /**
243
+ * Convenience builder that wires up `CantonClient`, `ScanClient`,
244
+ * `CantonExternalPartySigner`, and `loadEd25519KeyFromPem` from a
245
+ * flat config object so callers don't have to construct each
246
+ * dependency manually.
247
+ */
248
+ export function makeKeyfileSigner(config) {
249
+ const client = new CantonClient({
250
+ participantUrl: config.participantUrl,
251
+ token: config.participantToken,
252
+ packageName: config.packageName ?? "splice-amulet",
253
+ ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
254
+ });
255
+ const scan = new ScanClient({
256
+ scanUrl: config.scanUrl,
257
+ ...(config.scanToken !== undefined ? { token: config.scanToken } : {}),
258
+ ...(config.scanFlavor !== undefined ? { flavor: config.scanFlavor } : {}),
259
+ ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
260
+ });
261
+ const signer = new CantonExternalPartySigner(client);
262
+ const key = loadEd25519KeyFromPem({
263
+ ...(config.keyPath !== undefined ? { keyPath: config.keyPath } : {}),
264
+ ...(config.keyPem !== undefined ? { keyPem: config.keyPem } : {}),
265
+ ...(config.fingerprint !== undefined ? { fingerprint: config.fingerprint } : {}),
266
+ });
267
+ return new KeyfileSigner({
268
+ signer,
269
+ client,
270
+ scan,
271
+ key,
272
+ party: config.party,
273
+ externalPartyAmuletRules: config.externalPartyAmuletRules,
274
+ ...(config.dso !== undefined ? { dso: config.dso } : {}),
275
+ ...(config.userId !== undefined ? { userId: config.userId } : {}),
276
+ ...(config.defaultExpirySeconds !== undefined
277
+ ? { defaultExpirySeconds: config.defaultExpirySeconds }
278
+ : {}),
279
+ });
280
+ }
281
+ //# sourceMappingURL=keyfile-signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyfile-signer.js","sourceRoot":"","sources":["../src/keyfile-signer.ts"],"names":[],"mappings":"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"}