@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
@@ -0,0 +1,251 @@
1
+ import { p256 } from "@noble/curves/p256";
2
+ import {
3
+ concatHex,
4
+ hexToBytes,
5
+ sha256,
6
+ stringToHex,
7
+ toHex,
8
+ type Hex,
9
+ } from "viem";
10
+
11
+ export const CLEARING_REFERENCE = "3134e902ccf619987e2731a0c1e63114bcc0b060";
12
+ export const CLEARING_SCHEME = "bajillion";
13
+ const U64_MAX = (1n << 64n) - 1n;
14
+
15
+ export interface OutgoingEntry {
16
+ recipient: Hex;
17
+ cumulative: bigint;
18
+ count: bigint;
19
+ }
20
+ export interface ClearingSend {
21
+ anchor: Hex;
22
+ epoch: bigint;
23
+ payer: Hex;
24
+ sequence: bigint;
25
+ debit: bigint;
26
+ vector: Hex;
27
+ }
28
+ export interface ClearingReceipt {
29
+ body: Hex;
30
+ payerSignature: Hex;
31
+ operatorSignature: Hex;
32
+ }
33
+
34
+ /** The clearing units `amount` token base units make, where `unit` is the
35
+ * deployment's `Clearing.unit()`: Clearing counts whole units, so a fraction
36
+ * of one is refused rather than rounded. */
37
+ export function clearingUnits(amount: bigint, unit: bigint): bigint {
38
+ if (unit <= 0n || amount % unit !== 0n)
39
+ throw new Error(
40
+ `${amount} base units is not a whole number of clearing units of ${unit}`,
41
+ );
42
+ return amount / unit;
43
+ }
44
+ export function clearingUint64(value: bigint): Hex {
45
+ if (value < 0n || value > U64_MAX)
46
+ throw new Error("clearing amount exceeds uint64");
47
+ return toHex(value, { size: 8 });
48
+ }
49
+ function fixed(value: Hex, length: number): Hex {
50
+ if (!new RegExp(`^0x[0-9a-fA-F]{${length * 2}}$`).test(value))
51
+ throw new Error("invalid clearing wire length");
52
+ return value.toLowerCase() as Hex;
53
+ }
54
+ export function clearingAccount(value: Hex): Hex {
55
+ fixed(value, 33);
56
+ const point = p256.ProjectivePoint.fromHex(hexToBytes(value));
57
+ point.assertValidity();
58
+ // The canonical compressed encoding of the parsed point, not the wire
59
+ // string: what reaches a receipt URL or a signature check is what the
60
+ // curve produced, so no byte of the input passes through untouched.
61
+ return toHex(point.toRawBytes(true));
62
+ }
63
+ function hash(...values: Hex[]): Hex {
64
+ return sha256(concatHex(values));
65
+ }
66
+ const domain = (name: string) => stringToHex(`_COMMONWARE_CLEARING_${name}`);
67
+
68
+ /** The exact typed BMT root used by commonware-clearing at CLEARING_REFERENCE. */
69
+ export function outgoingRoot(entries: readonly OutgoingEntry[]): Hex {
70
+ if (entries.length > 1 << 24) throw new Error("too many clearing entries");
71
+ const length = toHex(entries.length, { size: 4 });
72
+ let previous = "";
73
+ let total = 0n;
74
+ let count = 0n;
75
+ let level = entries.map((entry, position) => {
76
+ const recipient = clearingAccount(entry.recipient);
77
+ if (
78
+ recipient <= previous ||
79
+ entry.count <= 0n ||
80
+ entry.cumulative < entry.count
81
+ )
82
+ throw new Error("noncanonical clearing vector");
83
+ previous = recipient;
84
+ total += entry.cumulative;
85
+ count += entry.count;
86
+ clearingUint64(total);
87
+ clearingUint64(count);
88
+ const encoded = concatHex([
89
+ recipient,
90
+ clearingUint64(entry.cumulative),
91
+ clearingUint64(entry.count),
92
+ ]);
93
+ const index = toHex(position, { size: 4 });
94
+ const leaf = hash(
95
+ domain("OUT_ENTRY_LEAF"),
96
+ length,
97
+ index,
98
+ clearingUint64(49n),
99
+ encoded,
100
+ );
101
+ return hash(index, leaf);
102
+ });
103
+ if (level.length === 0) level = [hash("0x")];
104
+ while (level.length > 1) {
105
+ const next: Hex[] = [];
106
+ for (let i = 0; i < level.length; i += 2)
107
+ next.push(hash(level[i]!, level[i + 1] ?? level[i]!));
108
+ level = next;
109
+ }
110
+ return hash(domain("OUT_ENTRY_ROOT"), length, hash(length, level[0]!));
111
+ }
112
+
113
+ export function encodeSend(body: ClearingSend): Hex {
114
+ return concatHex([
115
+ fixed(body.anchor, 32),
116
+ clearingUint64(body.epoch),
117
+ clearingAccount(body.payer),
118
+ clearingUint64(body.sequence),
119
+ clearingUint64(body.debit),
120
+ fixed(body.vector, 32),
121
+ ]);
122
+ }
123
+ export function decodeSend(encoded: Hex): ClearingSend {
124
+ fixed(encoded, 121);
125
+ const part = (start: number, length: number) =>
126
+ `0x${encoded.slice(2 + start * 2, 2 + (start + length) * 2)}` as Hex;
127
+ return {
128
+ anchor: part(0, 32),
129
+ epoch: BigInt(part(32, 8)),
130
+ payer: clearingAccount(part(40, 33)),
131
+ sequence: BigInt(part(73, 8)),
132
+ debit: BigInt(part(81, 8)),
133
+ vector: part(89, 32),
134
+ };
135
+ }
136
+ export function clearingSignatureDigest(namespace: string, message: Hex): Hex {
137
+ const bytes = stringToHex(namespace);
138
+ const length = (bytes.length - 2) / 2;
139
+ if (length >= 128) throw new Error("unsupported signature namespace length");
140
+ return hash(toHex(length, { size: 1 }), bytes, message);
141
+ }
142
+ export function verifyClearingSignature(
143
+ key: Hex,
144
+ namespace: string,
145
+ message: Hex,
146
+ signature: Hex,
147
+ ): boolean {
148
+ try {
149
+ clearingAccount(key);
150
+ fixed(signature, 64);
151
+ return p256.verify(
152
+ hexToBytes(signature),
153
+ hexToBytes(clearingSignatureDigest(namespace, message)),
154
+ hexToBytes(key),
155
+ { lowS: true, prehash: false },
156
+ );
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ export function verifyReceipt(
162
+ receipt: ClearingReceipt,
163
+ operator: Hex,
164
+ ): boolean {
165
+ try {
166
+ const body = decodeSend(receipt.body);
167
+ return (
168
+ verifyClearingSignature(
169
+ body.payer,
170
+ "_COMMONWARE_CLEARING_VECTOR_SEND",
171
+ receipt.body,
172
+ receipt.payerSignature,
173
+ ) &&
174
+ verifyClearingSignature(
175
+ operator,
176
+ "_COMMONWARE_CLEARING_VECTOR_ACK",
177
+ receipt.body,
178
+ receipt.operatorSignature,
179
+ )
180
+ );
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ export interface ClearingInvoice {
187
+ id: string;
188
+ network: string;
189
+ settlement: Hex;
190
+ asset: Hex;
191
+ recipient: Hex;
192
+ amount: string;
193
+ }
194
+
195
+ /** The HTTP invoice is separately signed; it does not alter Bajillion's wire format. */
196
+ export function invoiceMessage(invoice: ClearingInvoice, body: Hex): Hex {
197
+ if (!/^[a-zA-Z0-9:_./-]{1,256}$/.test(invoice.id))
198
+ throw new Error("invalid clearing invoice id");
199
+ if (!/^eip155:[1-9][0-9]*$/.test(invoice.network))
200
+ throw new Error("invalid clearing network");
201
+ return stringToHex(
202
+ JSON.stringify([
203
+ invoice.id,
204
+ invoice.network,
205
+ fixed(invoice.settlement, 20),
206
+ fixed(invoice.asset, 20),
207
+ clearingAccount(invoice.recipient),
208
+ // Invoices count ERC-20 base units; only clearing balances use uint64.
209
+ toHex(BigInt(invoice.amount), { size: 32 }),
210
+ sha256(body),
211
+ ]),
212
+ );
213
+ }
214
+
215
+ export function createClearingSigner(privateKey: Hex) {
216
+ const key = hexToBytes(fixed(privateKey, 32));
217
+ const publicKey = toHex(p256.getPublicKey(key, true));
218
+ return {
219
+ publicKey,
220
+ sign(namespace: string, message: Hex): Hex {
221
+ return toHex(
222
+ p256
223
+ .sign(hexToBytes(clearingSignatureDigest(namespace, message)), key, {
224
+ lowS: true,
225
+ prehash: false,
226
+ })
227
+ .toCompactRawBytes(),
228
+ );
229
+ },
230
+ };
231
+ }
232
+
233
+ export function decodeWithdrawal(encoded: Hex) {
234
+ const raw = hexToBytes(encoded);
235
+ if ((raw.length !== 199 && raw.length !== 191) || raw[97] !== 20)
236
+ throw new Error("invalid native withdrawal");
237
+ const part = (offset: number, length: number) =>
238
+ toHex(raw.slice(offset, offset + length));
239
+ const close = raw[118] === 2;
240
+ if ((!close && raw[118] !== 1) || raw.length !== (close ? 191 : 199))
241
+ throw new Error("invalid withdrawal action");
242
+ return {
243
+ account: clearingAccount(part(0, 33)),
244
+ deployment: part(33, 32),
245
+ state: part(65, 32),
246
+ destination: part(98, 20),
247
+ amount: close ? 0n : BigInt(part(119, 8)),
248
+ deadline: BigInt(part(close ? 119 : 127, 8)),
249
+ signature: part(close ? 127 : 135, 64),
250
+ };
251
+ }