@daski/x402-scheme 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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/binding.d.ts +37 -0
  4. package/dist/binding.d.ts.map +1 -0
  5. package/dist/binding.js +74 -0
  6. package/dist/binding.js.map +1 -0
  7. package/dist/canonical.d.ts +12 -0
  8. package/dist/canonical.d.ts.map +1 -0
  9. package/dist/canonical.js +53 -0
  10. package/dist/canonical.js.map +1 -0
  11. package/dist/eip712.d.ts +72 -0
  12. package/dist/eip712.d.ts.map +1 -0
  13. package/dist/eip712.js +61 -0
  14. package/dist/eip712.js.map +1 -0
  15. package/dist/errors.d.ts +34 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +32 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/extensions.d.ts +29 -0
  20. package/dist/extensions.d.ts.map +1 -0
  21. package/dist/extensions.js +65 -0
  22. package/dist/extensions.js.map +1 -0
  23. package/dist/index.d.ts +22 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lifecycle.d.ts +177 -0
  28. package/dist/lifecycle.d.ts.map +1 -0
  29. package/dist/lifecycle.js +345 -0
  30. package/dist/lifecycle.js.map +1 -0
  31. package/dist/policy.d.ts +113 -0
  32. package/dist/policy.d.ts.map +1 -0
  33. package/dist/policy.js +357 -0
  34. package/dist/policy.js.map +1 -0
  35. package/dist/recipe.d.ts +40 -0
  36. package/dist/recipe.d.ts.map +1 -0
  37. package/dist/recipe.js +61 -0
  38. package/dist/recipe.js.map +1 -0
  39. package/dist/register.d.ts +36 -0
  40. package/dist/register.d.ts.map +1 -0
  41. package/dist/register.js +12 -0
  42. package/dist/register.js.map +1 -0
  43. package/dist/scheme.d.ts +92 -0
  44. package/dist/scheme.d.ts.map +1 -0
  45. package/dist/scheme.js +182 -0
  46. package/dist/scheme.js.map +1 -0
  47. package/dist/signRequest.d.ts +30 -0
  48. package/dist/signRequest.d.ts.map +1 -0
  49. package/dist/signRequest.js +83 -0
  50. package/dist/signRequest.js.map +1 -0
  51. package/dist/signer.d.ts +38 -0
  52. package/dist/signer.d.ts.map +1 -0
  53. package/dist/signer.js +12 -0
  54. package/dist/signer.js.map +1 -0
  55. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # `@daski/x402-scheme`
2
+
3
+ A composite Exact-EVM client plugin for the modular x402 v2 SDK, and the
4
+ policy validator that decides whether a challenge may be signed at all.
5
+
6
+ ```bash
7
+ npm install @daski/x402-scheme @x402/core @x402/evm
8
+ ```
9
+
10
+ ## It wraps, it does not replace
11
+
12
+ ```ts
13
+ import { x402Client } from "@x402/core/client";
14
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
15
+ import { registerDaskiExactEvmScheme } from "@daski/x402-scheme";
16
+
17
+ const client = new x402Client();
18
+ registerDaskiExactEvmScheme(client, {
19
+ network: "eip155:84532",
20
+ signer, // your SignerAdapter
21
+ payerAddress,
22
+ policy, // PolicyConfig — no config, no signing
23
+ stock: new ExactEvmScheme(account), // wrapped, not discarded
24
+ resolvePurchaseContext, // what you're buying, what you approved
25
+ });
26
+ ```
27
+
28
+ On `PaymentRequired`:
29
+
30
+ - **`extensions["daski-order-binding"]` present** → the Daski path: validate,
31
+ recompute, sign.
32
+ - **absent** → the wrapped stock handler, called with the same requirements and
33
+ context objects, by reference, untouched.
34
+
35
+ The scheme name stays `"exact"`, because that is the only name the facilitator
36
+ knows — inventing a second one would make the payments unverifiable.
37
+ `findDefaultAsset` and `schemeHooks` are forwarded, so registering the
38
+ composite does not quietly disable the host's spend controls.
39
+
40
+ ## The policy validator
41
+
42
+ `PolicyConfig` is required. No config, no signing.
43
+
44
+ Every expectation is config- or catalog-sourced; nothing is read from the
45
+ challenge being validated. Before any purchase authorization is signed:
46
+
47
+ 1. Chain and verifying contract equal the pinned profile values
48
+ 2. `types` is exactly the closed 6-field `TransferWithAuthorization`
49
+ 3. `message.from` is the configured payer
50
+ 4. `message.to` is corroborated by **two** independent catalog sources
51
+ 5. The amount matches the challenge, the approved quote, and the caps
52
+ 6. The validity window is sane, and does not outlive the binding
53
+ 7. The payment identifier is unused
54
+ 8. (Lifecycle) the action URI and request hash are **recomputed**, not accepted
55
+
56
+ Failures raise `PolicyRefusal`, carrying the failed check, a stable code, and
57
+ a remediation. The validator never repairs a payload and proceeds.
58
+
59
+ ## Recipe recomputation
60
+
61
+ ```ts
62
+ import { recipeNonceV2 } from "@daski/x402-scheme";
63
+ ```
64
+
65
+ The authorization nonce is a commitment to the whole deal — chain, token,
66
+ payer, splitter, amount, and the five deal hashes. Recomputing it locally is
67
+ what lets you sign a server-proposed authorization without trusting the
68
+ server. When the gateway supplies `daski-sign-request`, its proposal is
69
+ treated as an input: recomputed, compared, and refused on mismatch.
70
+
71
+ Both `recipeNonce` (v1) and `recipeNonceV2` are implemented and pinned by
72
+ tests against a vector produced independently by the reference client behind
73
+ 43 settled sandbox orders.
74
+
75
+ ## Examples
76
+
77
+ [`examples/fetch`](../../examples/fetch) · [`examples/mcp`](../../examples/mcp)
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The `daski-order-binding` challenge extension: parsed as a closed shape.
3
+ *
4
+ * "Closed" is load-bearing. An extra field in a binding is a field the bridge
5
+ * did not check and did not feed into the recipe recomputation, so a binding
6
+ * that carries one is refused rather than trimmed.
7
+ */
8
+ import type { Hex } from "viem";
9
+ export interface OrderBindingV1 {
10
+ version: 1;
11
+ profile: "recipe-bound-v1";
12
+ listingManifestHash: Hex;
13
+ providerOfferHash: Hex;
14
+ quoteHash: Hex;
15
+ canonicalRequestHash: Hex;
16
+ orderNonce: Hex;
17
+ expiresAt: number;
18
+ }
19
+ /** Catalog-driven checkout: the runtime listing commitment plus the
20
+ * provider's signed registration intent. */
21
+ export interface OrderBindingV2 {
22
+ version: 2;
23
+ profile: "recipe-bound-v2";
24
+ runtimeCommitmentHash: Hex;
25
+ providerIntentHash: Hex;
26
+ quoteHash: Hex;
27
+ canonicalRequestHash: Hex;
28
+ orderNonce: Hex;
29
+ expiresAt: number;
30
+ }
31
+ export type OrderBinding = OrderBindingV1 | OrderBindingV2;
32
+ /**
33
+ * Parses the `daski-order-binding` extension, or returns `undefined` when the
34
+ * challenge carries none (the stock-delegation path).
35
+ */
36
+ export declare function parseOrderBinding(value: unknown): OrderBinding | undefined;
37
+ //# sourceMappingURL=binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAKhC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;IAC3B,mBAAmB,EAAE,GAAG,CAAC;IACzB,iBAAiB,EAAE,GAAG,CAAC;IACvB,SAAS,EAAE,GAAG,CAAC;IACf,oBAAoB,EAAE,GAAG,CAAC;IAC1B,UAAU,EAAE,GAAG,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;6CAC6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qBAAqB,EAAE,GAAG,CAAC;IAC3B,kBAAkB,EAAE,GAAG,CAAC;IACxB,SAAS,EAAE,GAAG,CAAC;IACf,oBAAoB,EAAE,GAAG,CAAC;IAC1B,UAAU,EAAE,GAAG,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,cAAc,CAAC;AAS3D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CA8D1E"}
@@ -0,0 +1,74 @@
1
+ import { refuse } from "./errors.js";
2
+ const HEX32 = /^0x[0-9a-fA-F]{64}$/;
3
+ const SHARED_SLOTS = ["quoteHash", "canonicalRequestHash", "orderNonce"];
4
+ const DEAL_SLOTS_V1 = ["listingManifestHash", "providerOfferHash"];
5
+ const DEAL_SLOTS_V2 = ["runtimeCommitmentHash", "providerIntentHash"];
6
+ const BINDING_DOC = "https://github.com/daski-io/buyer/blob/main/docs/policy.md#daski-order-binding";
7
+ /**
8
+ * Parses the `daski-order-binding` extension, or returns `undefined` when the
9
+ * challenge carries none (the stock-delegation path).
10
+ */
11
+ export function parseOrderBinding(value) {
12
+ if (value === undefined)
13
+ return undefined;
14
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
15
+ refuse({
16
+ check: "challenge-shape",
17
+ code: "DASKI_BINDING_NOT_AN_OBJECT",
18
+ expected: "an object under extensions['daski-order-binding']",
19
+ actual: Array.isArray(value) ? "an array" : typeof value,
20
+ remediation: `Request a fresh challenge; see ${BINDING_DOC}`,
21
+ });
22
+ }
23
+ const binding = value;
24
+ const isV2 = binding.profile === "recipe-bound-v2";
25
+ const dealSlots = isV2 ? DEAL_SLOTS_V2 : DEAL_SLOTS_V1;
26
+ const expectedKeys = [
27
+ "version", "profile", ...dealSlots, ...SHARED_SLOTS, "expiresAt",
28
+ ].sort();
29
+ const actualKeys = Object.keys(binding).sort();
30
+ if (actualKeys.join(",") !== expectedKeys.join(",")) {
31
+ refuse({
32
+ check: "challenge-shape",
33
+ code: "DASKI_BINDING_OPEN_SHAPE",
34
+ expected: `exactly [${expectedKeys.join(", ")}]`,
35
+ actual: `[${actualKeys.join(", ")}]`,
36
+ remediation: "An unrecognized binding layout cannot be recomputed, so it cannot be " +
37
+ `signed. Upgrade @daski/x402-scheme, or see ${BINDING_DOC}`,
38
+ });
39
+ }
40
+ const versionOk = isV2
41
+ ? binding.version === 2
42
+ : binding.version === 1 && binding.profile === "recipe-bound-v1";
43
+ if (!versionOk) {
44
+ refuse({
45
+ check: "challenge-shape",
46
+ code: "DASKI_BINDING_UNKNOWN_PROFILE",
47
+ expected: "recipe-bound-v1 (version 1) or recipe-bound-v2 (version 2)",
48
+ actual: `profile=${String(binding.profile)} version=${String(binding.version)}`,
49
+ remediation: `Upgrade @daski/x402-scheme; see ${BINDING_DOC}`,
50
+ });
51
+ }
52
+ for (const slot of [...dealSlots, ...SHARED_SLOTS]) {
53
+ if (!HEX32.test(String(binding[slot]))) {
54
+ refuse({
55
+ check: "challenge-shape",
56
+ code: "DASKI_BINDING_MALFORMED_SLOT",
57
+ expected: `${slot} to be a 32-byte hex string`,
58
+ actual: `${slot}=${String(binding[slot])}`,
59
+ remediation: `Request a fresh challenge; see ${BINDING_DOC}`,
60
+ });
61
+ }
62
+ }
63
+ if (!Number.isSafeInteger(binding.expiresAt) || Number(binding.expiresAt) <= 0) {
64
+ refuse({
65
+ check: "challenge-shape",
66
+ code: "DASKI_BINDING_MALFORMED_EXPIRY",
67
+ expected: "expiresAt to be a positive unix timestamp in seconds",
68
+ actual: `expiresAt=${String(binding.expiresAt)}`,
69
+ remediation: `Request a fresh challenge; see ${BINDING_DOC}`,
70
+ });
71
+ }
72
+ return binding;
73
+ }
74
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,KAAK,GAAG,qBAAqB,CAAC;AA4BpC,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,sBAAsB,EAAE,YAAY,CAAU,CAAC;AAClF,MAAM,aAAa,GAAG,CAAC,qBAAqB,EAAE,mBAAmB,CAAU,CAAC;AAC5E,MAAM,aAAa,GAAG,CAAC,uBAAuB,EAAE,oBAAoB,CAAU,CAAC;AAE/E,MAAM,WAAW,GACf,gFAAgF,CAAC;AAEnF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC;YACL,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,6BAA6B;YACnC,QAAQ,EAAE,mDAAmD;YAC7D,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,KAAK;YACxD,WAAW,EAAE,kCAAkC,WAAW,EAAE;SAC7D,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,KAAK,iBAAiB,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;IACvD,MAAM,YAAY,GAAG;QACnB,SAAS,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,GAAG,YAAY,EAAE,WAAW;KACjE,CAAC,IAAI,EAAE,CAAC;IACT,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC;YACL,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,YAAY,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAChD,MAAM,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACpC,WAAW,EACT,uEAAuE;gBACvE,8CAA8C,WAAW,EAAE;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,MAAM,SAAS,GAAG,IAAI;QACpB,CAAC,CAAC,OAAO,CAAC,OAAO,KAAK,CAAC;QACvB,CAAC,CAAC,OAAO,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,KAAK,iBAAiB,CAAC;IACnE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,CAAC;YACL,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,+BAA+B;YACrC,QAAQ,EAAE,4DAA4D;YACtE,MAAM,EAAE,WAAW,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC/E,WAAW,EAAE,mCAAmC,WAAW,EAAE;SAC9D,CAAC,CAAC;IACL,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC;gBACL,KAAK,EAAE,iBAAiB;gBACxB,IAAI,EAAE,8BAA8B;gBACpC,QAAQ,EAAE,GAAG,IAAI,6BAA6B;gBAC9C,MAAM,EAAE,GAAG,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE;gBAC1C,WAAW,EAAE,kCAAkC,WAAW,EAAE;aAC7D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/E,MAAM,CAAC;YACL,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,gCAAgC;YACtC,QAAQ,EAAE,sDAAsD;YAChE,MAAM,EAAE,aAAa,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YAChD,WAAW,EAAE,kCAAkC,WAAW,EAAE;SAC7D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAkC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Canonical JSON, byte-identical to the gateway's.
3
+ *
4
+ * Object keys sort, numbers must be finite, lone surrogates are rejected, and
5
+ * `undefined` is an error rather than an omission. The bridge uses this to
6
+ * recompute the `requestHash` in a lifecycle challenge instead of trusting
7
+ * the one it was handed.
8
+ */
9
+ import { type Hex } from "viem";
10
+ export declare const canonicalJson: (value: unknown) => string;
11
+ export declare const canonicalHash: (value: unknown) => Hex;
12
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA0B,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAsCxD,eAAO,MAAM,aAAa,UAAW,OAAO,KAAG,MAA+B,CAAC;AAC/E,eAAO,MAAM,aAAa,UAAW,OAAO,KAAG,GAAoD,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Canonical JSON, byte-identical to the gateway's.
3
+ *
4
+ * Object keys sort, numbers must be finite, lone surrogates are rejected, and
5
+ * `undefined` is an error rather than an omission. The bridge uses this to
6
+ * recompute the `requestHash` in a lifecycle challenge instead of trusting
7
+ * the one it was handed.
8
+ */
9
+ import { keccak256, stringToHex } from "viem";
10
+ function assertValidUnicode(value) {
11
+ for (let index = 0; index < value.length; index += 1) {
12
+ const code = value.charCodeAt(index);
13
+ if (code >= 0xd800 && code <= 0xdbff) {
14
+ const next = value.charCodeAt(index + 1);
15
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
16
+ throw new Error("Canonical JSON contains invalid Unicode");
17
+ }
18
+ index += 1;
19
+ }
20
+ else if (code >= 0xdc00 && code <= 0xdfff) {
21
+ throw new Error("Canonical JSON contains invalid Unicode");
22
+ }
23
+ }
24
+ }
25
+ function canonicalValue(value) {
26
+ if (value === null)
27
+ return "null";
28
+ if (typeof value === "string") {
29
+ assertValidUnicode(value);
30
+ return JSON.stringify(value);
31
+ }
32
+ if (typeof value === "boolean")
33
+ return JSON.stringify(value);
34
+ if (typeof value === "number") {
35
+ if (!Number.isFinite(value))
36
+ throw new Error("Canonical JSON accepts only finite numbers");
37
+ return JSON.stringify(value);
38
+ }
39
+ if (Array.isArray(value))
40
+ return `[${value.map(canonicalValue).join(",")}]`;
41
+ if (!value || typeof value !== "object")
42
+ throw new Error("Unsupported canonical value");
43
+ const object = value;
44
+ return `{${Object.keys(object).sort().map((key) => {
45
+ assertValidUnicode(key);
46
+ if (object[key] === undefined)
47
+ throw new Error("Undefined canonical value");
48
+ return `${JSON.stringify(key)}:${canonicalValue(object[key])}`;
49
+ }).join(",")}}`;
50
+ }
51
+ export const canonicalJson = (value) => canonicalValue(value);
52
+ export const canonicalHash = (value) => keccak256(stringToHex(canonicalValue(value)));
53
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAY,MAAM,MAAM,CAAC;AAExD,SAAS,kBAAkB,CAAC,KAAa;IACvC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAC7D,CAAC;YACD,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC3F,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC5E,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAChD,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC5E,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IACjE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAO,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The one typed-data shape this package will ever sign for a purchase.
3
+ *
4
+ * `TransferWithAuthorization` is declared here as a closed literal and
5
+ * compared field-for-field against whatever the server proposes. The bridge
6
+ * never signs a server-supplied `types` object: it signs *this* one, after
7
+ * confirming the server asked for exactly this one.
8
+ */
9
+ import type { Address, Hex } from "viem";
10
+ export declare const TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE: "TransferWithAuthorization";
11
+ /** The closed 6-field EIP-3009 type set. Order is part of the hash. */
12
+ export declare const TRANSFER_WITH_AUTHORIZATION_TYPES: {
13
+ readonly TransferWithAuthorization: readonly [{
14
+ readonly name: "from";
15
+ readonly type: "address";
16
+ }, {
17
+ readonly name: "to";
18
+ readonly type: "address";
19
+ }, {
20
+ readonly name: "value";
21
+ readonly type: "uint256";
22
+ }, {
23
+ readonly name: "validAfter";
24
+ readonly type: "uint256";
25
+ }, {
26
+ readonly name: "validBefore";
27
+ readonly type: "uint256";
28
+ }, {
29
+ readonly name: "nonce";
30
+ readonly type: "bytes32";
31
+ }];
32
+ };
33
+ /** The EIP-3009 authorization as it travels on the wire: decimal strings. */
34
+ export interface TransferAuthorization {
35
+ from: Address;
36
+ to: Address;
37
+ value: string;
38
+ validAfter: string;
39
+ validBefore: string;
40
+ nonce: Hex;
41
+ }
42
+ export interface Eip712Domain {
43
+ name: string;
44
+ version: string;
45
+ chainId: number;
46
+ verifyingContract: Address;
47
+ }
48
+ /** A complete typed-data request, in the shape a signer adapter receives. */
49
+ export interface TypedDataRequest {
50
+ domain: Eip712Domain;
51
+ types: Record<string, readonly {
52
+ name: string;
53
+ type: string;
54
+ }[]>;
55
+ primaryType: string;
56
+ message: Record<string, unknown>;
57
+ }
58
+ /**
59
+ * Builds the exact typed-data payload for an authorization. `value`,
60
+ * `validAfter` and `validBefore` become bigints here because that is what
61
+ * EIP-712 encoding requires; the wire form keeps them as strings.
62
+ */
63
+ export declare function transferWithAuthorizationTypedData(domain: Eip712Domain, authorization: TransferAuthorization): TypedDataRequest;
64
+ /**
65
+ * True when `types` is exactly the closed 6-field set: same type names, same
66
+ * field names, same field types, same order, and nothing extra at either
67
+ * level. Used by §4.1 check 2.
68
+ */
69
+ export declare function isClosedTransferWithAuthorizationTypes(types: unknown): boolean;
70
+ /** The exact field set an EIP-3009 authorization message may carry. */
71
+ export declare const AUTHORIZATION_FIELDS: readonly ["from", "to", "value", "validAfter", "validBefore", "nonce"];
72
+ //# sourceMappingURL=eip712.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712.d.ts","sourceRoot":"","sources":["../src/eip712.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEzC,eAAO,MAAM,wCAAwC,EAAG,2BAAoC,CAAC;AAE7F,uEAAuE;AACvE,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;CASpC,CAAC;AAEX,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,YAAY,EACpB,aAAa,EAAE,qBAAqB,GACnC,gBAAgB,CAclB;AAED;;;;GAIG;AACH,wBAAgB,sCAAsC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAe9E;AAED,uEAAuE;AACvE,eAAO,MAAM,oBAAoB,wEAEvB,CAAC"}
package/dist/eip712.js ADDED
@@ -0,0 +1,61 @@
1
+ export const TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE = "TransferWithAuthorization";
2
+ /** The closed 6-field EIP-3009 type set. Order is part of the hash. */
3
+ export const TRANSFER_WITH_AUTHORIZATION_TYPES = {
4
+ TransferWithAuthorization: [
5
+ { name: "from", type: "address" },
6
+ { name: "to", type: "address" },
7
+ { name: "value", type: "uint256" },
8
+ { name: "validAfter", type: "uint256" },
9
+ { name: "validBefore", type: "uint256" },
10
+ { name: "nonce", type: "bytes32" },
11
+ ],
12
+ };
13
+ /**
14
+ * Builds the exact typed-data payload for an authorization. `value`,
15
+ * `validAfter` and `validBefore` become bigints here because that is what
16
+ * EIP-712 encoding requires; the wire form keeps them as strings.
17
+ */
18
+ export function transferWithAuthorizationTypedData(domain, authorization) {
19
+ return {
20
+ domain,
21
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
22
+ primaryType: TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE,
23
+ message: {
24
+ from: authorization.from,
25
+ to: authorization.to,
26
+ value: BigInt(authorization.value),
27
+ validAfter: BigInt(authorization.validAfter),
28
+ validBefore: BigInt(authorization.validBefore),
29
+ nonce: authorization.nonce,
30
+ },
31
+ };
32
+ }
33
+ /**
34
+ * True when `types` is exactly the closed 6-field set: same type names, same
35
+ * field names, same field types, same order, and nothing extra at either
36
+ * level. Used by §4.1 check 2.
37
+ */
38
+ export function isClosedTransferWithAuthorizationTypes(types) {
39
+ if (!types || typeof types !== "object" || Array.isArray(types))
40
+ return false;
41
+ const record = types;
42
+ // EIP712Domain is derived from the domain, never accepted as a declared type.
43
+ if (Object.keys(record).join(",") !== TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE)
44
+ return false;
45
+ const fields = record[TRANSFER_WITH_AUTHORIZATION_PRIMARY_TYPE];
46
+ const expected = TRANSFER_WITH_AUTHORIZATION_TYPES.TransferWithAuthorization;
47
+ if (!Array.isArray(fields) || fields.length !== expected.length)
48
+ return false;
49
+ return expected.every((want, index) => {
50
+ const got = fields[index];
51
+ return !!got && typeof got === "object" && !Array.isArray(got) &&
52
+ Object.keys(got).sort().join(",") === "name,type" &&
53
+ got.name === want.name &&
54
+ got.type === want.type;
55
+ });
56
+ }
57
+ /** The exact field set an EIP-3009 authorization message may carry. */
58
+ export const AUTHORIZATION_FIELDS = [
59
+ "from", "to", "value", "validAfter", "validBefore", "nonce",
60
+ ];
61
+ //# sourceMappingURL=eip712.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712.js","sourceRoot":"","sources":["../src/eip712.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,wCAAwC,GAAG,2BAAoC,CAAC;AAE7F,uEAAuE;AACvE,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,yBAAyB,EAAE;QACzB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;QACjC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;QAC/B,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;QAClC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE;QACvC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE;QACxC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;KACnC;CACO,CAAC;AA2BX;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,MAAoB,EACpB,aAAoC;IAEpC,OAAO;QACL,MAAM;QACN,KAAK,EAAE,iCAAiC;QACxC,WAAW,EAAE,wCAAwC;QACrD,OAAO,EAAE;YACP,IAAI,EAAE,aAAa,CAAC,IAAI;YACxB,EAAE,EAAE,aAAa,CAAC,EAAE;YACpB,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC;YAC5C,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC;YAC9C,KAAK,EAAE,aAAa,CAAC,KAAK;SAC3B;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sCAAsC,CAAC,KAAc;IACnE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,8EAA8E;IAC9E,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,wCAAwC;QAAE,OAAO,KAAK,CAAC;IAC7F,MAAM,MAAM,GAAG,MAAM,CAAC,wCAAwC,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,iCAAiC,CAAC,yBAAyB,CAAC;IAC7E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9E,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACpC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAO,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,GAAa,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,WAAW;YAC1D,GAAyB,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAC5C,GAAyB,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO;CACnD,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Every refusal this package raises is structured: a stable machine code, the
3
+ * §4 check that failed, and the remediation a human can act on. Nothing here
4
+ * ever carries key material — see `redact.ts` for the outbound guard.
5
+ */
6
+ /** The §4.1 checks, in spec order, plus the lifecycle and recompute tiers. */
7
+ export type PolicyCheck = "chain-pinning" | "typed-data-shape" | "payer-match" | "splitter-allowlist" | "amount-and-caps" | "authorization-window" | "payment-identifier" | "lifecycle-binding" | "recipe-recompute" | "challenge-shape";
8
+ export interface PolicyRefusalDetail {
9
+ /** Which §4.1 assertion refused. */
10
+ check: PolicyCheck;
11
+ /** Stable machine-readable code, e.g. `DASKI_POLICY_SPLITTER_NOT_ALLOWLISTED`. */
12
+ code: string;
13
+ /** What the bridge required. */
14
+ expected?: string;
15
+ /** What the server proposed. Never key material. */
16
+ actual?: string;
17
+ /** The exact command or doc URL that resolves this. */
18
+ remediation: string;
19
+ }
20
+ /**
21
+ * A refusal to sign. The bridge never "fixes and proceeds": a failed check
22
+ * ends the attempt, and the caller is told which check and what to do.
23
+ */
24
+ export declare class PolicyRefusal extends Error {
25
+ readonly name = "PolicyRefusal";
26
+ readonly detail: PolicyRefusalDetail;
27
+ constructor(detail: PolicyRefusalDetail);
28
+ toJSON(): PolicyRefusalDetail & {
29
+ error: "policy_refusal";
30
+ message: string;
31
+ };
32
+ }
33
+ export declare function refuse(detail: PolicyRefusalDetail): never;
34
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,kBAAkB,GAClB,aAAa,GACb,oBAAoB,GACpB,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,GAClB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,KAAK,EAAE,WAAW,CAAC;IACnB,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAkB,IAAI,mBAAmB;IACzC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;gBAEzB,MAAM,EAAE,mBAAmB;IAKvC,MAAM,IAAI,mBAAmB,GAAG;QAAE,KAAK,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAG7E;AASD,wBAAgB,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,KAAK,CAEzD"}
package/dist/errors.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Every refusal this package raises is structured: a stable machine code, the
3
+ * §4 check that failed, and the remediation a human can act on. Nothing here
4
+ * ever carries key material — see `redact.ts` for the outbound guard.
5
+ */
6
+ /**
7
+ * A refusal to sign. The bridge never "fixes and proceeds": a failed check
8
+ * ends the attempt, and the caller is told which check and what to do.
9
+ */
10
+ export class PolicyRefusal extends Error {
11
+ name = "PolicyRefusal";
12
+ detail;
13
+ constructor(detail) {
14
+ super(`${detail.code}: ${refusalSentence(detail)}`);
15
+ this.detail = detail;
16
+ }
17
+ toJSON() {
18
+ return { error: "policy_refusal", message: this.message, ...this.detail };
19
+ }
20
+ }
21
+ function refusalSentence(detail) {
22
+ const parts = [`the ${detail.check} check refused this challenge`];
23
+ if (detail.expected !== undefined)
24
+ parts.push(`expected ${detail.expected}`);
25
+ if (detail.actual !== undefined)
26
+ parts.push(`server proposed ${detail.actual}`);
27
+ return parts.join("; ");
28
+ }
29
+ export function refuse(detail) {
30
+ throw new PolicyRefusal(detail);
31
+ }
32
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4BH;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IACpB,IAAI,GAAG,eAAe,CAAC;IAChC,MAAM,CAAsB;IAErC,YAAY,MAA2B;QACrC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,KAAK,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,MAAM;QACJ,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5E,CAAC;CACF;AAED,SAAS,eAAe,CAAC,MAA2B;IAClD,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,+BAA+B,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAChF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,MAA2B;IAChD,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,29 @@
1
+ export declare const DASKI_ORDER_BINDING = "daski-order-binding";
2
+ export declare const DASKI_SIGN_REQUEST = "daski-sign-request";
3
+ export declare const PAYMENT_IDENTIFIER = "payment-identifier";
4
+ /** Alphanumeric, hyphen and underscore, 16–128 characters. */
5
+ export declare const PAYMENT_IDENTIFIER_PATTERN: RegExp;
6
+ /**
7
+ * The extension set a paid submission echoes: every issued extension except
8
+ * `bazaar` and `daski-sign-request`.
9
+ *
10
+ * `bazaar` is dropped because its inlined outcome schemas can push the encoded
11
+ * `PAYMENT-SIGNATURE` header past the inbound caps in front of the gateway
12
+ * (Node's 16 KiB default, Cloudflare's per-header limit), which would refuse
13
+ * the paid retry with a 431 before the gateway ever saw it. The gateway
14
+ * verifies a payment that omits `bazaar` and hash-checks it when echoed.
15
+ *
16
+ * `daski-sign-request` is dropped because it is the server's *proposal*, not
17
+ * part of the deal: echoing it back would assert we agreed to a document we
18
+ * only used as an input to our own recomputation.
19
+ */
20
+ export declare function paymentEchoExtensions(issued: Record<string, unknown> | undefined): Record<string, unknown>;
21
+ /**
22
+ * Adds our payment identifier to the issued `payment-identifier` extension.
23
+ * The identifier is the reconciliation key: it is what makes an interrupted
24
+ * purchase provably findable instead of ambiguously re-signable.
25
+ */
26
+ export declare function withPaymentIdentifier(extensions: Record<string, unknown>, identifier: string | undefined): Record<string, unknown>;
27
+ /** Reads the identifier the server pinned, if it pinned one. */
28
+ export declare function issuedPaymentIdentifier(extensions: Record<string, unknown> | undefined): string | undefined;
29
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AACzD,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AACvD,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B,QAA4B,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED,gEAAgE;AAChE,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC9C,MAAM,GAAG,SAAS,CAKpB"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Challenge extensions: what we echo back with a paid submission, and what we
3
+ * refuse to echo.
4
+ */
5
+ import { refuse } from "./errors.js";
6
+ export const DASKI_ORDER_BINDING = "daski-order-binding";
7
+ export const DASKI_SIGN_REQUEST = "daski-sign-request";
8
+ export const PAYMENT_IDENTIFIER = "payment-identifier";
9
+ /** Alphanumeric, hyphen and underscore, 16–128 characters. */
10
+ export const PAYMENT_IDENTIFIER_PATTERN = /^[a-zA-Z0-9_-]{16,128}$/;
11
+ /**
12
+ * The extension set a paid submission echoes: every issued extension except
13
+ * `bazaar` and `daski-sign-request`.
14
+ *
15
+ * `bazaar` is dropped because its inlined outcome schemas can push the encoded
16
+ * `PAYMENT-SIGNATURE` header past the inbound caps in front of the gateway
17
+ * (Node's 16 KiB default, Cloudflare's per-header limit), which would refuse
18
+ * the paid retry with a 431 before the gateway ever saw it. The gateway
19
+ * verifies a payment that omits `bazaar` and hash-checks it when echoed.
20
+ *
21
+ * `daski-sign-request` is dropped because it is the server's *proposal*, not
22
+ * part of the deal: echoing it back would assert we agreed to a document we
23
+ * only used as an input to our own recomputation.
24
+ */
25
+ export function paymentEchoExtensions(issued) {
26
+ const { bazaar: _discovery, [DASKI_SIGN_REQUEST]: _proposal, ...extensions } = issued ?? {};
27
+ return extensions;
28
+ }
29
+ /**
30
+ * Adds our payment identifier to the issued `payment-identifier` extension.
31
+ * The identifier is the reconciliation key: it is what makes an interrupted
32
+ * purchase provably findable instead of ambiguously re-signable.
33
+ */
34
+ export function withPaymentIdentifier(extensions, identifier) {
35
+ if (identifier === undefined)
36
+ return extensions;
37
+ if (!PAYMENT_IDENTIFIER_PATTERN.test(identifier)) {
38
+ refuse({
39
+ check: "payment-identifier",
40
+ code: "DASKI_PAYMENT_IDENTIFIER_MALFORMED",
41
+ expected: "16-128 characters of [A-Za-z0-9_-]",
42
+ actual: `identifier of length ${identifier.length}`,
43
+ remediation: "Let @daski/pay generate the identifier, or supply one matching the " +
44
+ "documented pattern.",
45
+ });
46
+ }
47
+ const issued = extensions[PAYMENT_IDENTIFIER];
48
+ if (issued === undefined)
49
+ return extensions;
50
+ const declaration = issued;
51
+ return {
52
+ ...extensions,
53
+ [PAYMENT_IDENTIFIER]: {
54
+ ...declaration,
55
+ info: { ...(declaration.info ?? {}), id: identifier },
56
+ },
57
+ };
58
+ }
59
+ /** Reads the identifier the server pinned, if it pinned one. */
60
+ export function issuedPaymentIdentifier(extensions) {
61
+ const issued = extensions?.[PAYMENT_IDENTIFIER];
62
+ const id = issued?.info?.id;
63
+ return typeof id === "string" ? id : undefined;
64
+ }
65
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AACzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAA2C;IAE3C,MAAM,EACJ,MAAM,EAAE,UAAU,EAClB,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAC/B,GAAG,UAAU,EACd,GAAG,MAAM,IAAI,EAAE,CAAC;IACjB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAmC,EACnC,UAA8B;IAE9B,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAChD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,MAAM,CAAC;YACL,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,oCAAoC;YAC1C,QAAQ,EAAE,oCAAoC;YAC9C,MAAM,EAAE,wBAAwB,UAAU,CAAC,MAAM,EAAE;YACnD,WAAW,EACT,qEAAqE;gBACrE,qBAAqB;SACxB,CAAC,CAAC;IACL,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC5C,MAAM,WAAW,GAAG,MAA4C,CAAC;IACjE,OAAO;QACL,GAAG,UAAU;QACb,CAAC,kBAAkB,CAAC,EAAE;YACpB,GAAG,WAAW;YACd,IAAI,EAAE,EAAE,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE;SACtD;KACF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CACrC,UAA+C;IAE/C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC,kBAAkB,CACL,CAAC;IAC1C,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;IAC5B,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC"}