@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.
- package/README.md +104 -38
- package/dist/clearing-abi.d.ts +1836 -0
- package/dist/clearing-abi.js +2361 -0
- package/dist/clearing-client.d.ts +96 -159
- package/dist/clearing-client.js +412 -553
- package/dist/clearing-codec.d.ts +57 -0
- package/dist/clearing-codec.js +173 -0
- package/dist/clearing-custody.d.ts +202 -0
- package/dist/clearing-custody.js +552 -0
- package/dist/clearing-server.d.ts +30 -49
- package/dist/clearing-server.js +87 -67
- package/dist/clearing-x402.d.ts +38 -0
- package/dist/clearing-x402.js +249 -0
- package/dist/client.d.ts +14 -15
- package/dist/client.js +53 -22
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/mandates/account.d.ts +9 -4
- package/dist/mandates/account.js +15 -11
- package/dist/mandates/binding.d.ts +4 -27
- package/dist/mandates/binding.js +5 -9
- package/dist/mandates/checkout.js +5 -5
- package/dist/mandates/client.js +4 -3
- package/dist/mandates/l3a.d.ts +2 -2
- package/dist/mandates/l3a.js +5 -4
- package/dist/mandates/seller.d.ts +1 -1
- package/dist/mandates/seller.js +4 -4
- package/dist/mandates/verify.d.ts +4 -6
- package/dist/mandates/verify.js +147 -82
- package/dist/mandates/vi/credential.js +4 -4
- package/dist/networks.d.ts +9 -1
- package/dist/networks.js +105 -4
- package/dist/node.d.ts +11 -0
- package/dist/node.js +97 -0
- package/package.json +18 -11
- package/src/clearing-abi.ts +2361 -0
- package/src/clearing-client.test.ts +850 -0
- package/src/clearing-client.ts +612 -0
- package/src/clearing-codec.test.ts +57 -0
- package/src/clearing-codec.ts +251 -0
- package/src/clearing-custody.test.ts +763 -0
- package/src/clearing-custody.ts +743 -0
- package/src/clearing-fixture.json +37 -0
- package/src/clearing-server.test.ts +450 -0
- package/src/clearing-server.ts +123 -0
- package/src/clearing-x402.test.ts +181 -0
- package/src/clearing-x402.ts +328 -0
- package/src/client.test.ts +333 -0
- package/src/client.ts +195 -0
- package/src/index.ts +33 -0
- package/src/mandates/account.ts +95 -0
- package/src/mandates/binding.ts +280 -0
- package/src/mandates/chain.ts +68 -0
- package/src/mandates/checkout.ts +368 -0
- package/src/mandates/client.ts +159 -0
- package/src/mandates/extension.ts +48 -0
- package/src/mandates/index.ts +121 -0
- package/src/mandates/ingest.ts +42 -0
- package/src/mandates/jcs.ts +16 -0
- package/src/mandates/l3a.ts +216 -0
- package/src/mandates/seller.ts +91 -0
- package/src/mandates/verify.ts +625 -0
- package/src/mandates/vi/credential.ts +477 -0
- package/src/mandates/vi/issue.ts +118 -0
- package/src/mandates/vi/sd-jwt.ts +189 -0
- package/src/networks.test.ts +177 -0
- package/src/networks.ts +242 -0
- package/src/node.test.ts +24 -0
- package/src/node.ts +96 -0
- package/src/server.test.ts +223 -0
- package/src/server.ts +77 -0
- package/dist/clearing.d.ts +0 -204
- package/dist/clearing.js +0 -151
- package/dist/file-storage.d.ts +0 -39
- 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
|
+
}
|