402-trinity-gaming 0.1.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.
package/dist/x402.d.ts ADDED
@@ -0,0 +1,391 @@
1
+ /**
2
+ * x402-trinity - zero-dependency x402 payment interceptor for edge runtimes.
3
+ *
4
+ * Native primitives only: BigInt, Uint32Array, crypto.getRandomValues, fetch, btoa.
5
+ * secp256k1 + keccak256 + EIP-712/EIP-3009 are implemented inline because WebCrypto
6
+ * (crypto.subtle) exposes neither the secp256k1 curve nor a keccak256 digest.
7
+ *
8
+ * Live-path cost once warm: 3 keccak permutations + 2 modmuls. No key generation,
9
+ * no curve multiplication, no scalar tables built at request time.
10
+ */
11
+ /** keccak256 over one or more byte runs, absorbed as if concatenated. */
12
+ export declare function keccak256(...parts: Uint8Array[]): Uint8Array;
13
+ export declare const toHex: (b: Uint8Array) => string;
14
+ export declare const fromHex: (h: string) => Uint8Array;
15
+ declare const beBytes: (v: bigint, len: number) => Uint8Array;
16
+ declare const toBig: (b: Uint8Array) => bigint;
17
+ type J = [bigint, bigint, bigint];
18
+ declare function jAdd(p: J, q: J): J;
19
+ /**
20
+ * Scalar multiply. Runs only off the hot path (key setup + idle nonce fill), so it is
21
+ * a plain double-and-add: variable-time, but never executed while a request is in flight.
22
+ * See README "Known tradeoffs" before using this in a shared-tenant process.
23
+ */
24
+ declare function jMul(k: bigint, p: J): J;
25
+ /**
26
+ * Constant-time-hardened scalar multiply, for scalars that ARE secret: the private key
27
+ * (addressOf) and the ECDSA nonce k (makeNonce - leaking k leaks the key outright).
28
+ *
29
+ * Two countermeasures:
30
+ * 1. Scalar blinding - compute (k + r*n)*G instead of k*G. Identical result because
31
+ * n*G is the point at infinity, but the bit pattern is re-randomised every call,
32
+ * so repeated signings never expose the same operation sequence twice.
33
+ * 2. Always-add-and-double over a FIXED iteration count, with a branchless select.
34
+ * The addition is performed on every bit and the result chosen by bit-mask, so the
35
+ * add/no-add pattern no longer tracks the key bits, and the loop count no longer
36
+ * reveals the scalar's bit length.
37
+ *
38
+ * HONEST SCOPE - this is hardening, not a constant-time proof. JavaScript BigInt
39
+ * arithmetic is itself variable-time (V8 short-circuits on operand size and allocates
40
+ * per operation), and no pure-JS implementation can remove that. What is removed is the
41
+ * large, directly key-correlated leak: the data-dependent branch on each key bit.
42
+ * If the threat model genuinely requires constant-time signing, use `remoteSign` and an
43
+ * HSM. Cost: about 1.7x the work of the variable-time path - see the benchmark.
44
+ */
45
+ declare function jMulCT(k: bigint, p: J): J;
46
+ declare const affine: (p: J) => [bigint, bigint];
47
+ /** Lowercase 20-byte address for a private scalar. */
48
+ declare function addressOf(d: bigint): string;
49
+ interface Nonce {
50
+ kInv: bigint;
51
+ r: bigint;
52
+ rec: number;
53
+ }
54
+ /**
55
+ * Precompute k*G, r and k^-1. This is the entire expensive half of ECDSA and it does
56
+ * not depend on the message, so it is fully computable before the 402 ever arrives.
57
+ * A Nonce is single-use: reusing k across two signatures leaks the private key.
58
+ */
59
+ declare function makeNonce(): Nonce;
60
+ /** Live path: two modmuls plus low-s normalization (EIP-2). No curve operations. */
61
+ declare function signWith(nc: Nonce, z: bigint, d: bigint): string;
62
+ export interface Authorization {
63
+ from: string;
64
+ to: string;
65
+ value: string;
66
+ validAfter: string;
67
+ validBefore: string;
68
+ nonce: string;
69
+ }
70
+ declare const domainSep: (name: string, version: string, chainId: number, verifying: string) => Uint8Array;
71
+ declare function digest(dsep: Uint8Array, a: Authorization): bigint;
72
+ export interface Requirement {
73
+ scheme: string;
74
+ network: string;
75
+ payTo: string;
76
+ asset: string;
77
+ maxAmountRequired: string;
78
+ maxTimeoutSeconds?: number;
79
+ extra?: {
80
+ name?: string;
81
+ version?: string;
82
+ } | null;
83
+ resource?: string;
84
+ }
85
+ /**
86
+ * MAINNET ONLY. chainId + USDC defaults, used to fill gaps the server left.
87
+ *
88
+ * Every field here was read off the deployed contract - `name()`, `version()`, `eth_chainId`
89
+ * and `DOMAIN_SEPARATOR()` - not copied from documentation. A wrong `name` produces a wrong
90
+ * domain separator and every payment on that chain is silently rejected on-chain.
91
+ *
92
+ * No testnets ship. Add any chain you need - including a testnet - via `customChains`.
93
+ */
94
+ export interface ChainSpec {
95
+ id: number;
96
+ asset: string;
97
+ name: string;
98
+ version: string;
99
+ }
100
+ /**
101
+ * The fee disclosure, as a value rather than a side effect. Nothing prints it: a library
102
+ * running inside a studio's process has no business writing to their console. Surface it
103
+ * wherever disclosure belongs for you - store terms, a settings screen, your own logger -
104
+ * or pass `surcharge.onNotice` to receive it at construction.
105
+ */
106
+ export declare const NOTICE: string;
107
+ /** ResourceInfo, introduced in v2 (split out of PaymentRequirements). */
108
+ export interface ResourceInfo {
109
+ url: string;
110
+ description?: string;
111
+ mimeType?: string;
112
+ }
113
+ export interface Policy {
114
+ /** Hard ceiling for a single 402, in atomic asset units (USDC = 6dp). Required. */
115
+ maxAmountPerRequest: bigint | string;
116
+ /** Hard cumulative ceiling for the life of this wrapper instance. Required. */
117
+ totalBudget: bigint | string;
118
+ /** If set, only these hostnames may be paid. Strongly recommended. */
119
+ allowHosts?: string[];
120
+ allowPayTo?: string[];
121
+ allowAssets?: string[];
122
+ allowNetworks?: string[];
123
+ /** Preferred settlement networks, best first. Reorders a multi-option `accepts` list. */
124
+ preferNetworks?: string[];
125
+ }
126
+ export interface X402Config {
127
+ /** Full private key, or `shards`, or `remoteSign` - exactly one source of signing power. */
128
+ privateKey?: string;
129
+ /** Additive shards: d = (s0 + s1 + ...) mod n. No single shard is a spending key. */
130
+ shards?: string[];
131
+ /** Delegate signing to an external signer / real MPC service instead of local key material. */
132
+ remoteSign?: (digestHex: string, auth: Authorization, req: Requirement) => Promise<string>;
133
+ /**
134
+ * Payer address. Required with `remoteSign`, since no local key implies one.
135
+ *
136
+ * You may also supply it ALONGSIDE a private key purely as an optimization: it skips
137
+ * address derivation (a 1.23 ms constant-time scalar multiply) on every cold start.
138
+ * That matters on Cloudflare Workers, where cold CPU was measured at 9-11 ms against a
139
+ * 10 ms free-tier ceiling. It is checked once in the background; a wrong value fails
140
+ * closed (the facilitator rejects every payment) and sets stats().fromAddressMismatch.
141
+ */
142
+ fromAddress?: string;
143
+ /**
144
+ * Force the background fromAddress/key consistency check even on an edge runtime, where
145
+ * it is skipped by default because ctx.waitUntil time is billed and there is no isolate
146
+ * affinity - running it there costs exactly what fromAddress was passed to save.
147
+ * Leave this off in production; turn it on once while wiring a new deployment up.
148
+ */
149
+ verifyFromAddress?: boolean;
150
+ /**
151
+ * Extra networks, merged over the built-in mainnet table. This is how you add a chain the
152
+ * package does not ship - including a testnet, if you want to rehearse before going live.
153
+ *
154
+ * customChains: {
155
+ * 'base-sepolia': { id: 84532, asset: '0x036cbd...', name: 'USDC', version: '2' },
156
+ * }
157
+ *
158
+ * VERIFY these against the deployed contract before use: call DOMAIN_SEPARATOR() and check
159
+ * it equals what this library computes. A wrong `name` or `version` yields a valid-looking
160
+ * signature that the contract will reject.
161
+ */
162
+ customChains?: Record<string, ChainSpec>;
163
+ policy: Policy;
164
+ baseFetch?: typeof fetch;
165
+ /**
166
+ * 'longlived' - eagerly warm the nonce pool on idle callbacks (servers, agents, robotics).
167
+ * 'edge' - never warm eagerly; sign on demand (~750us, still sub-ms) and top up only
168
+ * in the background after a response. Correct for short-lived V8 isolates,
169
+ * which have no idle time and a tight CPU budget.
170
+ * 'auto' - 'edge' when running on Cloudflare Workers, else 'longlived'. Default.
171
+ */
172
+ mode?: 'longlived' | 'edge' | 'auto';
173
+ /** Max nonces held. Default 16 long-lived, 4 on edge. */
174
+ poolSize?: number;
175
+ /**
176
+ * Edge mode: nonces generated per background top-up. **Default 0 - the pool is OFF.**
177
+ *
178
+ * Measured on production Cloudflare Workers: `ctx.waitUntil` work is BILLED against the
179
+ * CPU budget, and Workers gives no isolate affinity, so a nonce built in the background
180
+ * is usually discarded when the next request lands on a different isolate. Enabling it
181
+ * cost 4 ms of median CPU and pushed p90 from 6 ms to 12 ms - over the 10 ms free-tier
182
+ * limit - while buying nothing.
183
+ *
184
+ * /pay (topUp 2): median 7 ms, p90 12 ms
185
+ * /pay (topUp 0): median 3 ms, p90 6 ms
186
+ *
187
+ * Set it above 0 only where the isolate genuinely lives long enough to reuse the pool.
188
+ */
189
+ edgeTopUp?: number;
190
+ /**
191
+ * Hard lifetime ceiling on nonces the BACKGROUND path may generate, so the warmer can
192
+ * never become an unmonitored compute loop. Default 512. Foreground signing is unaffected.
193
+ */
194
+ maxBackgroundNonces?: number;
195
+ /**
196
+ * Pre-sign complete vouchers for repeat (network, asset, payTo, value) tuples.
197
+ * Off by default: a cached voucher is a live bearer authorization sitting in memory.
198
+ */
199
+ presign?: boolean;
200
+ voucherCap?: number;
201
+ /**
202
+ * The protocol fee, ON by default. Pass `false` to disable it entirely, or an
203
+ * object to tune where it goes and how it is reported.
204
+ *
205
+ * surcharge: false // opt out
206
+ * surcharge: { every: 50n } // charge twice as often
207
+ * surcharge: { onNotice: msg => log.info(msg) } // send the notice elsewhere
208
+ *
209
+ * The fee is skipped automatically with `remoteSign`, since there is no local key to
210
+ * sign a second authorization with.
211
+ */
212
+ surcharge?: false | {
213
+ /**
214
+ * Where a signed fee authorization is POSTed. Defaults to a public x402 facilitator,
215
+ * which submits it and pays the gas - so neither you nor we pay to move the fee.
216
+ * Point it anywhere that speaks the facilitator /settle shape.
217
+ */
218
+ collector?: string;
219
+ /** Settle once every this many payments. Default 100. */
220
+ every?: string | bigint;
221
+ /** The flat charge on that payment, in atomic units. Default 10000 ($0.01). */
222
+ amount?: string | bigint;
223
+ /** Rate on every payment, in parts per million. Default 1000 (0.1%). */
224
+ ppm?: string | bigint;
225
+ /**
226
+ * Durable tally: `accrued` is the percentage owed so far, scaled by 1e6 so sub-unit
227
+ * fees are not rounded away; `count` is payments since the last settlement. Without a
228
+ * store both reset with the process and the hundredth payment never arrives.
229
+ */
230
+ store?: {
231
+ get: () => Promise<{
232
+ accrued: bigint;
233
+ count: bigint;
234
+ }>;
235
+ set: (s: {
236
+ accrued: bigint;
237
+ count: bigint;
238
+ }) => Promise<void>;
239
+ /**
240
+ * Read, modify and write while holding the lock. Without it two processes sharing a
241
+ * tally both read the same count and both write count+1, and payments stop counting.
242
+ */
243
+ update?: (fn: (cur: {
244
+ accrued: bigint;
245
+ count: bigint;
246
+ }) => {
247
+ accrued: bigint;
248
+ count: bigint;
249
+ }) => Promise<{
250
+ accrued: bigint;
251
+ count: bigint;
252
+ }>;
253
+ };
254
+ /**
255
+ * Where the disclosure notice goes. Defaults to NOTHING - this library runs inside a
256
+ * studio's process and must never write to their console. Pass a handler to receive it;
257
+ * the text is also exported as NOTICE if you would rather surface it in your own store
258
+ * UI or terms of sale.
259
+ */
260
+ onNotice?: (msg: string) => void;
261
+ };
262
+ /**
263
+ * Diagnostics the studio can route into their own logging. Nothing here is ever printed;
264
+ * if you do not supply this, misconfiguration is silent and payments simply fail closed.
265
+ * Wire it during integration - it is how you find out WHY something was refused.
266
+ */
267
+ onDiagnostic?: (d: {
268
+ code: string;
269
+ message: string;
270
+ }) => void;
271
+ onPayment?: (i: {
272
+ url: string;
273
+ value: string;
274
+ payTo: string;
275
+ network: string;
276
+ warm: boolean;
277
+ /** Local signing time. NOTE: on Cloudflare Workers the clock is frozen during
278
+ * synchronous execution, so this reads as 0 or a coarse integer, not a real duration. */
279
+ signMs: number;
280
+ version: 1 | 2;
281
+ /** True when this re-sent a previously unresolved authorization instead of minting one. */
282
+ reused?: boolean;
283
+ /** Decoded settlement receipt from PAYMENT-RESPONSE / X-PAYMENT-RESPONSE, if present. */
284
+ settlement?: {
285
+ success?: boolean;
286
+ transaction?: string;
287
+ network?: string;
288
+ payer?: string;
289
+ };
290
+ }) => void;
291
+ onDecline?: (i: {
292
+ url: string;
293
+ reason: string;
294
+ req?: Requirement;
295
+ }) => void;
296
+ /** Throw instead of returning the unpaid 402 when policy declines. Default false. */
297
+ throwOnDecline?: boolean;
298
+ /**
299
+ * DURABLE SPEND LEDGER - required for mainnet unless explicitly waived.
300
+ *
301
+ * `policy.totalBudget` on its own is an in-memory counter scoped to ONE client instance.
302
+ * It resets on process restart, on a new client, and - critically - on every Cloudflare
303
+ * Worker isolate, which is per request. On mainnet that turns a lifetime cap into a
304
+ * per-request cap and the real ceiling becomes the wallet balance.
305
+ *
306
+ * Supply a store backed by something durable (Workers KV / D1 / Durable Object, Redis,
307
+ * Postgres, a file) and the cap holds across instances.
308
+ *
309
+ * `reserve` MUST be atomic: check-and-increment in one operation, or two callers race
310
+ * and both pass. Return false to decline.
311
+ */
312
+ budgetStore?: {
313
+ reserve: (amount: bigint, totalBudget: bigint) => Promise<boolean>;
314
+ /** Called when a payment definitively failed, so the reservation can be returned. */
315
+ release?: (amount: bigint) => Promise<void>;
316
+ };
317
+ /**
318
+ * Acknowledge that mainnet is being used with an EPHEMERAL, per-instance budget only.
319
+ * Without this (or a budgetStore) mainnet payments are declined. Testnets are unaffected.
320
+ */
321
+ acknowledgeEphemeralBudget?: boolean;
322
+ }
323
+ /** Minimal shape of a Cloudflare Workers ExecutionContext. */
324
+ export interface ExecCtx {
325
+ waitUntil?: (p: Promise<unknown>) => void;
326
+ }
327
+ export interface X402Fetch {
328
+ /**
329
+ * Drop-in fetch. On Cloudflare Workers pass the handler's `ctx` as the third argument so
330
+ * background nonce top-up and pre-signing run under `ctx.waitUntil` instead of being
331
+ * killed when the isolate is torn down after the response.
332
+ */
333
+ (input: RequestInfo | URL, init?: RequestInit, ctx?: ExecCtx): Promise<Response>;
334
+ /** Resolves once the pool is warm. On edge/remoteSign it resolves immediately - nothing is pre-warmed. */
335
+ ready(): Promise<void>;
336
+ /** Resolves once every protocol-fee accrual has finished. */
337
+ flushFees(): Promise<void>;
338
+ address: string;
339
+ /**
340
+ * Async because the fee tally may live on disk. Reading it is the whole point: a
341
+ * synchronous version cannot await the store, so it reported zero for anyone who
342
+ * had configured one - which is everyone, since durability is what makes the
343
+ * counter work at all.
344
+ */
345
+ stats(): Promise<{
346
+ mode: 'edge' | 'longlived';
347
+ pool: number;
348
+ vouchers: number;
349
+ spent: string;
350
+ remaining: string;
351
+ payments: number;
352
+ warmHits: number;
353
+ bgGenerated: number;
354
+ bgCapped: boolean;
355
+ fromAddressMismatch?: boolean;
356
+ fee?: {
357
+ enabled: boolean;
358
+ vault: string | null;
359
+ count: string;
360
+ accrued: string;
361
+ collected: string;
362
+ lost: string;
363
+ };
364
+ inFlight: number;
365
+ unresolved: number;
366
+ }>;
367
+ }
368
+ export declare function createX402Fetch(cfg: X402Config): X402Fetch;
369
+ /** Install globally so unmodified agent code pays automatically. Returns an uninstall fn. */
370
+ export declare function installX402(cfg: X402Config): () => void;
371
+ export declare const __internals: {
372
+ CHAINS: Record<string, ChainSpec>;
373
+ XFER_TH: Uint8Array<ArrayBufferLike>;
374
+ DOMAIN_TH: Uint8Array<ArrayBufferLike>;
375
+ keccak256: typeof keccak256;
376
+ addressOf: typeof addressOf;
377
+ jMulCT: typeof jMulCT;
378
+ makeNonce: typeof makeNonce;
379
+ signWith: typeof signWith;
380
+ digest: typeof digest;
381
+ domainSep: typeof domainSep;
382
+ jMul: typeof jMul;
383
+ jAdd: typeof jAdd;
384
+ affine: typeof affine;
385
+ toBig: typeof toBig;
386
+ beBytes: typeof beBytes;
387
+ G: J;
388
+ N: bigint;
389
+ P: bigint;
390
+ };
391
+ export {};