@metalntwx/metal-pay 0.2.0 → 0.3.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 (70) hide show
  1. package/README.md +72 -33
  2. package/dist/clearing-abi.d.ts +1836 -0
  3. package/dist/clearing-abi.js +2361 -0
  4. package/dist/clearing-client.d.ts +49 -159
  5. package/dist/clearing-client.js +191 -564
  6. package/dist/clearing-codec.d.ts +57 -0
  7. package/dist/clearing-codec.js +173 -0
  8. package/dist/clearing-custody.d.ts +191 -0
  9. package/dist/clearing-custody.js +490 -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 +29 -0
  13. package/dist/clearing-x402.js +203 -0
  14. package/dist/client.d.ts +7 -13
  15. package/dist/client.js +16 -18
  16. package/dist/index.d.ts +4 -1
  17. package/dist/index.js +4 -1
  18. package/dist/mandates/binding.d.ts +4 -27
  19. package/dist/mandates/binding.js +5 -9
  20. package/dist/mandates/checkout.js +5 -5
  21. package/dist/mandates/client.js +4 -3
  22. package/dist/mandates/l3a.d.ts +2 -2
  23. package/dist/mandates/l3a.js +5 -4
  24. package/dist/mandates/seller.d.ts +1 -1
  25. package/dist/mandates/seller.js +1 -1
  26. package/dist/mandates/verify.d.ts +4 -6
  27. package/dist/mandates/verify.js +42 -66
  28. package/dist/mandates/vi/credential.js +4 -4
  29. package/dist/node.d.ts +11 -0
  30. package/dist/node.js +97 -0
  31. package/package.json +18 -11
  32. package/src/clearing-abi.ts +2361 -0
  33. package/src/clearing-client.ts +303 -0
  34. package/src/clearing-codec.test.ts +57 -0
  35. package/src/clearing-codec.ts +251 -0
  36. package/src/clearing-custody.test.ts +534 -0
  37. package/src/clearing-custody.ts +665 -0
  38. package/src/clearing-fixture.json +37 -0
  39. package/src/clearing-server.test.ts +388 -0
  40. package/src/clearing-server.ts +123 -0
  41. package/src/clearing-x402.test.ts +181 -0
  42. package/src/clearing-x402.ts +279 -0
  43. package/src/client.test.ts +325 -0
  44. package/src/client.ts +156 -0
  45. package/src/index.ts +33 -0
  46. package/src/mandates/account.ts +91 -0
  47. package/src/mandates/binding.ts +280 -0
  48. package/src/mandates/chain.ts +68 -0
  49. package/src/mandates/checkout.ts +368 -0
  50. package/src/mandates/client.ts +159 -0
  51. package/src/mandates/extension.ts +48 -0
  52. package/src/mandates/index.ts +121 -0
  53. package/src/mandates/ingest.ts +42 -0
  54. package/src/mandates/jcs.ts +16 -0
  55. package/src/mandates/l3a.ts +216 -0
  56. package/src/mandates/seller.ts +91 -0
  57. package/src/mandates/verify.ts +532 -0
  58. package/src/mandates/vi/credential.ts +477 -0
  59. package/src/mandates/vi/issue.ts +118 -0
  60. package/src/mandates/vi/sd-jwt.ts +189 -0
  61. package/src/networks.test.ts +16 -0
  62. package/src/networks.ts +152 -0
  63. package/src/node.test.ts +24 -0
  64. package/src/node.ts +96 -0
  65. package/src/server.test.ts +223 -0
  66. package/src/server.ts +77 -0
  67. package/dist/clearing.d.ts +0 -204
  68. package/dist/clearing.js +0 -151
  69. package/dist/file-storage.d.ts +0 -39
  70. package/dist/file-storage.js +0 -195
@@ -0,0 +1,57 @@
1
+ import { type Hex } from "viem";
2
+ export declare const CLEARING_REFERENCE = "3134e902ccf619987e2731a0c1e63114bcc0b060";
3
+ export declare const CLEARING_SCHEME = "bajillion";
4
+ export interface OutgoingEntry {
5
+ recipient: Hex;
6
+ cumulative: bigint;
7
+ count: bigint;
8
+ }
9
+ export interface ClearingSend {
10
+ anchor: Hex;
11
+ epoch: bigint;
12
+ payer: Hex;
13
+ sequence: bigint;
14
+ debit: bigint;
15
+ vector: Hex;
16
+ }
17
+ export interface ClearingReceipt {
18
+ body: Hex;
19
+ payerSignature: Hex;
20
+ operatorSignature: Hex;
21
+ }
22
+ /** The clearing units `amount` token base units make, where `unit` is the
23
+ * deployment's `Clearing.unit()`: Clearing counts whole units, so a fraction
24
+ * of one is refused rather than rounded. */
25
+ export declare function clearingUnits(amount: bigint, unit: bigint): bigint;
26
+ export declare function clearingUint64(value: bigint): Hex;
27
+ export declare function clearingAccount(value: Hex): Hex;
28
+ /** The exact typed BMT root used by commonware-clearing at CLEARING_REFERENCE. */
29
+ export declare function outgoingRoot(entries: readonly OutgoingEntry[]): Hex;
30
+ export declare function encodeSend(body: ClearingSend): Hex;
31
+ export declare function decodeSend(encoded: Hex): ClearingSend;
32
+ export declare function clearingSignatureDigest(namespace: string, message: Hex): Hex;
33
+ export declare function verifyClearingSignature(key: Hex, namespace: string, message: Hex, signature: Hex): boolean;
34
+ export declare function verifyReceipt(receipt: ClearingReceipt, operator: Hex): boolean;
35
+ export interface ClearingInvoice {
36
+ id: string;
37
+ network: string;
38
+ settlement: Hex;
39
+ asset: Hex;
40
+ recipient: Hex;
41
+ amount: string;
42
+ }
43
+ /** The HTTP invoice is separately signed; it does not alter Bajillion's wire format. */
44
+ export declare function invoiceMessage(invoice: ClearingInvoice, body: Hex): Hex;
45
+ export declare function createClearingSigner(privateKey: Hex): {
46
+ publicKey: `0x${string}`;
47
+ sign(namespace: string, message: Hex): Hex;
48
+ };
49
+ export declare function decodeWithdrawal(encoded: Hex): {
50
+ account: `0x${string}`;
51
+ deployment: `0x${string}`;
52
+ state: `0x${string}`;
53
+ destination: `0x${string}`;
54
+ amount: bigint;
55
+ deadline: bigint;
56
+ signature: `0x${string}`;
57
+ };
@@ -0,0 +1,173 @@
1
+ import { p256 } from "@noble/curves/p256";
2
+ import { concatHex, hexToBytes, sha256, stringToHex, toHex, } from "viem";
3
+ export const CLEARING_REFERENCE = "3134e902ccf619987e2731a0c1e63114bcc0b060";
4
+ export const CLEARING_SCHEME = "bajillion";
5
+ const U64_MAX = (1n << 64n) - 1n;
6
+ /** The clearing units `amount` token base units make, where `unit` is the
7
+ * deployment's `Clearing.unit()`: Clearing counts whole units, so a fraction
8
+ * of one is refused rather than rounded. */
9
+ export function clearingUnits(amount, unit) {
10
+ if (unit <= 0n || amount % unit !== 0n)
11
+ throw new Error(`${amount} base units is not a whole number of clearing units of ${unit}`);
12
+ return amount / unit;
13
+ }
14
+ export function clearingUint64(value) {
15
+ if (value < 0n || value > U64_MAX)
16
+ throw new Error("clearing amount exceeds uint64");
17
+ return toHex(value, { size: 8 });
18
+ }
19
+ function fixed(value, length) {
20
+ if (!new RegExp(`^0x[0-9a-fA-F]{${length * 2}}$`).test(value))
21
+ throw new Error("invalid clearing wire length");
22
+ return value.toLowerCase();
23
+ }
24
+ export function clearingAccount(value) {
25
+ fixed(value, 33);
26
+ const point = p256.ProjectivePoint.fromHex(hexToBytes(value));
27
+ point.assertValidity();
28
+ // The canonical compressed encoding of the parsed point, not the wire
29
+ // string: what reaches a receipt URL or a signature check is what the
30
+ // curve produced, so no byte of the input passes through untouched.
31
+ return toHex(point.toRawBytes(true));
32
+ }
33
+ function hash(...values) {
34
+ return sha256(concatHex(values));
35
+ }
36
+ const domain = (name) => stringToHex(`_COMMONWARE_CLEARING_${name}`);
37
+ /** The exact typed BMT root used by commonware-clearing at CLEARING_REFERENCE. */
38
+ export function outgoingRoot(entries) {
39
+ if (entries.length > 1 << 24)
40
+ throw new Error("too many clearing entries");
41
+ const length = toHex(entries.length, { size: 4 });
42
+ let previous = "";
43
+ let total = 0n;
44
+ let count = 0n;
45
+ let level = entries.map((entry, position) => {
46
+ const recipient = clearingAccount(entry.recipient);
47
+ if (recipient <= previous ||
48
+ entry.count <= 0n ||
49
+ entry.cumulative < entry.count)
50
+ throw new Error("noncanonical clearing vector");
51
+ previous = recipient;
52
+ total += entry.cumulative;
53
+ count += entry.count;
54
+ clearingUint64(total);
55
+ clearingUint64(count);
56
+ const encoded = concatHex([
57
+ recipient,
58
+ clearingUint64(entry.cumulative),
59
+ clearingUint64(entry.count),
60
+ ]);
61
+ const index = toHex(position, { size: 4 });
62
+ const leaf = hash(domain("OUT_ENTRY_LEAF"), length, index, clearingUint64(49n), encoded);
63
+ return hash(index, leaf);
64
+ });
65
+ if (level.length === 0)
66
+ level = [hash("0x")];
67
+ while (level.length > 1) {
68
+ const next = [];
69
+ for (let i = 0; i < level.length; i += 2)
70
+ next.push(hash(level[i], level[i + 1] ?? level[i]));
71
+ level = next;
72
+ }
73
+ return hash(domain("OUT_ENTRY_ROOT"), length, hash(length, level[0]));
74
+ }
75
+ export function encodeSend(body) {
76
+ return concatHex([
77
+ fixed(body.anchor, 32),
78
+ clearingUint64(body.epoch),
79
+ clearingAccount(body.payer),
80
+ clearingUint64(body.sequence),
81
+ clearingUint64(body.debit),
82
+ fixed(body.vector, 32),
83
+ ]);
84
+ }
85
+ export function decodeSend(encoded) {
86
+ fixed(encoded, 121);
87
+ const part = (start, length) => `0x${encoded.slice(2 + start * 2, 2 + (start + length) * 2)}`;
88
+ return {
89
+ anchor: part(0, 32),
90
+ epoch: BigInt(part(32, 8)),
91
+ payer: clearingAccount(part(40, 33)),
92
+ sequence: BigInt(part(73, 8)),
93
+ debit: BigInt(part(81, 8)),
94
+ vector: part(89, 32),
95
+ };
96
+ }
97
+ export function clearingSignatureDigest(namespace, message) {
98
+ const bytes = stringToHex(namespace);
99
+ const length = (bytes.length - 2) / 2;
100
+ if (length >= 128)
101
+ throw new Error("unsupported signature namespace length");
102
+ return hash(toHex(length, { size: 1 }), bytes, message);
103
+ }
104
+ export function verifyClearingSignature(key, namespace, message, signature) {
105
+ try {
106
+ clearingAccount(key);
107
+ fixed(signature, 64);
108
+ return p256.verify(hexToBytes(signature), hexToBytes(clearingSignatureDigest(namespace, message)), hexToBytes(key), { lowS: true, prehash: false });
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ }
114
+ export function verifyReceipt(receipt, operator) {
115
+ try {
116
+ const body = decodeSend(receipt.body);
117
+ return (verifyClearingSignature(body.payer, "_COMMONWARE_CLEARING_VECTOR_SEND", receipt.body, receipt.payerSignature) &&
118
+ verifyClearingSignature(operator, "_COMMONWARE_CLEARING_VECTOR_ACK", receipt.body, receipt.operatorSignature));
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ /** The HTTP invoice is separately signed; it does not alter Bajillion's wire format. */
125
+ export function invoiceMessage(invoice, body) {
126
+ if (!/^[a-zA-Z0-9:_./-]{1,256}$/.test(invoice.id))
127
+ throw new Error("invalid clearing invoice id");
128
+ if (!/^eip155:[1-9][0-9]*$/.test(invoice.network))
129
+ throw new Error("invalid clearing network");
130
+ return stringToHex(JSON.stringify([
131
+ invoice.id,
132
+ invoice.network,
133
+ fixed(invoice.settlement, 20),
134
+ fixed(invoice.asset, 20),
135
+ clearingAccount(invoice.recipient),
136
+ // Invoices count ERC-20 base units; only clearing balances use uint64.
137
+ toHex(BigInt(invoice.amount), { size: 32 }),
138
+ sha256(body),
139
+ ]));
140
+ }
141
+ export function createClearingSigner(privateKey) {
142
+ const key = hexToBytes(fixed(privateKey, 32));
143
+ const publicKey = toHex(p256.getPublicKey(key, true));
144
+ return {
145
+ publicKey,
146
+ sign(namespace, message) {
147
+ return toHex(p256
148
+ .sign(hexToBytes(clearingSignatureDigest(namespace, message)), key, {
149
+ lowS: true,
150
+ prehash: false,
151
+ })
152
+ .toCompactRawBytes());
153
+ },
154
+ };
155
+ }
156
+ export function decodeWithdrawal(encoded) {
157
+ const raw = hexToBytes(encoded);
158
+ if ((raw.length !== 199 && raw.length !== 191) || raw[97] !== 20)
159
+ throw new Error("invalid native withdrawal");
160
+ const part = (offset, length) => toHex(raw.slice(offset, offset + length));
161
+ const close = raw[118] === 2;
162
+ if ((!close && raw[118] !== 1) || raw.length !== (close ? 191 : 199))
163
+ throw new Error("invalid withdrawal action");
164
+ return {
165
+ account: clearingAccount(part(0, 33)),
166
+ deployment: part(33, 32),
167
+ state: part(65, 32),
168
+ destination: part(98, 20),
169
+ amount: close ? 0n : BigInt(part(119, 8)),
170
+ deadline: BigInt(part(close ? 119 : 127, 8)),
171
+ signature: part(close ? 127 : 135, 64),
172
+ };
173
+ }
@@ -0,0 +1,191 @@
1
+ import { type Address, type Hex, type LocalAccount } from "viem";
2
+ import type { ClearingSigner } from "./clearing-client.js";
3
+ export interface ClearingLogProof {
4
+ location: string;
5
+ leaves: string;
6
+ inactivePeaks: number;
7
+ digests: Hex[];
8
+ }
9
+ export interface ClearingBalance {
10
+ state: Hex;
11
+ amount: string;
12
+ opening: null | (ClearingLogProof & {
13
+ amount: string;
14
+ nextKey: Hex;
15
+ chunk: Hex;
16
+ opsRoot: Hex;
17
+ pending: Hex;
18
+ partialDigest: Hex;
19
+ });
20
+ }
21
+ export declare function clearingLogProof<T extends ClearingLogProof>(proof: T): T & {
22
+ location: bigint;
23
+ leaves: bigint;
24
+ inactivePeaks: bigint;
25
+ };
26
+ /** Authorize an epoch-tail close to a fixed EVM destination. */
27
+ export declare function clearingClose(signer: ClearingSigner, deployment: Hex, balance: ClearingBalance, destination: Hex, deadline: bigint): Promise<{
28
+ withdrawal: {
29
+ account: `0x${string}`;
30
+ deployment: `0x${string}`;
31
+ state: `0x${string}`;
32
+ destination: `0x${string}`;
33
+ amount: bigint;
34
+ deadline: bigint;
35
+ signature: `0x${string}`;
36
+ };
37
+ opening: {
38
+ amount: bigint;
39
+ nextKey: `0x${string}`;
40
+ proof: {
41
+ location: string;
42
+ leaves: string;
43
+ inactivePeaks: number;
44
+ digests: Hex[];
45
+ chunk: Hex;
46
+ opsRoot: Hex;
47
+ pending: Hex;
48
+ partialDigest: Hex;
49
+ } & {
50
+ location: bigint;
51
+ leaves: bigint;
52
+ inactivePeaks: bigint;
53
+ };
54
+ };
55
+ }>;
56
+ /** The most the Clearing contract holds in all, in clearing units: it
57
+ * counts `uint64` units of `Clearing.unit()` token base units each, a
58
+ * millionth of the token (its base unit at six decimals or fewer). */
59
+ export declare const CLEARING_MAX_AMOUNT: bigint;
60
+ /** What `account` can still spend in the open epoch, in the token's base
61
+ * units: the operator's estimate from the balance and deposits it has
62
+ * counted and the payments it has acknowledged. */
63
+ export declare function clearingSpendable(opts: {
64
+ rpcUrl: string;
65
+ settlement: Address;
66
+ serviceUrl: string;
67
+ account: Hex;
68
+ fetch?: typeof globalThis.fetch;
69
+ }): Promise<bigint>;
70
+ /** `account`'s finalized balance in clearing units and, when the service
71
+ * retains one, the proof `withdrawFromClearing` closes it with. */
72
+ export declare function clearingBalance(serviceUrl: string, account: Hex, fetcher?: typeof globalThis.fetch): Promise<ClearingBalance>;
73
+ export interface ClearingChain {
74
+ rpcUrl: string;
75
+ chainId: number;
76
+ /** The EVM account that sends the transactions and pays their gas. */
77
+ from: LocalAccount;
78
+ settlement: Address;
79
+ }
80
+ /** Put `amount` token base units into clearing custody under `account`, a
81
+ * clearing key; it must be a whole number of the contract's clearing
82
+ * units. `from` pays: an approval for the amount unless one that covers it
83
+ * stands, then the deposit. The contract takes deposits only between
84
+ * registrations, so the deposit waits for that window and is sent again
85
+ * if the window closes under it, up to `timeoutMs` (default 60 s). It
86
+ * refuses at once a contract that holds another token, and a deposit past
87
+ * what the contract holds in all. A deposit whose outcome is unknown is not
88
+ * sent again: the error names it. */
89
+ export declare function depositToClearing(opts: ClearingChain & {
90
+ asset: Address;
91
+ account: Hex;
92
+ amount: bigint;
93
+ timeoutMs?: number;
94
+ pollMs?: number;
95
+ }): Promise<{
96
+ approveTx: Hex | null;
97
+ depositTx: Hex;
98
+ }>;
99
+ /** A payout claimed, `amount` in token base units. */
100
+ type ClearingClaim = {
101
+ amount: bigint;
102
+ tx: Hex;
103
+ deferred: boolean;
104
+ };
105
+ export type ClearingWithdrawal =
106
+ /** A withdrawal is queued and `epoch`, the one that carries it, has not
107
+ * finalized; nothing can be sent until it does. */
108
+ {
109
+ status: "waiting";
110
+ epoch: bigint;
111
+ }
112
+ /** What this call sent: the account's payout it claimed, to the
113
+ * destination the finalized withdrawal named, and the withdrawal it
114
+ * queued for the account's finalized balance, or `null` when that balance
115
+ * is zero. A
116
+ * deferred payout is one the destination refused: it waits in the
117
+ * contract's `unclaimedPayout` until the destination calls
118
+ * `withdrawUnclaimed`. */
119
+ | {
120
+ status: "sent";
121
+ claimed: ClearingClaim[];
122
+ queued: {
123
+ tx: Hex;
124
+ } | null;
125
+ };
126
+ /** Take `signer`'s funds out of clearing: claim the account's payout of a
127
+ * withdrawal that has finalized, to the destination it named, then queue a
128
+ * withdrawal of whatever finalized balance remains, to `destination` (`from` itself
129
+ * unless given). A payer's unused deposit and a seller's earnings leave the
130
+ * same way. Call again after the queued withdrawal's epoch finalizes. A
131
+ * failure after a claim names the claims made before it. */
132
+ export declare function withdrawFromClearing(opts: ClearingChain & {
133
+ signer: ClearingSigner;
134
+ serviceUrl: string;
135
+ destination?: Address;
136
+ fetch?: typeof globalThis.fetch;
137
+ }): Promise<ClearingWithdrawal>;
138
+ export type ClearingRecovery =
139
+ /** The deployment has missed no deadline: withdraw with `withdrawFromClearing`. */
140
+ {
141
+ status: "operating";
142
+ }
143
+ /** Faulted, and `epoch`, admitted before the fault, is still in its
144
+ * challenge window; recovery starts once it is final. `claimed` is the
145
+ * payout of the account's finalized withdrawal, claimed meanwhile. */
146
+ | {
147
+ status: "waiting";
148
+ epoch: bigint;
149
+ claimed: ClearingClaim[];
150
+ }
151
+ /** What this call sent: the fault it raised, if it raised one, the
152
+ * payout of the account's finalized withdrawal, claimed to the
153
+ * destination it named, the account's last finalized balance, and each
154
+ * deposit no final epoch counted. `recover` pays a withdrawal still in
155
+ * flight to the destination it named and the rest to `destination`. A
156
+ * deferred payout waits in `unclaimedPayout` for `withdrawUnclaimed`.
157
+ * Every amount is in token base units. */
158
+ | {
159
+ status: "recovered";
160
+ faulted: Hex | null;
161
+ claimed: ClearingClaim[];
162
+ recovered: {
163
+ amount: bigint;
164
+ tx: Hex;
165
+ } | null;
166
+ refunded: {
167
+ epoch: bigint;
168
+ amount: bigint;
169
+ tx: Hex;
170
+ }[];
171
+ };
172
+ /** Take `signer`'s funds out of a Clearing deployment whose service has
173
+ * stopped. Once the service has missed a deadline, to admit an epoch it
174
+ * registered, a deposit, or the account's queued withdrawal, this faults
175
+ * the deployment, finalizes the epochs it admitted, starts recovery, and
176
+ * takes the payout of the account's finalized withdrawal, its last
177
+ * finalized balance and every deposit no final epoch counted, to
178
+ * `destination` (`from` itself unless given) or, for a withdrawal, to the
179
+ * destination it named. The payout and balance proofs come from
180
+ * `serviceUrl`, the clearing service or any of its validators, or the
181
+ * first of several that proves each. A proof none of them gives does not
182
+ * stop the steps that need none: they are taken, and then the error names
183
+ * what is left and what was sent. Each step already taken is skipped, so
184
+ * call it again after `waiting` or such an error. `from` pays the gas. */
185
+ export declare function recoverFromClearing(opts: ClearingChain & {
186
+ signer: ClearingSigner;
187
+ serviceUrl: string | readonly string[];
188
+ destination?: Address;
189
+ fetch?: typeof globalThis.fetch;
190
+ }): Promise<ClearingRecovery>;
191
+ export {};