@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
@@ -1,168 +1,105 @@
1
- import type { PaymentPayloadResult, PaymentRequirements, SchemeClientHooks, SchemeNetworkClient } from "@x402/core/types";
2
- import { type Address, type Hex, type LocalAccount } from "viem";
3
- /** What signs: a viem local account, or anything with an address and
4
- * EIP-712 `signTypedData`. */
1
+ import { type Hex } from "viem";
2
+ import { type ClearingInvoice, type ClearingReceipt } from "./clearing-codec.js";
5
3
  export interface ClearingSigner {
6
- readonly address: Address;
7
- signTypedData(args: {
8
- domain: Record<string, unknown>;
9
- types: Record<string, unknown>;
10
- primaryType: string;
11
- message: Record<string, unknown>;
12
- }): Promise<Hex>;
4
+ publicKey: Hex;
5
+ sign(namespace: string, message: Hex): Hex | Promise<Hex>;
13
6
  }
14
- export interface ClearingDebitSchemeOptions {
15
- /** The facilitator this payer trusts to report its position: Metal's,
16
- * `https://<facilitator host>/x402/facilitator`. */
17
- facilitator: string;
18
- /** Where the highest total signed each epoch is remembered. Default: in
19
- * memory, which does not survive a restart. */
20
- storage?: ClearingPositionStorage;
21
- fetch?: typeof globalThis.fetch;
22
- /** Clock, in milliseconds. Default: wall time. */
23
- now?: () => number;
7
+ export interface ClearingEntry {
8
+ recipient: Hex;
9
+ cumulative: string;
10
+ count: string;
24
11
  }
25
- /** The payer's position as stored: the epoch, the next debit index and the
26
- * running total it last signed. Decimal strings, so any store can hold it. */
27
- export interface StoredClearingPosition {
28
- epoch: string;
29
- /** The next debit index, after the debit signed last. */
30
- index: string;
31
- /** The running total of the debit signed last. */
32
- cumulativeDebit: string;
33
- /** The highest running total ever signed in this epoch. It never goes
34
- * down, so a stale or low report cannot weaken the check made against it. */
35
- highestSigned: string;
12
+ export interface ClearingPayment {
13
+ invoice: ClearingInvoice;
14
+ body: Hex;
15
+ payerSignature: Hex;
16
+ invoiceSignature: Hex;
17
+ entries: ClearingEntry[];
36
18
  }
37
- /** Where positions are kept: one record per network, Clearing contract,
38
- * payer and epoch. */
39
- export interface ClearingPositionStorage {
40
- get(key: string): Promise<StoredClearingPosition | undefined>;
41
- /** Store the position. An implementation MUST keep the larger of the
42
- * stored and the new `highestSigned`, atomically with the write: two
43
- * clients sharing a store sign in between each other's reads and writes,
44
- * and the mark must never move down. `keepHighest` does the merge. */
45
- set(key: string, position: StoredClearingPosition): Promise<void>;
19
+ export interface ClearingPaid {
20
+ invoice: ClearingInvoice;
21
+ receipt: ClearingReceipt;
22
+ invoiceSignature: Hex;
23
+ entries: ClearingEntry[];
24
+ previous?: {
25
+ receipt: ClearingReceipt;
26
+ entries: ClearingEntry[];
27
+ };
46
28
  }
47
- /** `next`, with `highestSigned` no lower than the one already stored. */
48
- export declare function keepHighest(existing: StoredClearingPosition | undefined, next: StoredClearingPosition): StoredClearingPosition;
49
- /** The default store: positions do not survive a process restart. */
50
- export declare class InMemoryClearingPositionStorage implements ClearingPositionStorage {
51
- private readonly positions;
52
- get(key: string): Promise<StoredClearingPosition | undefined>;
53
- set(key: string, position: StoredClearingPosition): Promise<void>;
29
+ export interface ClearingClientState {
30
+ epoch?: string | undefined;
31
+ entries: ClearingEntry[];
32
+ sequence: string;
33
+ /** The payment sent whose outcome is unknown, with the URL it pays for
34
+ * when `pay` was given one. */
35
+ pending?: (ClearingPayment & {
36
+ url?: string | undefined;
37
+ }) | undefined;
38
+ payments: Record<string, ClearingPaid>;
54
39
  }
55
- export declare class ClearingDebitScheme implements SchemeNetworkClient {
56
- private readonly signer;
57
- readonly scheme = "clearing-debit";
58
- readonly schemeHooks: SchemeClientHooks;
59
- private readonly facilitator;
60
- private readonly storage;
61
- private readonly doFetch;
62
- private readonly now;
63
- /** Per payer and contract: the facilitator's last answer, adjusted for
64
- * what this client has signed since. */
65
- private readonly positions;
66
- /** Per payer and contract: until when to pay the standard way. */
67
- private readonly shortUntil;
68
- /** Sellers the facilitator settles no clearing for, by network, contract
69
- * and payee: a seller without Metal's seller token. That holds for as long
70
- * as the client runs, so they are paid the standard way from then on. */
71
- private readonly standardOnly;
72
- /** Per payer and contract: the Debits whose outcome is in doubt, each to
73
- * be presented again for its own offer. Only an answer about that Debit
74
- * clears it. */
75
- private readonly unresolved;
76
- constructor(signer: ClearingSigner, options: ClearingDebitSchemeOptions);
77
- /** The selector to give `new x402Client(...)`: the clearing offer, unless
78
- * custody was lately found short or the seller cannot take clearing, in
79
- * which case the standard one. A Debit whose outcome is in doubt is always
80
- * presented again by clearing. */
81
- readonly prefer: (_x402Version: number, accepts: PaymentRequirements[]) => PaymentRequirements;
82
- /** One payment at a time per payer and contract: reading the position,
83
- * signing and recording it must not interleave, or two payments would sign
84
- * the same index. */
85
- private readonly queues;
86
- private exclusive;
87
- private key;
88
- private sellerOf;
89
- /** The fetch to give `wrapFetchWithPayment`. A request carrying a Debit
90
- * that fails without an answer, or ends in a server error that says
91
- * nothing about the Debit (a proxy's 502 or 504), may still have been
92
- * admitted, so its Debit is held in doubt: the next payment for that offer
93
- * presents it again. It is held in memory, for as long as the client
94
- * runs. */
95
- wrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch;
96
- /** Holds the Debit a request carried, if it carried one of this client's. */
97
- private holdSent;
98
- private hold;
99
- /** Clears a Debit once a seller has answered about it. */
100
- private release;
101
- /** What makes two offers the same payment: a Debit signed for one can be
102
- * presented again for the other. */
103
- private offerId;
104
- /** Where the next Debit starts: the facilitator's answer while it is
105
- * fresh, else a new one. */
106
- private position;
107
- createPaymentPayload(x402Version: number, requirements: PaymentRequirements): Promise<PaymentPayloadResult>;
108
- private sign;
40
+ /** save must complete an atomic durability barrier; one process owns each payer. */
41
+ export interface ClearingClientStore {
42
+ load(): Promise<ClearingClientState | null>;
43
+ save(state: ClearingClientState): Promise<void>;
109
44
  }
110
- /** Put funds into clearing custody. `from` pays and sends the transactions:
111
- * the agent's own wallet, or anyone funding it, in which case `account` is
112
- * the agent's address. The tokens go from `from` straight into custody under
113
- * `account`; only `account`'s signature can spend or withdraw them.
114
- *
115
- * Two transactions (an approval for exactly this amount, unless that much
116
- * is already approved, then the deposit), paid for in the network's fee token by `from`. Gas and
117
- * fees are set here because a Metal node cannot be asked to estimate them.
118
- *
119
- * The operator counts a deposit some seconds after its receipt, and refuses
120
- * clearing payments against it until then. Given `facilitator`, this waits
121
- * until the facilitator reports the deposit in the payer's position, so the
122
- * next payment can draw on it. */
123
- export declare function depositToClearing(opts: {
45
+ export interface ClearingClientConfig {
46
+ serviceUrl: string;
124
47
  rpcUrl: string;
125
- chainId: number;
126
- from: LocalAccount;
127
- /** The Clearing contract and the token it holds, as the 402 names them:
128
- * `extra.clearing.contract` and `asset`. */
129
- contract: Address;
130
- asset: Address;
131
- /** Whose custody is credited. Default: `from` itself. */
132
- account?: Address;
133
- amount: bigint;
134
- /** The facilitator to wait on until it counts the deposit, as for
135
- * `ClearingDebitScheme`. Only `from`'s own custody can be waited on: the
136
- * position request is signed by the account it is about. */
137
- facilitator?: string;
138
- /** How long to wait for the facilitator to count it. Default: 60 s. */
139
- timeoutMs?: number;
140
- /** How often to ask. Default: every second. */
141
- pollMs?: number;
142
- fetch?: typeof globalThis.fetch;
143
- }): Promise<{
144
- approveTx: Hex | null;
145
- depositTx: Hex;
146
- available?: bigint;
147
- }>;
148
- /** Take funds out of clearing custody: a payer's unused deposit, or what a
149
- * seller has been paid. It asks the facilitator what can be withdrawn, signs
150
- * an Exit for that amount, and submits it; Metal's operator sends the two
151
- * transactions (request, then release) and pays their gas.
152
- *
153
- * Only this wallet's signature moves its funds, and only to the destination
154
- * it names, which defaults to the wallet itself. */
155
- export declare function withdrawFromClearing(signer: ClearingSigner, facilitator: string, options: {
156
- network: string;
157
- /** Where the funds go. Default: the wallet itself. */
158
- destination?: Address;
159
- /** How much to withdraw, in base units. Default: everything available. */
160
- amount?: bigint;
48
+ network: `eip155:${number}`;
49
+ settlement: Hex;
50
+ asset: Hex;
51
+ signer: ClearingSigner;
52
+ store: ClearingClientStore;
53
+ maxAmount: bigint;
161
54
  fetch?: typeof globalThis.fetch;
162
- /** Clock, in milliseconds. Default: wall time. */
163
- now?: () => number;
164
- }): Promise<{
165
- amount: bigint;
166
- requestTx: string;
167
- releaseTx: string | null;
168
- }>;
55
+ }
56
+ /** Why a clearing payment was not made. */
57
+ export type ClearingRefusalReason = "between_epochs" | "insufficient_deposit" | "payment_held" | "answer_lost";
58
+ /** What to do after a `ClearingRefusal`. */
59
+ export type ClearingRetry =
60
+ /** Nothing was paid: send the request again after `ms`, and wait `ms`
61
+ * again after each further `between_epochs` refusal. */
62
+ {
63
+ action: "retry_after_ms";
64
+ ms: number;
65
+ }
66
+ /** Nothing was paid: deposit more, then send the request again. */
67
+ | {
68
+ action: "deposit";
69
+ }
70
+ /** The wallet holds a payment whose outcome is unknown, `amount` base
71
+ * units for `url` (`null` when `pay` was given none; through
72
+ * `fetchWithPayment`, the URL the request was sent to), and pays nothing
73
+ * else until it goes through: send that request again at that price
74
+ * after `ms`, which sends the same payment, so it is never paid twice. If
75
+ * that URL no longer offers clearing at that price, pass the wallet's
76
+ * `pendingInvoice()` to its `pay`, which sends the held payment itself
77
+ * once more; the wallet pays others once it goes through or is refused. */
78
+ | {
79
+ action: "repeat_same_request";
80
+ url: string | null;
81
+ amount: string;
82
+ ms: number;
83
+ };
84
+ /** A clearing payment the service did not take: `reason` says why, `retry`
85
+ * what to do, and the message both in words. */
86
+ export declare class ClearingRefusal extends Error {
87
+ readonly reason: ClearingRefusalReason;
88
+ readonly retry: ClearingRetry;
89
+ readonly name = "ClearingRefusal";
90
+ constructor(reason: ClearingRefusalReason, retry: ClearingRetry, message: string, options?: ErrorOptions);
91
+ }
92
+ export declare function createClearingClient(config: ClearingClientConfig): {
93
+ account: `0x${string}`;
94
+ pendingInvoice(): Promise<ClearingInvoice | null>;
95
+ /** Why this wallet does not pay `invoice`, or undefined when it does. */
96
+ unpayable: (invoice: ClearingInvoice) => {
97
+ reason: string;
98
+ aboveMaxAmount: boolean;
99
+ } | undefined;
100
+ /** Pay `invoice`; `url`, the request it pays for, is kept with a
101
+ * payment whose outcome is unknown, so a refusal can name it. A payment
102
+ * the service does not take throws a `ClearingRefusal`. */
103
+ pay(invoice: ClearingInvoice, url?: string): Promise<ClearingPaid>;
104
+ request: <T>(path: string, body?: unknown) => Promise<T>;
105
+ };