@metalntwx/metal-pay 0.2.0 → 0.3.1

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 (75) hide show
  1. package/README.md +104 -38
  2. package/dist/clearing-abi.d.ts +1836 -0
  3. package/dist/clearing-abi.js +2361 -0
  4. package/dist/clearing-client.d.ts +96 -159
  5. package/dist/clearing-client.js +412 -553
  6. package/dist/clearing-codec.d.ts +57 -0
  7. package/dist/clearing-codec.js +173 -0
  8. package/dist/clearing-custody.d.ts +202 -0
  9. package/dist/clearing-custody.js +552 -0
  10. package/dist/clearing-server.d.ts +30 -49
  11. package/dist/clearing-server.js +87 -67
  12. package/dist/clearing-x402.d.ts +38 -0
  13. package/dist/clearing-x402.js +249 -0
  14. package/dist/client.d.ts +14 -15
  15. package/dist/client.js +53 -22
  16. package/dist/index.d.ts +4 -1
  17. package/dist/index.js +4 -1
  18. package/dist/mandates/account.d.ts +9 -4
  19. package/dist/mandates/account.js +15 -11
  20. package/dist/mandates/binding.d.ts +4 -27
  21. package/dist/mandates/binding.js +5 -9
  22. package/dist/mandates/checkout.js +5 -5
  23. package/dist/mandates/client.js +4 -3
  24. package/dist/mandates/l3a.d.ts +2 -2
  25. package/dist/mandates/l3a.js +5 -4
  26. package/dist/mandates/seller.d.ts +1 -1
  27. package/dist/mandates/seller.js +4 -4
  28. package/dist/mandates/verify.d.ts +4 -6
  29. package/dist/mandates/verify.js +147 -82
  30. package/dist/mandates/vi/credential.js +4 -4
  31. package/dist/networks.d.ts +9 -1
  32. package/dist/networks.js +105 -4
  33. package/dist/node.d.ts +11 -0
  34. package/dist/node.js +97 -0
  35. package/package.json +18 -11
  36. package/src/clearing-abi.ts +2361 -0
  37. package/src/clearing-client.test.ts +850 -0
  38. package/src/clearing-client.ts +612 -0
  39. package/src/clearing-codec.test.ts +57 -0
  40. package/src/clearing-codec.ts +251 -0
  41. package/src/clearing-custody.test.ts +763 -0
  42. package/src/clearing-custody.ts +743 -0
  43. package/src/clearing-fixture.json +37 -0
  44. package/src/clearing-server.test.ts +450 -0
  45. package/src/clearing-server.ts +123 -0
  46. package/src/clearing-x402.test.ts +181 -0
  47. package/src/clearing-x402.ts +328 -0
  48. package/src/client.test.ts +333 -0
  49. package/src/client.ts +195 -0
  50. package/src/index.ts +33 -0
  51. package/src/mandates/account.ts +95 -0
  52. package/src/mandates/binding.ts +280 -0
  53. package/src/mandates/chain.ts +68 -0
  54. package/src/mandates/checkout.ts +368 -0
  55. package/src/mandates/client.ts +159 -0
  56. package/src/mandates/extension.ts +48 -0
  57. package/src/mandates/index.ts +121 -0
  58. package/src/mandates/ingest.ts +42 -0
  59. package/src/mandates/jcs.ts +16 -0
  60. package/src/mandates/l3a.ts +216 -0
  61. package/src/mandates/seller.ts +91 -0
  62. package/src/mandates/verify.ts +625 -0
  63. package/src/mandates/vi/credential.ts +477 -0
  64. package/src/mandates/vi/issue.ts +118 -0
  65. package/src/mandates/vi/sd-jwt.ts +189 -0
  66. package/src/networks.test.ts +177 -0
  67. package/src/networks.ts +242 -0
  68. package/src/node.test.ts +24 -0
  69. package/src/node.ts +96 -0
  70. package/src/server.test.ts +223 -0
  71. package/src/server.ts +77 -0
  72. package/dist/clearing.d.ts +0 -204
  73. package/dist/clearing.js +0 -151
  74. package/dist/file-storage.d.ts +0 -39
  75. package/dist/file-storage.js +0 -195
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export { METAL_NETWORKS, USDZ, formatUsdz, metalNetwork, usdzBaseUnits, accountFactoryFor, rpcUrlFor, } from "./networks.js";
2
2
  export { DEFAULT_MAX_PAYMENT, createMetalPaymentClient, registerMetalNetworks, } from "./client.js";
3
3
  export { metalUsd, registerMetalScheme, sellerToken, } from "./server.js";
4
- export * from "./clearing.js";
4
+ export * from "./clearing-codec.js";
5
+ export { clearingAbi } from "./clearing-abi.js";
5
6
  export * from "./clearing-client.js";
7
+ export * from "./clearing-x402.js";
6
8
  export * from "./clearing-server.js";
9
+ export * from "./clearing-custody.js";
@@ -70,6 +70,9 @@ export declare const metalAccountAbi: readonly [{
70
70
  }, {
71
71
  readonly type: "uint256";
72
72
  readonly name: "nonce";
73
+ }, {
74
+ readonly type: "uint64";
75
+ readonly name: "validUntil";
73
76
  }, {
74
77
  readonly type: "bytes";
75
78
  readonly name: "signature";
@@ -84,7 +87,9 @@ export declare function assertMetalAccount(client: PublicClient, account: Addres
84
87
  /** The gas an executeSigned adds to the call it carries, by the quorum signature's signers. */
85
88
  export declare function quorumGas(signature: Hex): bigint;
86
89
  /** Self-calls approved by the account's existing ERC-7913 quorum, in one batch
87
- * that runs whole or not at all. The signing callback may collect mixed keys,
88
- * weights and passkeys; it returns the normal abi.encode(bytes[] signers,
89
- * bytes[] signatures) account signature. */
90
- export declare function authorizeMandateCall(client: PublicClient, factory: Address, call: MandateCall | readonly MandateCall[], signQuorum: (digest: Hex) => Promise<Hex>): Promise<MandateCall>;
90
+ * that runs whole or not at all. The account refuses the signed batch once the
91
+ * chain's block timestamp passes `validUntil` (unix seconds): pick a deadline
92
+ * just long enough to collect the signatures and submit. The signing callback
93
+ * may collect mixed keys, weights and passkeys; it returns the normal
94
+ * abi.encode(bytes[] signers, bytes[] signatures) account signature. */
95
+ export declare function authorizeMandateCall(client: PublicClient, factory: Address, call: MandateCall | readonly MandateCall[], validUntil: bigint, signQuorum: (digest: Hex) => Promise<Hex>): Promise<MandateCall>;
@@ -6,7 +6,7 @@ export const metalAccountFactoryAbi = parseAbi([
6
6
  ]);
7
7
  export const metalAccountAbi = parseAbi([
8
8
  "function nonces(address owner, uint192 key) view returns (uint256)",
9
- "function executeSigned(bytes32 mode, bytes executionData, uint256 nonce, bytes signature)",
9
+ "function executeSigned(bytes32 mode, bytes executionData, uint256 nonce, uint64 validUntil, bytes signature)",
10
10
  ]);
11
11
  export const ACCOUNT_BATCH_MODE = "0x0100000000000000000000000000000000000000000000000000000000000000";
12
12
  /** Exact immutable ERC-1167 clone emitted by MetalAccountFactory. */
@@ -56,10 +56,12 @@ export function quorumGas(signature) {
56
56
  return gas;
57
57
  }
58
58
  /** Self-calls approved by the account's existing ERC-7913 quorum, in one batch
59
- * that runs whole or not at all. The signing callback may collect mixed keys,
60
- * weights and passkeys; it returns the normal abi.encode(bytes[] signers,
61
- * bytes[] signatures) account signature. */
62
- export async function authorizeMandateCall(client, factory, call, signQuorum) {
59
+ * that runs whole or not at all. The account refuses the signed batch once the
60
+ * chain's block timestamp passes `validUntil` (unix seconds): pick a deadline
61
+ * just long enough to collect the signatures and submit. The signing callback
62
+ * may collect mixed keys, weights and passkeys; it returns the normal
63
+ * abi.encode(bytes[] signers, bytes[] signatures) account signature. */
64
+ export async function authorizeMandateCall(client, factory, call, validUntil, signQuorum) {
63
65
  const chainId = client.chain?.id;
64
66
  if (chainId === undefined)
65
67
  throw new Error("the RPC client names no chain");
@@ -71,9 +73,11 @@ export async function authorizeMandateCall(client, factory, call, signQuorum) {
71
73
  if (calls.some((c) => !isAddressEqual(c.to, account)))
72
74
  throw new Error("every call in one batch goes to the same account");
73
75
  await assertMetalAccount(client, account, factory);
74
- // A fresh 192-bit nonce lane per call: two administration calls in flight,
75
- // or one beside the owner's own operations on lane 0, never claim one nonce.
76
- const lane = hexToBigInt(bytesToHex(crypto.getRandomValues(new Uint8Array(24))));
76
+ // A fresh nonce lane per call: two administration calls in flight, or one
77
+ // beside the owner's own operations on lane 0, never claim one nonce. A
78
+ // quorum's lane keeps the key's top bit clear; set, it is a lane for
79
+ // signatures below the threshold, which the account refuses for the quorum.
80
+ const lane = hexToBigInt(bytesToHex(crypto.getRandomValues(new Uint8Array(24)))) >> 1n;
77
81
  const sequence = await client.readContract({ address: account, abi: metalAccountAbi, functionName: "nonces", args: [account, lane] });
78
82
  const nonce = (lane << 64n) | sequence;
79
83
  const executionData = encodeAbiParameters(parseAbiParameters("(address target, uint256 value, bytes callData)[]"), [calls.map((c) => ({ target: account, value: 0n, callData: c.data }))]);
@@ -81,11 +85,11 @@ export async function authorizeMandateCall(client, factory, call, signQuorum) {
81
85
  // it could have the quorum sign a batch of its own choosing.
82
86
  const digest = hashTypedData({
83
87
  domain: { name: "MetalAccount", version: "1", chainId, verifyingContract: account },
84
- types: { Execute: [{ name: "mode", type: "bytes32" }, { name: "executionData", type: "bytes" }, { name: "nonce", type: "uint256" }] },
88
+ types: { Execute: [{ name: "mode", type: "bytes32" }, { name: "executionData", type: "bytes" }, { name: "nonce", type: "uint256" }, { name: "validUntil", type: "uint64" }] },
85
89
  primaryType: "Execute",
86
- message: { mode: ACCOUNT_BATCH_MODE, executionData, nonce },
90
+ message: { mode: ACCOUNT_BATCH_MODE, executionData, nonce, validUntil },
87
91
  });
88
92
  const signature = await signQuorum(digest);
89
93
  const gas = calls.reduce((sum, c) => sum + c.gas, 0n) + quorumGas(signature);
90
- return { ...first, gas, data: encodeFunctionData({ abi: metalAccountAbi, functionName: "executeSigned", args: [ACCOUNT_BATCH_MODE, executionData, nonce, signature] }) };
94
+ return { ...first, gas, data: encodeFunctionData({ abi: metalAccountAbi, functionName: "executeSigned", args: [ACCOUNT_BATCH_MODE, executionData, nonce, validUntil, signature] }) };
91
95
  }
@@ -2,14 +2,6 @@ import { type Address, type Hex } from "viem";
2
2
  /** Domain tag for the payment-only ERC-1271 path on MetalAccount. */
3
3
  export declare const MANDATE_SIGNATURE_TAG: Hex;
4
4
  export declare const mandateBindingAbi: readonly [{
5
- readonly name: "mip20TransferHandler";
6
- readonly type: "function";
7
- readonly stateMutability: "pure";
8
- readonly inputs: readonly [];
9
- readonly outputs: readonly [{
10
- readonly type: "bytes4";
11
- }];
12
- }, {
13
5
  readonly name: "mandateReference";
14
6
  readonly type: "function";
15
7
  readonly stateMutability: "pure";
@@ -402,7 +394,10 @@ export declare const mandateBindingAbi: readonly [{
402
394
  }];
403
395
  readonly name: "l";
404
396
  }];
405
- readonly outputs: readonly [];
397
+ readonly outputs: readonly [{
398
+ readonly type: "bool";
399
+ readonly name: "paid";
400
+ }];
406
401
  }, {
407
402
  readonly name: "MandateBound";
408
403
  readonly type: "event";
@@ -503,9 +498,6 @@ export declare const mandateBindingAbi: readonly [{
503
498
  }, {
504
499
  readonly type: "uint256";
505
500
  readonly name: "occurrences";
506
- }, {
507
- readonly type: "bool";
508
- readonly name: "direct";
509
501
  }];
510
502
  }, {
511
503
  readonly name: "MandateRefused";
@@ -619,21 +611,6 @@ export declare const mandateBindingAbi: readonly [{
619
611
  readonly name: "RefusalLimitReached";
620
612
  readonly type: "error";
621
613
  readonly inputs: readonly [];
622
- }, {
623
- readonly name: "NotApproved";
624
- readonly type: "error";
625
- readonly inputs: readonly [];
626
- }, {
627
- readonly name: "MandateBreach";
628
- readonly type: "error";
629
- readonly inputs: readonly [{
630
- readonly type: "uint8";
631
- readonly name: "reason";
632
- }];
633
- }, {
634
- readonly name: "UnauthorizedHookCaller";
635
- readonly type: "error";
636
- readonly inputs: readonly [];
637
614
  }];
638
615
  /** `MandateTypes.Reason` in declaration order, which is the order Mandate Binding checks them in; `None` is a pass. */
639
616
  export declare const REASONS: readonly ["None", "NoMandate", "Revoked", "Expired", "BadSignature", "NonceUsed", "OutsideWindow", "PayeeNotAllowed", "OverPerPaymentCap", "OverBudget", "OutsideRecurrence", "OverOccurrences"];
@@ -1,4 +1,4 @@
1
- // MetalAccount mandate ABI, typed agent keys, references, self-calls and direct pulls.
1
+ // MetalAccount mandate ABI, typed agent keys, references, self-calls and pulls.
2
2
  import { concat, encodeFunctionData, encodePacked, keccak256, parseAbi, size, slice } from "viem";
3
3
  /** Domain tag for the payment-only ERC-1271 path on MetalAccount. */
4
4
  export const MANDATE_SIGNATURE_TAG = "0x4d4e4454";
@@ -10,7 +10,6 @@ export const mandateBindingAbi = parseAbi([
10
10
  "struct Terms { string kid; string instrument; string currency; uint256 unitsPerMinor; Payee[] payees; bytes32 openSdHash; }",
11
11
  "struct PayeeTerms { bytes32 sdHash; string object; }",
12
12
  "struct MandateState { address principal; bool revoked; bytes32 agentX; bytes32 agentY; string kid; bytes32 openSdHash; uint64 boundBlock; uint64 occurrences; uint128 spent; }",
13
- "function mip20TransferHandler() pure returns (bytes4)",
14
13
  "function mandateReference(address principal, bytes agentKey, bytes32 hashC) pure returns (bytes32)",
15
14
  "function mandates(bytes32 ref) view returns (MandateState)",
16
15
  "function payee(bytes32 ref, address account) view returns (PayeeTerms)",
@@ -21,9 +20,9 @@ export const mandateBindingAbi = parseAbi([
21
20
  "function revoke(bytes32 ref)",
22
21
  "function isValidSignature(bytes32 hash, bytes blob) view returns (bytes4)",
23
22
  "function check(Pull p, L3a l) view returns (uint8)",
24
- "function pull(Pull p, L3a l)",
23
+ "function pull(Pull p, L3a l) returns (bool paid)",
25
24
  "event MandateBound(bytes32 indexed ref, bytes32 hashC, bytes32 issuerKey, bytes agentKey, Constraints constraints, Terms terms)",
26
- "event MandatePayment(bytes32 indexed ref, address indexed to, bytes20 l3aDigest, bytes agentKey, uint256 amount, uint256 spent, uint256 occurrences, bool direct)",
25
+ "event MandatePayment(bytes32 indexed ref, address indexed to, bytes20 l3aDigest, bytes agentKey, uint256 amount, uint256 spent, uint256 occurrences)",
27
26
  "event MandateRefused(bytes32 indexed ref, address indexed to, bytes20 l3aDigest, bytes agentKey, uint256 amount, uint8 reason)",
28
27
  "event MandateRevoked(bytes32 indexed ref)",
29
28
  "event MandateReplaced(bytes32 indexed predecessor, bytes32 indexed successor)",
@@ -44,9 +43,6 @@ export const mandateBindingAbi = parseAbi([
44
43
  "error NonceAlreadyUsed()",
45
44
  "error NotYetValid()",
46
45
  "error RefusalLimitReached()",
47
- "error NotApproved()",
48
- "error MandateBreach(uint8 reason)",
49
- "error UnauthorizedHookCaller()",
50
46
  ]);
51
47
  /** `MandateTypes.Reason` in declaration order, which is the order Mandate Binding checks them in; `None` is a pass. */
52
48
  export const REASONS = [
@@ -74,13 +70,13 @@ export const REFUSALS = {
74
70
  NoMandate: { reason: "mandate_not_bound", meaning: "no such mandate is bound on the account" },
75
71
  Revoked: { reason: "mandate_revoked", meaning: "the mandate is revoked, or replaced: the agent pays under the credential that replaced it" },
76
72
  Expired: { reason: "mandate_expired", meaning: "the mandate has expired" },
77
- BadSignature: { reason: "mandate_bad_signature", meaning: "the payment is not signed by the mandate's agent key" },
73
+ BadSignature: { reason: "mandate_bad_signature", meaning: "the payment is not signed by the mandate key" },
78
74
  NonceUsed: { reason: "mandate_nonce_used", meaning: "this payment was already made or refused" },
79
75
  OutsideWindow: { reason: "mandate_outside_window", meaning: "the payment's validity window is not open" },
80
76
  PayeeNotAllowed: { reason: "mandate_payee_not_listed", meaning: "the payee is not one the mandate allows" },
81
77
  OverPerPaymentCap: { reason: "mandate_cap_exceeded", meaning: "the amount is above the mandate's per-payment cap" },
82
78
  OverBudget: { reason: "mandate_budget_spent", meaning: "the amount would take the mandate past its budget" },
83
- OutsideRecurrence: { reason: "mandate_outside_recurrence", meaning: "today is outside the mandate's dates" },
79
+ OutsideRecurrence: { reason: "mandate_outside_recurrence", meaning: "the mandate has not started yet, or its dates have ended" },
84
80
  OverOccurrences: { reason: "mandate_occurrences_used", meaning: "the mandate's payment count is used up" },
85
81
  };
86
82
  /** `mandate_cap_exceeded (the amount is above the mandate's per-payment cap)`: a refusal as a person reads it. */
@@ -61,7 +61,7 @@ export function readCheckoutJwt(jwt) {
61
61
  const [h, p, sig] = segments;
62
62
  const header = JSON.parse(Buffer.from(fromB64url(h)).toString("utf8"));
63
63
  if (header.alg !== "ES256" || header.typ !== "JWT" || typeof header.kid !== "string")
64
- throw new Error("a checkout JWT is an ES256 JWT naming the agent key");
64
+ throw new Error("a checkout JWT is an ES256 JWT naming the mandate key");
65
65
  const offer = JSON.parse(Buffer.from(fromB64url(p)).toString("utf8"));
66
66
  if (offer === null || typeof offer !== "object" || Array.isArray(offer))
67
67
  throw new Error("a checkout JWT's payload is an object");
@@ -126,7 +126,7 @@ export function readL3b(serialized) {
126
126
  const payload = JSON.parse(Buffer.from(fromB64url(p)).toString("utf8"));
127
127
  const [, final] = JSON.parse(Buffer.from(fromB64url(checkoutDisclosure)).toString("utf8"));
128
128
  if (typeof header.kid !== "string")
129
- throw new Error("an L3b names the agent key");
129
+ throw new Error("an L3b names the mandate key");
130
130
  if (final.vct !== CHECKOUT_VCT || typeof final.checkout_jwt !== "string")
131
131
  throw new Error(`the disclosure is not a ${CHECKOUT_VCT} with a checkout JWT`);
132
132
  const signature = fromB64url(sig);
@@ -183,9 +183,9 @@ function checkCheckoutMandate(serialized, expect) {
183
183
  if (l3b.typ !== "kb-sd-jwt")
184
184
  problems.push("the checkout mandate's typ is not kb-sd-jwt");
185
185
  if (l3b.kid !== expect.kid)
186
- problems.push("the checkout mandate names another agent key");
186
+ problems.push("the checkout mandate names another mandate key");
187
187
  if (!verifyL3aSignature(expect.agent, sha256Hex(Buffer.from(l3b.signingInput, "ascii")), l3b.r, l3b.s))
188
- problems.push("the checkout mandate is not signed by the mandate's agent key");
188
+ problems.push("the checkout mandate is not signed by the mandate key");
189
189
  if (typeof l3b.aud !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(l3b.aud) || !isAddressEqual(l3b.aud, expect.payee))
190
190
  problems.push("the checkout mandate is for another payee");
191
191
  const now = expect.now ?? Math.floor(Date.now() / 1000);
@@ -215,7 +215,7 @@ function checkCheckoutMandate(serialized, expect) {
215
215
  let lineItemId = expect.lineItemId;
216
216
  if (jwt !== undefined) {
217
217
  if (jwt.kid !== expect.kid || !verifyL3aSignature(expect.agent, sha256Hex(Buffer.from(jwt.signingInput, "ascii")), jwt.r, jwt.s))
218
- problems.push("the checkout JWT is not signed by the mandate's agent key");
218
+ problems.push("the checkout JWT is not signed by the mandate key");
219
219
  if (expect.offer !== undefined && jwt.signingInput !== buildCheckoutJwt(expect.kid, expect.offer).signingInput)
220
220
  problems.push("the checkout JWT is not over the offer the payee served");
221
221
  const offer = expect.offer ?? jwt.offer;
@@ -5,9 +5,10 @@
5
5
  // `TransferWithAuthorization` whose `from` is the principal's account, whose
6
6
  // nonce is `bytes12(mandate reference) ‖ bytes20(SHA-256(L3a signing input))`,
7
7
  // whose window is the L3a's `iat` and `exp`, and whose signature is
8
- // `abi.encode(Pull, L3a fields)`, which the account's Mandate Binding accepts
9
- // or refuses through ERC-1271. Sellers and facilitators see an ordinary
10
- // `exact` payment; a seller that asks for the agent's signed order gets it in
8
+ // `MNDT || abi.encode(Pull, L3a fields)`, the arguments of `pull` on the
9
+ // principal's account. Sellers see an ordinary `exact` payment; Metal's
10
+ // facilitator submits it as that `pull`, where Mandate Binding accepts or
11
+ // refuses it. A seller that asks for the agent's signed order gets it in
11
12
  // the payload's `extensions`, and the per-payment mandate names its hash.
12
13
  //
13
14
  // `@x402/evm`'s `exact` client draws its own nonce and signs EIP-712 as
@@ -64,9 +64,9 @@ export declare function signL3a(args: {
64
64
  iss: string;
65
65
  signDigest: SignDigest;
66
66
  }): Promise<SignedL3a>;
67
- /** The relayed authorisation's signature: `MNDT || abi.encode(Pull, L3a fields)`. */
67
+ /** The x402 payload's signature: `MNDT || abi.encode(Pull, L3a fields)`, the arguments of `pull`. */
68
68
  export declare function encodeMandateBlob(pull: Pull, fields: L3aFields): Hex;
69
- /** The Pull and L3a fields a relayed authorisation's signature carries, as Mandate Binding's `check` and `pull` take them. */
69
+ /** The Pull and L3a fields an x402 payload's signature carries, as Mandate Binding's `check` and `pull` take them. */
70
70
  export declare function decodeMandateBlob(blob: Hex): {
71
71
  pull: Pull;
72
72
  fields: L3aFields;
@@ -4,8 +4,9 @@
4
4
  // verbatim), with `aud` the chain as `eip155:<chainId>`, `iat`/`exp` the
5
5
  // ERC-3009 window, and `transaction_id` the SHA-256 of the RFC 8785 form of
6
6
  // the seller's x402 payment requirements. The ERC-3009 nonce is
7
- // `bytes12(mandate reference) ‖ bytes20(SHA-256(signing input))`; the relayed
8
- // signature is `MNDT || abi.encode(Pull, L3a fields)`.
7
+ // `bytes12(mandate reference) ‖ bytes20(SHA-256(signing input))`; the x402
8
+ // payload's signature is `MNDT || abi.encode(Pull, L3a fields)`, which the
9
+ // facilitator decodes into `pull`'s arguments.
9
10
  import { p256 } from "@noble/curves/nist";
10
11
  import { bytesToHex, concat, decodeAbiParameters, encodeAbiParameters, getAbiItem, hexToBigInt, hexToBytes, isAddressEqual, numberToHex, size, slice, zeroAddress, } from "viem";
11
12
  import { mandateBindingAbi, referencePrefix } from "./binding.js";
@@ -145,11 +146,11 @@ export async function signL3a(args) {
145
146
  return { pull, fields: { ...unsigned, r, s }, serialized: serializeL3a(l3a, r, s), digest: l3a.digest };
146
147
  }
147
148
  const pullInputs = getAbiItem({ abi: mandateBindingAbi, name: "pull" }).inputs;
148
- /** The relayed authorisation's signature: `MNDT || abi.encode(Pull, L3a fields)`. */
149
+ /** The x402 payload's signature: `MNDT || abi.encode(Pull, L3a fields)`, the arguments of `pull`. */
149
150
  export function encodeMandateBlob(pull, fields) {
150
151
  return concat([MANDATE_SIGNATURE_TAG, encodeAbiParameters(pullInputs, [pull, fields])]);
151
152
  }
152
- /** The Pull and L3a fields a relayed authorisation's signature carries, as Mandate Binding's `check` and `pull` take them. */
153
+ /** The Pull and L3a fields an x402 payload's signature carries, as Mandate Binding's `check` and `pull` take them. */
153
154
  export function decodeMandateBlob(blob) {
154
155
  if (size(blob) < 4 || slice(blob, 0, 4) !== MANDATE_SIGNATURE_TAG)
155
156
  throw new Error("not a MetalAccount mandate signature");
@@ -17,7 +17,7 @@ export type RequireSignedOfferConfig = {
17
17
  * On a route whose offer asks for it (`extra.signedOffer: "required"`), a
18
18
  * payment under a mandate must carry the agent's signed order, checked before
19
19
  * the facilitator is asked: the payer's account binds the mandate the payment
20
- * names, and the signed order is that mandate's agent key over the offer the
20
+ * names, and the signed order is that mandate's key over the offer the
21
21
  * server served, for this payee, buying its one line item (`extra.resource`
22
22
  * when the route names one, else the payee). A refusal is a 402 whose reason
23
23
  * names the check that failed, a payment that names no mandate included. A
@@ -1,8 +1,8 @@
1
1
  // The seller's side of a signed order: a check, before the facilitator is
2
2
  // asked to verify, that a payment under a mandate carries the agent's signed
3
3
  // offer for this very offer, under the agent key the payer's account binds.
4
- import { createPublicClient, getAddress, http, isAddress, isHex, isAddressEqual } from "viem";
5
- import { accountFactoryFor, metalNetwork } from "../networks.js";
4
+ import { createPublicClient, getAddress, isAddress, isHex, isAddressEqual } from "viem";
5
+ import { accountFactoryFor, metalNetwork, rpcTransport } from "../networks.js";
6
6
  import { assertMetalAccount } from "./account.js";
7
7
  import { referencePrefix } from "./binding.js";
8
8
  import { decodeMandateBlob } from "./l3a.js";
@@ -13,14 +13,14 @@ import { asksSignedOffer, mandateRefOf, signedOfferOf } from "./extension.js";
13
13
  * On a route whose offer asks for it (`extra.signedOffer: "required"`), a
14
14
  * payment under a mandate must carry the agent's signed order, checked before
15
15
  * the facilitator is asked: the payer's account binds the mandate the payment
16
- * names, and the signed order is that mandate's agent key over the offer the
16
+ * names, and the signed order is that mandate's key over the offer the
17
17
  * server served, for this payee, buying its one line item (`extra.resource`
18
18
  * when the route names one, else the payee). A refusal is a 402 whose reason
19
19
  * names the check that failed, a payment that names no mandate included. A
20
20
  * route that does not ask is left to the facilitator.
21
21
  */
22
22
  export function requireSignedOffer(server, config) {
23
- const publicClient = "publicClient" in config ? config.publicClient : createPublicClient({ transport: http(config.rpcUrl) });
23
+ const publicClient = "publicClient" in config ? config.publicClient : createPublicClient({ transport: rpcTransport(config.rpcUrl) });
24
24
  const refuse = (reason) => ({ abort: true, reason });
25
25
  // x402 treats a hook that throws as one that passed, so a check that cannot
26
26
  // finish refuses the payment instead. The refusal reaches the payer, so it
@@ -14,10 +14,10 @@ export interface VerifyStep {
14
14
  detail: string;
15
15
  }
16
16
  export declare const STEP_TITLES: {
17
- readonly 1: "The mandate is bound on the principal's account: hash(C), the limits, the payees, the agent key and the issuer key are on chain";
18
- readonly 2: "The credential in hand hashes to the bound hash(C) and states the bound limits, payees and agent key";
17
+ readonly 1: "The mandate is bound on the principal's account: hash(C), the limits, the payees, the mandate key and the issuer key are on chain";
18
+ readonly 2: "The credential in hand hashes to the bound hash(C) and states the bound limits, payees and mandate key";
19
19
  readonly 3: "The credential's signatures: the issuer's under a trusted key that is the one recorded at binding, the owner's under the key the issuer bound";
20
- readonly 4: "Each payment and refusal is the agent key's per-payment mandate, rebuilt from its transaction, inside the mandate at its block";
20
+ readonly 4: "Each payment and refusal is the mandate key's per-payment mandate, rebuilt from its transaction, inside the mandate at its block";
21
21
  };
22
22
  /** A mandate as its `MandateBound` event records it. */
23
23
  export interface BoundMandate {
@@ -40,11 +40,9 @@ export interface VerifiedPayment {
40
40
  kind: "payment" | "refusal";
41
41
  txHash: Hash;
42
42
  block: bigint;
43
- /** Whether `pull` submitted it, with no seller; otherwise a relayer submitted the ERC-3009 authorisation to the token. */
44
- direct: boolean;
45
43
  to: Address;
46
44
  amount: bigint;
47
- /** The ERC-3009 nonce the token recorded; a seller's `/settlements` row carries it for a relayed payment. */
45
+ /** The ERC-3009 nonce the token recorded; a seller's `/settlements` row carries it for a payment the facilitator settled. */
48
46
  nonce?: Hex;
49
47
  reason?: ReasonName;
50
48
  /** The L3a SD-JWT, as the VI reference serialises it, when the transaction carried one. */