@ftptech/x402-canton-core 0.1.0 → 0.2.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.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Canton Coin amount-unit conversion (x402-ENVELOPE upstream convention).
3
+ *
4
+ * The x402 v2 wire field `PaymentRequirements.maxAmountRequired` (this scheme's
5
+ * `amount`) travels in ATOMIC UNITS as an integer string, while the on-ledger
6
+ * Daml `Decimal` the Token Standard / TransferCommand uses is a fixed-scale
7
+ * decimal string. The conversion boundary is fixed across CIP-56 by the Daml
8
+ * `Decimal` scale: **1 CC = 10^10 atomic units** (10 decimal places).
9
+ *
10
+ * BACK-COMPAT (read this): the DEPLOYED v1 (external-party-amulet-rules, live on
11
+ * MainNet) and cip56 paths put a Daml **Decimal** string on the x402 wire today
12
+ * (e.g. `"0.0100000000"`), NOT atomic integer units — every deployed client
13
+ * signs the Decimal verbatim into the ledger and the facilitator compares it
14
+ * byte-for-byte. These helpers therefore exist so a deploy that OPTS IN to the
15
+ * atomic-on-wire convention can convert EXACTLY at the boundary; they do NOT
16
+ * change the default wire unit (see specs/scheme_exact_canton.upstream.md
17
+ * § Amount units). The maths is pure BigInt/string so the round-trip never
18
+ * drifts by 10^10 (the off-by-scale footgun) and never goes through float.
19
+ *
20
+ * Mirrors `packages/pay-proxy/src/spend-budget-store.ts`'s `toAtomic` (the same
21
+ * "compare as BigInt atomic, never Number()" guardrail) but adds the inverse and
22
+ * a strict integer-atomic parser, and lives in core so client + facilitator
23
+ * share one implementation.
24
+ */
25
+ /** Daml Decimal scale for Canton Coin / Amulet — 10 fractional digits. */
26
+ export declare const CC_ATOMIC_SCALE = 10;
27
+ /**
28
+ * Convert a Daml Decimal CC string (e.g. `"0.1"`, `"1.0000000000"`) to integer
29
+ * atomic units as a decimal string (e.g. `"1000000000"`, `"10000000000"`).
30
+ *
31
+ * Fail-CLOSED: throws on a non-numeric value, a negative value, or a fractional
32
+ * part with MORE than `CC_ATOMIC_SCALE` significant digits (which would lose
33
+ * precision) — a malformed amount must never silently produce a wrong integer.
34
+ * Trailing zeros within the scale are fine (`"0.1"` and `"0.1000000000"` both
35
+ * → `"1000000000"`).
36
+ */
37
+ export declare function decimalToAtomicCC(dec: string): string;
38
+ /**
39
+ * Convert integer atomic units (decimal string, e.g. `"1"`, `"10000000000"`) to
40
+ * a fixed-scale Daml Decimal CC string with exactly `CC_ATOMIC_SCALE` fractional
41
+ * digits (e.g. `"0.0000000001"`, `"1.0000000000"`).
42
+ *
43
+ * Fail-CLOSED: throws on a non-integer / non-numeric / negative input. The
44
+ * output is the canonical fixed-scale form the Token Standard ledger uses, so
45
+ * `decimalToAtomicCC(atomicToDecimalCC(x)) === x` and
46
+ * `atomicToDecimalCC(decimalToAtomicCC(d))` is `d` normalized to 10 dp.
47
+ */
48
+ export declare function atomicToDecimalCC(atomic: string): string;
49
+ /** True iff this x402 scheme string carries the amount in ATOMIC integer units
50
+ * on the wire ("exact" — the x402-ENVELOPE convention). The legacy
51
+ * "exact-canton" (and anything else) carries a Daml Decimal. Mirrors
52
+ * `schemeMatches`'s "exact" is canonical / "exact-canton" is legacy split. */
53
+ export declare function schemeIsAtomic(scheme: string): boolean;
54
+ /**
55
+ * Convert an x402 WIRE amount to the on-ledger Daml **Decimal** it denotes,
56
+ * keyed by the wire's scheme. Atomic scheme ("exact") => `atomicToDecimalCC`;
57
+ * legacy/Decimal scheme ("exact-canton", anything else) => passthrough (the wire
58
+ * value already IS the Decimal). Fail-CLOSED via the underlying converter (a
59
+ * non-integer atomic value, or a malformed Decimal at a later comparison,
60
+ * throws rather than silently mis-comparing). Use this at EVERY site that
61
+ * compares a wire amount against an on-ledger Decimal.
62
+ */
63
+ export declare function wireAmountToLedgerDecimal(scheme: string, wireAmount: string): string;
64
+ /**
65
+ * Inverse of `wireAmountToLedgerDecimal`: given an on-ledger Daml Decimal,
66
+ * produce the WIRE amount for the given scheme. Atomic scheme ("exact") =>
67
+ * `decimalToAtomicCC`; legacy/Decimal scheme => passthrough. Used by the client
68
+ * builder / signer seam when emitting under the atomic scheme.
69
+ */
70
+ export declare function ledgerDecimalToWireAmount(scheme: string, decimal: string): string;
71
+ /**
72
+ * Canonical VALUE-equality of two on-ledger Daml CC Decimal strings: compares
73
+ * by BigInt atomic units so "0.1" ≡ "0.1000000000" and a 10x/0.1x difference
74
+ * provably never folds. Fail-CLOSED: a malformed Decimal on either side throws
75
+ * (via `decimalToAtomicCC`), so a comparison can never silently pass on junk.
76
+ * This is the exactness guarantee the spec asks for at every amount-compare site
77
+ * (replaces a raw string `!==`).
78
+ */
79
+ export declare function ledgerDecimalEquals(a: string, b: string): boolean;
80
+ /**
81
+ * Non-throwing, FAIL-CLOSED variant of `ledgerDecimalEquals` for the
82
+ * facilitator amount-validation arms: returns `true` only when both inputs are
83
+ * well-formed Daml CC Decimals of equal value; a malformed/junk value on either
84
+ * side returns `false` (→ amount_mismatch) instead of throwing. Use this at the
85
+ * pure validator sites (validateTransferCommand / validateAllocation /
86
+ * validateCip56*) that must map to a discriminated reject, never a 5xx.
87
+ */
88
+ export declare function ledgerDecimalsMatch(a: string, b: string): boolean;
89
+ //# sourceMappingURL=amount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount.d.ts","sourceRoot":"","sources":["../src/amount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,eAAe,KAAK,CAAC;AAIlC;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsBrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASxD;AAwBD;;;+EAG+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAMjE"}
package/dist/amount.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Canton Coin amount-unit conversion (x402-ENVELOPE upstream convention).
3
+ *
4
+ * The x402 v2 wire field `PaymentRequirements.maxAmountRequired` (this scheme's
5
+ * `amount`) travels in ATOMIC UNITS as an integer string, while the on-ledger
6
+ * Daml `Decimal` the Token Standard / TransferCommand uses is a fixed-scale
7
+ * decimal string. The conversion boundary is fixed across CIP-56 by the Daml
8
+ * `Decimal` scale: **1 CC = 10^10 atomic units** (10 decimal places).
9
+ *
10
+ * BACK-COMPAT (read this): the DEPLOYED v1 (external-party-amulet-rules, live on
11
+ * MainNet) and cip56 paths put a Daml **Decimal** string on the x402 wire today
12
+ * (e.g. `"0.0100000000"`), NOT atomic integer units — every deployed client
13
+ * signs the Decimal verbatim into the ledger and the facilitator compares it
14
+ * byte-for-byte. These helpers therefore exist so a deploy that OPTS IN to the
15
+ * atomic-on-wire convention can convert EXACTLY at the boundary; they do NOT
16
+ * change the default wire unit (see specs/scheme_exact_canton.upstream.md
17
+ * § Amount units). The maths is pure BigInt/string so the round-trip never
18
+ * drifts by 10^10 (the off-by-scale footgun) and never goes through float.
19
+ *
20
+ * Mirrors `packages/pay-proxy/src/spend-budget-store.ts`'s `toAtomic` (the same
21
+ * "compare as BigInt atomic, never Number()" guardrail) but adds the inverse and
22
+ * a strict integer-atomic parser, and lives in core so client + facilitator
23
+ * share one implementation.
24
+ */
25
+ /** Daml Decimal scale for Canton Coin / Amulet — 10 fractional digits. */
26
+ export const CC_ATOMIC_SCALE = 10;
27
+ const SCALE_FACTOR = 10n ** BigInt(CC_ATOMIC_SCALE);
28
+ /**
29
+ * Convert a Daml Decimal CC string (e.g. `"0.1"`, `"1.0000000000"`) to integer
30
+ * atomic units as a decimal string (e.g. `"1000000000"`, `"10000000000"`).
31
+ *
32
+ * Fail-CLOSED: throws on a non-numeric value, a negative value, or a fractional
33
+ * part with MORE than `CC_ATOMIC_SCALE` significant digits (which would lose
34
+ * precision) — a malformed amount must never silently produce a wrong integer.
35
+ * Trailing zeros within the scale are fine (`"0.1"` and `"0.1000000000"` both
36
+ * → `"1000000000"`).
37
+ */
38
+ export function decimalToAtomicCC(dec) {
39
+ if (typeof dec !== "string") {
40
+ throw new Error(`invalid CC decimal amount: ${JSON.stringify(dec)}`);
41
+ }
42
+ const m = /^(\d+)(?:\.(\d+))?$/.exec(dec.trim());
43
+ if (!m) {
44
+ throw new Error(`invalid CC decimal amount: ${JSON.stringify(dec)}`);
45
+ }
46
+ const whole = m[1] ?? "0";
47
+ const fracRaw = m[2] ?? "";
48
+ // Reject any significant digit past the scale (lossy). Trailing zeros that
49
+ // exceed the scale are NOT significant, so trim them before the length check.
50
+ const fracTrimmed = fracRaw.replace(/0+$/, "");
51
+ if (fracTrimmed.length > CC_ATOMIC_SCALE) {
52
+ throw new Error(`CC decimal amount ${JSON.stringify(dec)} has more than ${CC_ATOMIC_SCALE} ` +
53
+ `fractional digits — converting to atomic units would lose precision`);
54
+ }
55
+ const frac = fracRaw.slice(0, CC_ATOMIC_SCALE).padEnd(CC_ATOMIC_SCALE, "0");
56
+ const atomic = BigInt(whole) * SCALE_FACTOR + BigInt(frac || "0");
57
+ return atomic.toString();
58
+ }
59
+ /**
60
+ * Convert integer atomic units (decimal string, e.g. `"1"`, `"10000000000"`) to
61
+ * a fixed-scale Daml Decimal CC string with exactly `CC_ATOMIC_SCALE` fractional
62
+ * digits (e.g. `"0.0000000001"`, `"1.0000000000"`).
63
+ *
64
+ * Fail-CLOSED: throws on a non-integer / non-numeric / negative input. The
65
+ * output is the canonical fixed-scale form the Token Standard ledger uses, so
66
+ * `decimalToAtomicCC(atomicToDecimalCC(x)) === x` and
67
+ * `atomicToDecimalCC(decimalToAtomicCC(d))` is `d` normalized to 10 dp.
68
+ */
69
+ export function atomicToDecimalCC(atomic) {
70
+ if (typeof atomic !== "string" || !/^\d+$/.test(atomic.trim())) {
71
+ throw new Error(`invalid CC atomic amount: ${JSON.stringify(atomic)}`);
72
+ }
73
+ const v = BigInt(atomic.trim());
74
+ const whole = v / SCALE_FACTOR;
75
+ const frac = v % SCALE_FACTOR;
76
+ const fracStr = frac.toString().padStart(CC_ATOMIC_SCALE, "0");
77
+ return `${whole.toString()}.${fracStr}`;
78
+ }
79
+ /* ------------------------------------------------------------------------- *
80
+ * Unit-by-scheme boundary (x402-ENVELOPE atomic-by-scheme, BACK-COMPAT).
81
+ *
82
+ * The amount unit on the x402 WIRE is keyed by the x402 scheme string on the
83
+ * SAME object the amount lives on (PaymentRequirements.scheme /
84
+ * PaymentPayload.scheme):
85
+ *
86
+ * - scheme "exact-canton" (LEGACY, the live emitted wire today) => the wire
87
+ * `amount` is a Daml **Decimal** string (e.g. "0.0100000000"), UNCHANGED
88
+ * from deployed behavior. Every deployed v1/cip56/allocation client signs
89
+ * this Decimal verbatim into the ledger and the facilitator compares it.
90
+ * - scheme "exact" (x402-ENVELOPE, post-deploy emit flip) => the wire
91
+ * `amount` is ATOMIC integer units (1 CC = 10^10), and the on-ledger Daml
92
+ * Decimal is derived EXACTLY via the BigInt converters above.
93
+ *
94
+ * The on-ledger Daml `transferLeg.amount` / `TransferCommand.amount` /
95
+ * `Holding.amount` is ALWAYS a fixed-scale Decimal. These two functions are the
96
+ * SINGLE place the scheme->unit decision is made, so every comparison site
97
+ * (facilitator verify arms, selectServerRequirements, client builder,
98
+ * verify-before-sign) converts identically — the off-by-10^10 firewall.
99
+ * ------------------------------------------------------------------------- */
100
+ /** True iff this x402 scheme string carries the amount in ATOMIC integer units
101
+ * on the wire ("exact" — the x402-ENVELOPE convention). The legacy
102
+ * "exact-canton" (and anything else) carries a Daml Decimal. Mirrors
103
+ * `schemeMatches`'s "exact" is canonical / "exact-canton" is legacy split. */
104
+ export function schemeIsAtomic(scheme) {
105
+ return scheme === "exact";
106
+ }
107
+ /**
108
+ * Convert an x402 WIRE amount to the on-ledger Daml **Decimal** it denotes,
109
+ * keyed by the wire's scheme. Atomic scheme ("exact") => `atomicToDecimalCC`;
110
+ * legacy/Decimal scheme ("exact-canton", anything else) => passthrough (the wire
111
+ * value already IS the Decimal). Fail-CLOSED via the underlying converter (a
112
+ * non-integer atomic value, or a malformed Decimal at a later comparison,
113
+ * throws rather than silently mis-comparing). Use this at EVERY site that
114
+ * compares a wire amount against an on-ledger Decimal.
115
+ */
116
+ export function wireAmountToLedgerDecimal(scheme, wireAmount) {
117
+ return schemeIsAtomic(scheme) ? atomicToDecimalCC(wireAmount) : wireAmount;
118
+ }
119
+ /**
120
+ * Inverse of `wireAmountToLedgerDecimal`: given an on-ledger Daml Decimal,
121
+ * produce the WIRE amount for the given scheme. Atomic scheme ("exact") =>
122
+ * `decimalToAtomicCC`; legacy/Decimal scheme => passthrough. Used by the client
123
+ * builder / signer seam when emitting under the atomic scheme.
124
+ */
125
+ export function ledgerDecimalToWireAmount(scheme, decimal) {
126
+ return schemeIsAtomic(scheme) ? decimalToAtomicCC(decimal) : decimal;
127
+ }
128
+ /**
129
+ * Canonical VALUE-equality of two on-ledger Daml CC Decimal strings: compares
130
+ * by BigInt atomic units so "0.1" ≡ "0.1000000000" and a 10x/0.1x difference
131
+ * provably never folds. Fail-CLOSED: a malformed Decimal on either side throws
132
+ * (via `decimalToAtomicCC`), so a comparison can never silently pass on junk.
133
+ * This is the exactness guarantee the spec asks for at every amount-compare site
134
+ * (replaces a raw string `!==`).
135
+ */
136
+ export function ledgerDecimalEquals(a, b) {
137
+ return decimalToAtomicCC(a) === decimalToAtomicCC(b);
138
+ }
139
+ /**
140
+ * Non-throwing, FAIL-CLOSED variant of `ledgerDecimalEquals` for the
141
+ * facilitator amount-validation arms: returns `true` only when both inputs are
142
+ * well-formed Daml CC Decimals of equal value; a malformed/junk value on either
143
+ * side returns `false` (→ amount_mismatch) instead of throwing. Use this at the
144
+ * pure validator sites (validateTransferCommand / validateAllocation /
145
+ * validateCip56*) that must map to a discriminated reject, never a 5xx.
146
+ */
147
+ export function ledgerDecimalsMatch(a, b) {
148
+ try {
149
+ return ledgerDecimalEquals(a, b);
150
+ }
151
+ catch {
152
+ return false;
153
+ }
154
+ }
155
+ //# sourceMappingURL=amount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount.js","sourceRoot":"","sources":["../src/amount.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./caip2.js";
3
3
  export * from "./encoding.js";
4
+ export * from "./amount.js";
5
+ export * from "./resource-url.js";
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./caip2.js";
3
3
  export * from "./encoding.js";
4
+ export * from "./amount.js";
5
+ export * from "./resource-url.js";
4
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** Lowercase-hex unsalted SHA-256 of the exact UTF-8 URL string. */
2
+ export declare function hashResourceUrl(url: string): string;
3
+ /**
4
+ * True when an on-ledger `x402.resourceUrl` stamp binds to the expected URL —
5
+ * accepting BOTH the new hashed form `H(url)` and the legacy plaintext `url`
6
+ * (back-compat with deployed plaintext-stamping clients). Callers only invoke
7
+ * this when a stamp is PRESENT; an absent stamp is tolerated by the caller
8
+ * (the documented residual), exactly as before.
9
+ */
10
+ export declare function resourceUrlMatchesStamp(stamp: string, expectedUrl: string): boolean;
11
+ //# sourceMappingURL=resource-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-url.d.ts","sourceRoot":"","sources":["../src/resource-url.ts"],"names":[],"mappings":"AAsBA,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAEnF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Resource-URL on-ledger privacy stamp (x402-ENVELOPE upstream convention,
3
+ * PR #2634 review point 7).
4
+ *
5
+ * The client binds the payment to the gated resource by stamping the resource
6
+ * URL into the on-ledger Transfer/allocation `meta["x402.resourceUrl"]`, which
7
+ * the facilitator's /verify reproduces and compares (the binding guarantee that
8
+ * stops a transfer for resource A unlocking resource B). Committing the
9
+ * PLAINTEXT URL on-ledger is a privacy concern — it identifies which API the
10
+ * agent is paying for, visible to every stakeholder + in Scan. The convention
11
+ * is therefore to stamp an UNSALTED SHA-256 hash (lowercase hex of the digest of
12
+ * the exact UTF-8 URL string) instead of the plaintext, preserving the binding
13
+ * while hiding the URL.
14
+ *
15
+ * BACK-COMPAT: the facilitator MUST accept EITHER the stamped plaintext URL OR
16
+ * its hash (compare the on-ledger value against both `url` and
17
+ * `hashResourceUrl(url)`), so deployed clients that still stamp plaintext keep
18
+ * verifying while new clients stamp the hash. Absence stays tolerated (existing
19
+ * behavior). See `resourceUrlMatchesStamp`.
20
+ */
21
+ import { createHash } from "node:crypto";
22
+ /** Lowercase-hex unsalted SHA-256 of the exact UTF-8 URL string. */
23
+ export function hashResourceUrl(url) {
24
+ return createHash("sha256").update(url, "utf8").digest("hex");
25
+ }
26
+ /**
27
+ * True when an on-ledger `x402.resourceUrl` stamp binds to the expected URL —
28
+ * accepting BOTH the new hashed form `H(url)` and the legacy plaintext `url`
29
+ * (back-compat with deployed plaintext-stamping clients). Callers only invoke
30
+ * this when a stamp is PRESENT; an absent stamp is tolerated by the caller
31
+ * (the documented residual), exactly as before.
32
+ */
33
+ export function resourceUrlMatchesStamp(stamp, expectedUrl) {
34
+ return stamp === expectedUrl || stamp === hashResourceUrl(expectedUrl);
35
+ }
36
+ //# sourceMappingURL=resource-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-url.js","sourceRoot":"","sources":["../src/resource-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,oEAAoE;AACpE,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa,EAAE,WAAmB;IACxE,OAAO,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,eAAe,CAAC,WAAW,CAAC,CAAC;AACzE,CAAC"}
package/dist/types.d.ts CHANGED
@@ -8,58 +8,162 @@
8
8
  */
9
9
  /** CAIP-2-style Canton network identifier. */
10
10
  export type CantonNetwork = `canton:devnet` | `canton:mainnet` | `canton:${string}`;
11
+ /** x402 scheme discriminator. The x402-ENVELOPE upstream convention (PR #2634
12
+ * review) is that the scheme NAME is `"exact"` and Canton is a NETWORK of the
13
+ * exact scheme (CAIP-2 `canton:*`). The codebase historically used
14
+ * `"exact-canton"`; BOTH are accepted on input for back-compat (deployed
15
+ * facilitator + resource-server middleware string-check `"exact-canton"`).
16
+ * New emitters MAY use `"exact"` once the facilitator accept-both is deployed;
17
+ * see `schemeMatches` for the equivalence used in requirement matching. */
18
+ export type ExactScheme = "exact" | "exact-canton";
19
+ /** True when two scheme strings refer to the same exact scheme — `"exact"` and
20
+ * the legacy `"exact-canton"` are equivalent. Used so a new client emitting
21
+ * `"exact"` still matches a merchant `accepts[]` configured with the legacy
22
+ * `"exact-canton"` (and vice-versa). */
23
+ export declare function schemeMatches(a: string, b: string): boolean;
24
+ /**
25
+ * Extra-block reader helpers (x402-ENVELOPE accept-both). Prefer the new
26
+ * upstream key, fall back to the legacy one. Used everywhere the facilitator /
27
+ * client reads `extra.feePayer`/`facilitatorParty` or
28
+ * `extra.assetTransferMethod`/`transferMethod`, so a request carrying EITHER
29
+ * shape resolves identically.
30
+ */
31
+ export declare function extraFeePayer(extra: {
32
+ feePayer?: string;
33
+ facilitatorParty?: string;
34
+ }): string | undefined;
35
+ export declare function extraMethod(extra: {
36
+ assetTransferMethod?: string;
37
+ transferMethod?: string;
38
+ }): string | undefined;
39
+ /**
40
+ * PaymentPayload payer reader (phdargen / upstream-maintainer naming, accept-both).
41
+ * The x402 payment payload's payer-party field is `payer` — aligning with the
42
+ * `payer` field on `SettleResponse` / `VerifyResponse`. The codebase historically
43
+ * used `payerParty`, and the DEPLOYED v1 MainNet facilitator still reads
44
+ * `payerParty` off the wire, so BOTH keys are accepted on input: prefer the new
45
+ * `payer`, fall back to the legacy `payerParty`. Mirrors `extraFeePayer`. New
46
+ * emitters set BOTH (see `client/src/scheme.ts`); every reader of the wire payer
47
+ * resolves via this helper so a payload carrying EITHER shape resolves identically.
48
+ */
49
+ export declare function payloadPayer(p: {
50
+ payer?: string;
51
+ payerParty?: string;
52
+ }): string | undefined;
53
+ /** True when two `asset` symbols denote the same instrument. The x402-ENVELOPE
54
+ * convention is the symbol `"CC"`; `"canton-coin"` (legacy) is the same thing.
55
+ * The structured `"<admin>::Amulet"` form is also treated as Canton Coin. Any
56
+ * other value matches only by exact string equality (multi-asset tokens). */
57
+ export declare function assetMatches(a: string, b: string): boolean;
11
58
  /** Which on-ledger primitive carries the actual CC movement.
12
- * external-party-amulet-rules (v1): the facilitator submits
13
- * TransferCommand_Send and pays the Global Synchronizer traffic fee.
14
- * cip56-transfer-factory: the payer submits TransferFactory_Transfer and
15
- * pays; the facilitator only verifies. A deploy advertises one via
16
- * CANTON_X402_PRIMARY_METHOD; both are handled at verify/settle. */
17
- export type CantonTransferMethod = "external-party-amulet-rules" | "cip56-transfer-factory";
18
- /** Canton-specific `extra` block in 402 PaymentRequirements. */
59
+ * allocation-direct (CIP-56 DVP "2-tx DIRECT", Design B): the payer LOCKS funds
60
+ * via AllocationFactory_Allocate naming the MERCHANT (payTo) as
61
+ * transferLeg.receiver AND the facilitator as settlement.executor; the
62
+ * facilitator then settles in ONE tx via DirectSettlementConsent_Execute (a
63
+ * standing both-party consent), which runs Allocation_ExecuteTransfer paying the
64
+ * merchant DIRECTLY no escrow contract, no facilitator-owned forward leg, no
65
+ * facilitator custody. See daml/x402-direct +
66
+ * specs/scheme_exact_canton.upstream.md § Design B. */
67
+ export type CantonTransferMethod = "allocation-direct";
68
+ /** Canton-specific `extra` block in 402 PaymentRequirements.
69
+ *
70
+ * x402-ENVELOPE upstream convention renames (PR #2634 review):
71
+ * - `facilitatorParty` → `feePayer` (the party that pays GS traffic)
72
+ * - `transferMethod` → `assetTransferMethod` (allocation value
73
+ * "allocation-api")
74
+ * - `synchronizerId` may be SOURCED FROM /supported (AmuletRules.domain_id)
75
+ * instead of stamped in `extra`.
76
+ *
77
+ * BACK-COMPAT: the OLD keys (`facilitatorParty`, `transferMethod`,
78
+ * `synchronizerId`) stay first-class so every deployed v1/cip56 reader compiles
79
+ * and runs unchanged; the NEW keys (`feePayer`, `assetTransferMethod`) are
80
+ * ADDED as optional siblings. New emitters set BOTH the canonical old field
81
+ * (kept for the deployed facilitator that only reads old) AND the new alias.
82
+ * Read via `extraFeePayer` / `extraMethod` (prefer new, fall back to old). The
83
+ * discriminator stays the `transferMethod` literal so the union narrows. */
19
84
  export type CantonPaymentRequirementsExtra = {
20
- transferMethod: "external-party-amulet-rules";
85
+ /** CIP-56 DVP "2-tx DIRECT" Allocation method (Design B). The payer LOCKS
86
+ * funds via AllocationFactory_Allocate naming the MERCHANT (payTo) as
87
+ * transferLeg.receiver AND the facilitator as settlement.executor; the
88
+ * facilitator then settles in ONE tx via DirectSettlementConsent_Execute,
89
+ * which runs Allocation_ExecuteTransfer paying the merchant DIRECTLY. The
90
+ * `extra` shape is identical to "allocation-api" (same factory + executor +
91
+ * deadlines + instrument); only the discriminator differs, so the client
92
+ * builds the SAME AllocationFactory_Allocate exercise (receiver=merchant)
93
+ * and the facilitator routes to the direct settle path. See daml/x402-direct.
94
+ */
95
+ transferMethod: "allocation-direct";
96
+ /** The facilitator's own party. Also bound as settlement.executor. */
21
97
  facilitatorParty: string;
98
+ /** x402-ENVELOPE fee payer (alias of facilitatorParty). */
99
+ feePayer?: string;
100
+ /** x402-ENVELOPE method discriminator name (= "allocation-direct"). */
101
+ assetTransferMethod?: "allocation-direct";
22
102
  synchronizerId: string;
23
- merchantContractCid?: string;
24
- memo?: string;
25
- } | {
26
- transferMethod: "cip56-transfer-factory";
27
- facilitatorParty: string;
28
- synchronizerId: string;
29
- transferFactoryCid: string;
30
- /** Hash-prefixed templateId of the factory contract, same form
31
- * Canton emits on createdEvent.templateId. Lets the client submit
32
- * TransferFactory_Transfer without its own Scan registry lookup. */
33
- transferFactoryTemplateId: string;
34
103
  instrumentId: {
35
104
  admin: string;
36
105
  id: string;
37
106
  };
107
+ /** The party that will submit DirectSettlementConsent_Execute. For the
108
+ * sponsored flow this EQUALS facilitatorParty and is bound during
109
+ * verification (settlement.executor == facilitatorParty). */
110
+ executor: string;
111
+ /** Relative deadline (seconds from now) the client uses to compute
112
+ * the SettlementInfo.allocateBefore absolute time. */
113
+ allocateBeforeSeconds: number;
114
+ /** Relative deadline (seconds from now) for SettlementInfo.settleBefore.
115
+ * MUST be strictly after allocateBefore. */
116
+ settleBeforeSeconds: number;
38
117
  memo?: string;
39
118
  };
40
- /** Canton-specific `payload` block in PaymentPayload. */
119
+ /** Canton-specific `payload` block in PaymentPayload.
120
+ *
121
+ * PAYER FIELD (phdargen / upstream-maintainer naming, accept-both): the
122
+ * payer-party field is `payer` — aligning with `SettleResponse`/`VerifyResponse`
123
+ * `payer`. The legacy `payerParty` stays accepted on input because the DEPLOYED
124
+ * v1 MainNet facilitator reads `payerParty` off the wire. BOTH are optional here;
125
+ * a well-formed payload carries AT LEAST ONE (validate-body.ts enforces it), and
126
+ * new emitters set BOTH (`payer` primary + `payerParty` legacy). Read via
127
+ * `payloadPayer` (prefer `payer`, fall back to `payerParty`). */
41
128
  export type CantonPaymentPayload = {
42
- transferMethod: "external-party-amulet-rules";
43
- transferCommandCid: string;
44
- payerParty: string;
45
- nonce: number;
129
+ /** CIP-56 DVP "2-tx DIRECT" (Design B). The payer LOCKED funds via
130
+ * AllocationFactory_Allocate with transferLeg.receiver = the MERCHANT
131
+ * (payTo) and settlement.executor = the facilitator; the facilitator then
132
+ * settles in ONE tx via DirectSettlementConsent_Execute (no escrow, no
133
+ * forward, no facilitator custody). See daml/x402-direct. */
134
+ transferMethod: "allocation-direct";
135
+ /** Payer party id (phdargen naming, primary). */
136
+ payer?: string;
137
+ /** Legacy payer party id (deployed-facilitator compat). Read via payloadPayer. */
138
+ payerParty?: string;
139
+ /** Contract id of the live Allocation (the _Completed case — Amulet creates
140
+ * it synchronously). receiver == merchant. This is the proof the facilitator
141
+ * verifies (participant ACS read) and then executes via the consent. */
142
+ allocationCid?: string;
143
+ /** Contract id of a still-pending AllocationInstruction (the _Pending case).
144
+ * Track A is sync-only: the facilitator rejects a pending-only payload with
145
+ * invalid_exact_canton_allocation_pending. */
146
+ allocationInstructionCid?: string;
147
+ /** Optional: the standing DirectSettlementConsent {sender, merchant} cid the
148
+ * facilitator exercises DirectSettlementConsent_Execute on. When ABSENT the
149
+ * facilitator RESOLVES it from its own ACS (the {sender, merchant} pair) and,
150
+ * if none exists, MINTS it from the standing SenderConsent + MerchantConsent
151
+ * (facilitator-alone, once-total onboarding). Supplying it is an O(1)
152
+ * fast-path; the cid moves no funds on its own — the consent's on-ledger
153
+ * choice pins receiver==merchant / sender==consent.sender / executor==
154
+ * facilitator and re-validates every term. */
155
+ directConsentCid?: string;
156
+ /** Optional Ed25519 proof over the prepared-tx hash of the
157
+ * AllocationFactory_Allocate exercise. */
46
158
  signatureProof?: string;
47
- /** updateId of the TransferCommand_Create transaction (relay path only).
48
- * When present, the facilitator records it for traffic-burn attribution
49
- * alongside the Send updateId. Optional: absent for direct-key signers. */
159
+ /** updateId of the AllocationFactory_Allocate (the ALLOCATE/create tx, the
160
+ * FIRST of the two on-ledger DVP txs). allocation-direct is two GS-billed
161
+ * txs the allocate (payer) then DirectSettlementConsent_Execute
162
+ * (facilitator). When present the facilitator records it for traffic-burn
163
+ * attribution ALONGSIDE the settle updateId so attribution covers BOTH txs
164
+ * (exactly like v1's createUpdateId above). Optional: absent for direct-key
165
+ * signers; the relay path surfaces it via the allocate's submission result. */
50
166
  createUpdateId?: string;
51
- } | {
52
- transferMethod: "cip56-transfer-factory";
53
- payerParty: string;
54
- /** Canton ledger updateId of the TransferFactory_Transfer exercise.
55
- * Required when the registry returned
56
- * TransferInstructionResult_Completed (settlement already on-ledger;
57
- * the updateId is the proof). */
58
- updateId?: string;
59
- /** Contract id of a TransferInstruction returned when the result is
60
- * TransferInstructionResult_Pending; /settle waits for it. */
61
- transferInstructionCid?: string;
62
- signatureProof?: string;
63
167
  };
64
168
  /** Resource being paid for. Echoed from the server's 402 PAYMENT-REQUIRED
65
169
  * header into every PaymentPayload per x402 v2. */
@@ -73,7 +177,7 @@ export type FacilitatorRequest = {
73
177
  x402Version: 2;
74
178
  paymentPayload: {
75
179
  x402Version: 2;
76
- scheme: "exact-canton";
180
+ scheme: ExactScheme;
77
181
  network: CantonNetwork;
78
182
  resource: X402ResourceInfo;
79
183
  accepted: PaymentRequirements;
@@ -108,10 +212,14 @@ export type SettleResponse = {
108
212
  export type SupportedResponse = {
109
213
  kinds: Array<{
110
214
  x402Version: 1 | 2;
111
- scheme: "exact-canton";
215
+ scheme: ExactScheme;
112
216
  network: CantonNetwork;
113
217
  extra?: {
114
218
  transferMethods: CantonTransferMethod[];
219
+ /** x402-ENVELOPE: the Global Synchronizer id (AmuletRules.domain_id) the
220
+ * facilitator settles on. Advertised here so a 402 `extra` MAY omit
221
+ * `synchronizerId` and the client sources it from /supported. */
222
+ synchronizerId?: string;
115
223
  };
116
224
  }>;
117
225
  extensions: string[];
@@ -119,7 +227,7 @@ export type SupportedResponse = {
119
227
  };
120
228
  /** PaymentRequirements entry as advertised in an `accepts[]` array. */
121
229
  export type PaymentRequirements = {
122
- scheme: "exact-canton";
230
+ scheme: ExactScheme;
123
231
  network: CantonNetwork;
124
232
  amount: string;
125
233
  asset: string;
@@ -128,7 +236,7 @@ export type PaymentRequirements = {
128
236
  extra: CantonPaymentRequirementsExtra;
129
237
  };
130
238
  /** Canton-specific x402 error codes. Prefix `invalid_exact_canton_*`. */
131
- export type CantonErrorCode = "invalid_exact_canton_transfer_command_not_found" | "invalid_exact_canton_amount_mismatch" | "invalid_exact_canton_asset_mismatch" | "invalid_exact_canton_expired" | "invalid_exact_canton_nonce_reuse" | "invalid_exact_canton_payer_mismatch" | "invalid_exact_canton_merchant_mismatch" | "invalid_exact_canton_signature" | "invalid_exact_canton_resource_url_mismatch" | "invalid_exact_canton_merchant_not_registered" | "invalid_exact_canton_counter_not_ready" | "invalid_exact_canton_transfer_instruction_not_found" | "invalid_exact_canton_transfer_completed_not_visible" | "invalid_exact_canton_transfer_instruction_pending" | "invalid_exact_canton_instrument_id_mismatch" | "invalid_exact_canton_transfer_factory_not_found" | "invalid_exact_canton_missing_proof" | "invalid_exact_canton_holding_locked" | "invalid_exact_canton_payment_already_settled" | "unexpected_canton_ledger_error";
239
+ export type CantonErrorCode = "invalid_exact_canton_transfer_command_not_found" | "invalid_exact_canton_amount_mismatch" | "invalid_exact_canton_asset_mismatch" | "invalid_exact_canton_expired" | "invalid_exact_canton_nonce_reuse" | "invalid_exact_canton_payer_mismatch" | "invalid_exact_canton_merchant_mismatch" | "invalid_exact_canton_signature" | "invalid_exact_canton_merchant_not_registered" | "invalid_exact_canton_counter_not_ready" | "invalid_exact_canton_transfer_instruction_not_found" | "invalid_exact_canton_transfer_completed_not_visible" | "invalid_exact_canton_transfer_instruction_pending" | "invalid_exact_canton_instrument_id_mismatch" | "invalid_exact_canton_transfer_factory_not_found" | "invalid_exact_canton_missing_proof" | "invalid_exact_canton_holding_locked" | "invalid_exact_canton_payment_already_settled" | "invalid_exact_canton_executor_mismatch" | "invalid_exact_canton_allocation_pending" | "invalid_exact_canton_allocation_not_found" | "invalid_exact_canton_allocation_factory_not_found" | "invalid_exact_canton_execute_failed" | "invalid_exact_canton_insufficient_balance" | "invalid_exact_canton_self_payment" | "invalid_exact_canton_direct_disabled" | "unexpected_canton_ledger_error";
132
240
  /**
133
241
  * Pick the server's OWN PaymentRequirements entry that a client claims to
134
242
  * be paying against — defeating client tampering of price / recipient.
@@ -149,9 +257,13 @@ export type CantonErrorCode = "invalid_exact_canton_transfer_command_not_found"
149
257
  * the facilitator with the client's numbers.
150
258
  *
151
259
  * Matching is on the money-critical fields only: scheme, network, amount,
152
- * asset, payTo, and extra.{transferMethod, facilitatorParty,
153
- * synchronizerId, instrumentId}. `maxTimeoutSeconds` / `memo` / discovery
154
- * cids are not part of the price contract.
260
+ * asset, payTo, and extra.{method, feePayer, synchronizerId, instrumentId}.
261
+ * `maxTimeoutSeconds` / `memo` / discovery cids are not part of the price
262
+ * contract. x402-ENVELOPE accept-both (PR #2634): scheme `"exact"`
263
+ * `"exact-canton"`, asset `"CC"` ≡ `"canton-coin"` ≡ `"<admin>::Amulet"`, the
264
+ * method/feePayer fields are read via the new (assetTransferMethod/feePayer) or
265
+ * legacy (transferMethod/facilitatorParty) key, and synchronizerId is only
266
+ * enforced when BOTH sides carry it (it may be sourced from /supported).
155
267
  */
156
268
  export declare function selectServerRequirements(accepts: PaymentRequirements[], clientAccepted: unknown): PaymentRequirements | null;
157
269
  /**
@@ -160,8 +272,9 @@ export declare function selectServerRequirements(accepts: PaymentRequirements[],
160
272
  * configures an `asset` of the structured `<admin>::<id>` form that disagrees
161
273
  * with `extra.instrumentId`, the mismatch is silent and the instrumentId wins.
162
274
  * Catch it at middleware setup instead. `asset` may also be a symbolic value
163
- * such as "canton-coin" (see PaymentRequirements.asset) — only the `::` form is
164
- * cross-checked. Throws on a mismatch; no-op when consistent or not applicable.
275
+ * such as "CC" / "canton-coin" (see PaymentRequirements.asset) — only the `::`
276
+ * form is cross-checked. Throws on a mismatch; no-op when consistent or not
277
+ * applicable.
165
278
  */
166
279
  export declare function assertAssetInstrumentConsistency(req: PaymentRequirements): void;
167
280
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -6,6 +6,54 @@
6
6
  * `extra` (server side) and `payload` (client side); the envelope is
7
7
  * standard x402 v2.
8
8
  */
9
+ import { ledgerDecimalEquals, wireAmountToLedgerDecimal } from "./amount.js";
10
+ /** True when two scheme strings refer to the same exact scheme — `"exact"` and
11
+ * the legacy `"exact-canton"` are equivalent. Used so a new client emitting
12
+ * `"exact"` still matches a merchant `accepts[]` configured with the legacy
13
+ * `"exact-canton"` (and vice-versa). */
14
+ export function schemeMatches(a, b) {
15
+ if (a === b)
16
+ return true;
17
+ const norm = (s) => (s === "exact-canton" ? "exact" : s);
18
+ return norm(a) === norm(b);
19
+ }
20
+ /**
21
+ * Extra-block reader helpers (x402-ENVELOPE accept-both). Prefer the new
22
+ * upstream key, fall back to the legacy one. Used everywhere the facilitator /
23
+ * client reads `extra.feePayer`/`facilitatorParty` or
24
+ * `extra.assetTransferMethod`/`transferMethod`, so a request carrying EITHER
25
+ * shape resolves identically.
26
+ */
27
+ export function extraFeePayer(extra) {
28
+ return extra.feePayer ?? extra.facilitatorParty;
29
+ }
30
+ export function extraMethod(extra) {
31
+ return extra.assetTransferMethod ?? extra.transferMethod;
32
+ }
33
+ /**
34
+ * PaymentPayload payer reader (phdargen / upstream-maintainer naming, accept-both).
35
+ * The x402 payment payload's payer-party field is `payer` — aligning with the
36
+ * `payer` field on `SettleResponse` / `VerifyResponse`. The codebase historically
37
+ * used `payerParty`, and the DEPLOYED v1 MainNet facilitator still reads
38
+ * `payerParty` off the wire, so BOTH keys are accepted on input: prefer the new
39
+ * `payer`, fall back to the legacy `payerParty`. Mirrors `extraFeePayer`. New
40
+ * emitters set BOTH (see `client/src/scheme.ts`); every reader of the wire payer
41
+ * resolves via this helper so a payload carrying EITHER shape resolves identically.
42
+ */
43
+ export function payloadPayer(p) {
44
+ return p.payer ?? p.payerParty;
45
+ }
46
+ /** True when two `asset` symbols denote the same instrument. The x402-ENVELOPE
47
+ * convention is the symbol `"CC"`; `"canton-coin"` (legacy) is the same thing.
48
+ * The structured `"<admin>::Amulet"` form is also treated as Canton Coin. Any
49
+ * other value matches only by exact string equality (multi-asset tokens). */
50
+ export function assetMatches(a, b) {
51
+ if (a === b)
52
+ return true;
53
+ const CC = new Set(["CC", "canton-coin"]);
54
+ const isCC = (s) => CC.has(s) || /::Amulet$/.test(s);
55
+ return isCC(a) && isCC(b);
56
+ }
9
57
  /**
10
58
  * Pick the server's OWN PaymentRequirements entry that a client claims to
11
59
  * be paying against — defeating client tampering of price / recipient.
@@ -26,33 +74,84 @@
26
74
  * the facilitator with the client's numbers.
27
75
  *
28
76
  * Matching is on the money-critical fields only: scheme, network, amount,
29
- * asset, payTo, and extra.{transferMethod, facilitatorParty,
30
- * synchronizerId, instrumentId}. `maxTimeoutSeconds` / `memo` / discovery
31
- * cids are not part of the price contract.
77
+ * asset, payTo, and extra.{method, feePayer, synchronizerId, instrumentId}.
78
+ * `maxTimeoutSeconds` / `memo` / discovery cids are not part of the price
79
+ * contract. x402-ENVELOPE accept-both (PR #2634): scheme `"exact"`
80
+ * `"exact-canton"`, asset `"CC"` ≡ `"canton-coin"` ≡ `"<admin>::Amulet"`, the
81
+ * method/feePayer fields are read via the new (assetTransferMethod/feePayer) or
82
+ * legacy (transferMethod/facilitatorParty) key, and synchronizerId is only
83
+ * enforced when BOTH sides carry it (it may be sourced from /supported).
32
84
  */
33
85
  export function selectServerRequirements(accepts, clientAccepted) {
34
86
  if (typeof clientAccepted !== "object" || clientAccepted === null) {
35
87
  return null;
36
88
  }
37
89
  const c = clientAccepted;
90
+ // x402-ENVELOPE accept-both: read the extra via the alias helpers so a client
91
+ // sending EITHER the new (feePayer/assetTransferMethod) or old
92
+ // (facilitatorParty/transferMethod) shape matches a server `accepts[]`
93
+ // configured in EITHER shape.
38
94
  const cExtra = (c.extra ?? {});
95
+ const cMethod = extraMethod(cExtra);
96
+ const cFeePayer = extraFeePayer(cExtra);
39
97
  for (const r of accepts) {
40
98
  const rExtra = r.extra;
41
- if (r.scheme !== c.scheme)
99
+ // scheme: "exact" ≡ "exact-canton" (a new client vs an old merchant config).
100
+ if (typeof r.scheme !== "string" ||
101
+ typeof c.scheme !== "string" ||
102
+ !schemeMatches(r.scheme, c.scheme))
42
103
  continue;
43
104
  if (r.network !== c.network)
44
105
  continue;
45
- if (r.amount !== c.amount)
46
- continue;
47
- if (r.asset !== c.asset)
106
+ // amount: unit-by-scheme + canonical-decimal compare. The server-configured
107
+ // wire amount `r.amount` and the client-claimed wire amount `c.amount` may
108
+ // legitimately be in DIFFERENT units when scheme `"exact"` (atomic) and the
109
+ // legacy `"exact-canton"` (Decimal) are accept-both equivalent: a raw
110
+ // `r.amount !== c.amount` would then wrongly reject an honest cross-scheme
111
+ // match, OR (worse) let a crafted atomic value string-coincide with a
112
+ // Decimal. Normalize BOTH sides to the on-ledger Daml Decimal keyed by their
113
+ // OWN scheme, then compare by BigInt atomic units (folds "0.1" ≡
114
+ // "0.1000000000", and a 10x/0.1x amount provably cannot match). Fail-closed:
115
+ // a malformed amount throws in the converter rather than passing. This is the
116
+ // matching authority and MUST agree byte-exactly with the facilitator's
117
+ // wireAmountToLedgerDecimal comparisons.
118
+ {
119
+ let rDec;
120
+ let cDec;
121
+ try {
122
+ rDec = wireAmountToLedgerDecimal(r.scheme, r.amount);
123
+ cDec = wireAmountToLedgerDecimal(c.scheme, c.amount);
124
+ }
125
+ catch {
126
+ continue; // malformed amount on either side → no match (fail-closed).
127
+ }
128
+ let amountEq;
129
+ try {
130
+ amountEq = ledgerDecimalEquals(rDec, cDec);
131
+ }
132
+ catch {
133
+ continue;
134
+ }
135
+ if (!amountEq)
136
+ continue;
137
+ }
138
+ // asset: "CC" ≡ "canton-coin" ≡ "<admin>::Amulet" (symbolic equivalence).
139
+ if (typeof r.asset !== "string" ||
140
+ typeof c.asset !== "string" ||
141
+ !assetMatches(r.asset, c.asset))
48
142
  continue;
49
143
  if (r.payTo !== c.payTo)
50
144
  continue;
51
- if (rExtra.transferMethod !== cExtra.transferMethod)
145
+ if (extraMethod(rExtra) !== cMethod)
52
146
  continue;
53
- if (rExtra.facilitatorParty !== cExtra.facilitatorParty)
147
+ if (extraFeePayer(rExtra) !== cFeePayer)
54
148
  continue;
55
- if (rExtra.synchronizerId !== cExtra.synchronizerId)
149
+ // synchronizerId: x402-ENVELOPE allows omitting it from `extra` (sourced from
150
+ // /supported). Mirror the instrumentId pattern — only enforce equality when
151
+ // BOTH sides carry it; if either omits it, do not reject on this field.
152
+ if (rExtra.synchronizerId !== undefined &&
153
+ cExtra.synchronizerId !== undefined &&
154
+ rExtra.synchronizerId !== cExtra.synchronizerId)
56
155
  continue;
57
156
  // instrumentId (CIP-56): if either side carries it, both must match.
58
157
  const rInst = rExtra.instrumentId;
@@ -73,8 +172,9 @@ export function selectServerRequirements(accepts, clientAccepted) {
73
172
  * configures an `asset` of the structured `<admin>::<id>` form that disagrees
74
173
  * with `extra.instrumentId`, the mismatch is silent and the instrumentId wins.
75
174
  * Catch it at middleware setup instead. `asset` may also be a symbolic value
76
- * such as "canton-coin" (see PaymentRequirements.asset) — only the `::` form is
77
- * cross-checked. Throws on a mismatch; no-op when consistent or not applicable.
175
+ * such as "CC" / "canton-coin" (see PaymentRequirements.asset) — only the `::`
176
+ * form is cross-checked. Throws on a mismatch; no-op when consistent or not
177
+ * applicable.
78
178
  */
79
179
  export function assertAssetInstrumentConsistency(req) {
80
180
  const inst = req.extra.instrumentId;
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA4KH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAA8B,EAC9B,cAAuB;IAEvB,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,cAA8C,CAAC;IACzD,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAK3B,CAAC;IACH,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,CAAC,CAAC,KAKhB,CAAC;QACF,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;YAAE,SAAS;QACtC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,SAAS;QACpC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;YAAE,SAAS;QAClC,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;YAAE,SAAS;QAClC,IAAI,MAAM,CAAC,cAAc,KAAK,MAAM,CAAC,cAAc;YAAE,SAAS;QAC9D,IAAI,MAAM,CAAC,gBAAgB,KAAK,MAAM,CAAC,gBAAgB;YAAE,SAAS;QAClE,IAAI,MAAM,CAAC,cAAc,KAAK,MAAM,CAAC,cAAc;YAAE,SAAS;QAC9D,qEAAqE;QACrE,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC;QAClC,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC;QAClC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK;gBAAE,SAAS;YAC/B,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE;gBAAE,SAAS;QACrE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gCAAgC,CAC9C,GAAwB;IAExB,MAAM,IAAI,GACR,GAAG,CAAC,KACL,CAAC,YAAY,CAAC;IACf,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,sCAAsC;IAC7E,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;IAC7C,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,+BAA+B,GAAG,CAAC,KAAK,mBAAmB;YACzD,wBAAwB,QAAQ,sCAAsC;YACtE,qEAAqE;YACrE,0DAA0D,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ftptech/x402-canton-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared types and helpers for the Canton x402 stack",
5
5
  "license": "Apache-2.0",
6
6
  "author": "FTP team",