uvd-x402-sdk 2.104.3 → 2.105.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/README.md CHANGED
@@ -23,6 +23,7 @@ Users sign a message or transaction, and the Ultravioleta facilitator handles on
23
23
  - **Escrow & Refunds**: Hold payments with dispute resolution
24
24
  - **Advanced Escrow**: Full escrow lifecycle (authorize, release, refund, charge) with SigningWalletAdapter support
25
25
  - **Escrow Pre-Auth**: Sign-on-assignment `X-Payment-Auth` builder (`buildEscrowPreAuth`) — vector-pinned parity with the Python SDK and Execution Market
26
+ - **Batch Settlement (buyer)**: `uvd-x402-sdk/batch-settlement` — one ERC-3009 deposit opens a channel, each later request pays with a cumulative voucher; capped deposits, vector-pinned parity with the Python SDK
26
27
  - **Commerce Scheme**: `'commerce'` scheme alias for marketplace integrations (identical to `'escrow'` on-chain)
27
28
  - **`/accepts` Negotiation**: Discover facilitator capabilities before constructing payments
28
29
  - **Bazaar Discovery**: Register and discover paid resources across the x402 network
@@ -686,6 +687,26 @@ const paymentAuth = await buildEscrowPreAuth(new EnvKeyAdapter(), {
686
687
 
687
688
  Any `SigningWalletAdapter` works as the signer (only `signTypedData` is used); browser wallets can pass a minimal `{ signTypedData }` wrapper. Validation fails loud instead of falling back: incomplete network config, unknown tier, bounty outside the on-chain deposit limit ($100), or a `maxFeeBps` below the operator's 1300 bps all throw before anything is signed. What the config asserts is also checked against the SDK's own tables before signing, as in the Python SDK: the `chain_id` against the `network` you name (a registry name or `eip155:<id>`), the escrow, token collector and USDC against `ESCROW_CONTRACTS`, the operator against `ESCROW_OPERATORS`, `maxFeeBps` against the chain's ceiling in `ESCROW_MAX_FEE_BPS`, and the USDC EIP-712 domain (`usdc_domain_name` / `usdc_domain_version`) against the token's on-chain domain in `VERIFIED_USDC_DOMAINS`. A contradiction throws `INVALID_CONFIG`, and so do a `network` the SDK does not register and a chain without a row in each of the four tables, with or without `network`; name the network you expect to pay on, from your own code, never one read from the config. A server-published tier window must be an integer from 0 to its `ESCROW_TIER_WINDOW_MAX`, and `authorizationExpiry` at most `ESCROW_MAX_AUTHORIZATION_SEC` after now: past either, the SDK refuses, it never clamps. `computeEscrowNonce` is exported to reproduce `AuthCaptureEscrow.getHash` externally.
688
689
 
690
+ ## Batch Settlement (buyer)
691
+
692
+ The buyer side of the x402 `batch-settlement` scheme on EVM ([spec](https://github.com/coinbase/x402/tree/main/specs/schemes/batch-settlement)): the first paid request signs an ERC-3009 deposit into a payment channel plus a voucher, and every later request on that channel signs only the next cumulative voucher. Same API and the same golden vectors as the Python SDK's `uvd_x402_sdk.batch_settlement`.
693
+
694
+ ```typescript
695
+ import { EnvKeyAdapter } from 'uvd-x402-sdk';
696
+ import { BatchSettlementClient, FileChannelStorage } from 'uvd-x402-sdk/batch-settlement';
697
+
698
+ const client = new BatchSettlementClient(new EnvKeyAdapter(), {
699
+ storage: new FileChannelStorage('./channels.json'), // Node; MemoryChannelStorage by default
700
+ maxDeposit: 1_000_000n, // the default: 1 USDC in atomic units
701
+ });
702
+
703
+ const prepared = await client.createPayment(accepted, { url }); // accepted = the 402's batch-settlement accept
704
+ const res = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': prepared.header() } });
705
+ if (res.ok) await client.confirm(prepared, settleResponse.extra); // from the PAYMENT-RESPONSE header
706
+ ```
707
+
708
+ A deposit is never above `maxDeposit`: the seller's `extra.minDeposit` is a target, and a deposit the next voucher needs above the cap throws `BatchSettlementError` with nothing signed. Only `eip155:*` networks and the ERC-3009 deposit are signed (`assetTransferMethod: 'permit2'` throws). `confirm` records previous cumulative + `chargedAmount` and never copies the seller's `channelState`; a mismatch leaves the local state untouched and resolves to `false`. If the payer may be an EIP-7702 delegated account (a Paybox wallet), pass `delegationResolver` (e.g. `rpcDelegationResolver(rpcUrl)`): the deposit authorization is then wrapped for ERC-1271 when the delegate needs it, and an unreadable chain refuses instead of signing plain. Not included: Permit2 deposits, the cooperative refund and the corrective-402 resync.
709
+
689
710
  ## Multi-Stablecoin (EVM)
690
711
 
691
712
  ```typescript
@@ -0,0 +1,322 @@
1
+ import { D as DelegationResolver } from '../erc7702-CC-UIL7E.mjs';
2
+
3
+ /**
4
+ * uvd-x402-sdk/batch-settlement - x402 `batch-settlement` on EVM, BUYER side.
5
+ *
6
+ * The scheme (coinbase/x402, `specs/schemes/batch-settlement/scheme_batch_settlement_evm.md`)
7
+ * lets a buyer open a payment channel once and pay each later request with an
8
+ * off-chain voucher. This module is the part a buyer needs, and nothing of the
9
+ * seller or the facilitator:
10
+ *
11
+ * - {@link computeChannelId} - the chain-bound EIP-712 hash of the channel config.
12
+ * - {@link signVoucher} - the cumulative `Voucher` typed data.
13
+ * - {@link buildDepositPayload} - ERC-3009 `ReceiveWithAuthorization` to the
14
+ * deposit collector + a voucher.
15
+ * - {@link BatchSettlementClient} - decides deposit vs voucher, keeps the channel
16
+ * state, and updates it from the seller's response the way the spec says (from
17
+ * its own previous state plus `chargedAmount`, never by copying the seller's
18
+ * `channelState`).
19
+ *
20
+ * It signs through anything with `signTypedData(typedDataJson)` - the SDK's
21
+ * `SigningWalletAdapter` (`EnvKeyAdapter`, `OWSWalletAdapter`) or a wrapper over
22
+ * a remote wallet - so the key never has to be loaded here.
23
+ *
24
+ * WHAT IS NOT HERE, on purpose: the Permit2 deposit path
25
+ * (`assetTransferMethod: "permit2"` throws {@link BatchSettlementError}, it is
26
+ * never guessed), the cooperative refund, and the corrective-402 resync. USDC
27
+ * settles through ERC-3009, which is the default the spec names.
28
+ *
29
+ * MONEY RULES this module enforces, because a 402 is somebody else's input:
30
+ *
31
+ * - A deposit is capped by `maxDeposit` (default 1 USDC in atomic units of the
32
+ * asset, i.e. `1_000_000` for a 6-decimals token). A seller whose `minDeposit`
33
+ * is above the cap is capped, not obeyed; a deposit the next voucher NEEDS above
34
+ * the cap is refused.
35
+ * - The seller's `extra.minDeposit` is only a target; the amount is never below
36
+ * what the next voucher needs.
37
+ * - Only `eip155:*` networks and only `scheme === "batch-settlement"` are signed.
38
+ *
39
+ * Port of `uvd_x402_sdk.batch_settlement` (Python). Keep the two in step: the
40
+ * golden vectors of `src/batch-settlement.test.ts` are the Python suite's, and a
41
+ * divergence here is a channel the seller cannot open or a voucher it rejects.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { EnvKeyAdapter } from 'uvd-x402-sdk';
46
+ * import { BatchSettlementClient, FileChannelStorage } from 'uvd-x402-sdk/batch-settlement';
47
+ *
48
+ * const client = new BatchSettlementClient(new EnvKeyAdapter(), {
49
+ * storage: new FileChannelStorage('./channels.json'),
50
+ * });
51
+ * const prepared = await client.createPayment(accepted, { url });
52
+ * const res = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': prepared.header() } });
53
+ * if (res.ok) await client.confirm(prepared, settleResponse.extra);
54
+ * ```
55
+ */
56
+
57
+ declare const SCHEME = "batch-settlement";
58
+ /** Deterministic CREATE2 addresses, the same on every supported EVM chain (spec, "Contract addresses"). */
59
+ declare const BATCH_SETTLEMENT_ADDRESS = "0x4020074e9dF2ce1deE5A9C1b5c3f541D02a10003";
60
+ declare const ERC3009_DEPOSIT_COLLECTOR_ADDRESS = "0x4020806089470a89826cB9fB1f4059150b550004";
61
+ declare const PERMIT2_DEPOSIT_COLLECTOR_ADDRESS = "0x4020425FAf3B746C082C2f942b4E5159887B0005";
62
+ declare const BATCH_SETTLEMENT_DOMAIN_NAME = "x402 Batch Settlement";
63
+ declare const BATCH_SETTLEMENT_DOMAIN_VERSION = "1";
64
+ declare const MIN_WITHDRAW_DELAY = 900;
65
+ declare const MAX_WITHDRAW_DELAY = 2592000;
66
+ declare const ZERO_ADDRESS: string;
67
+ declare const ZERO_BYTES32: string;
68
+ /** Default ceiling for ONE deposit, in atomic units. A 402 must not be able to lock unbounded escrow (spec). */
69
+ declare const DEFAULT_MAX_DEPOSIT = 1000000n;
70
+ /** Used when the seller announces no `minDeposit`: the deposit target is this many requests. */
71
+ declare const DEFAULT_DEPOSIT_MULTIPLIER = 5n;
72
+ type TypedFields = Record<string, Array<{
73
+ name: string;
74
+ type: string;
75
+ }>>;
76
+ declare const CHANNEL_CONFIG_TYPES: TypedFields;
77
+ declare const VOUCHER_TYPES: TypedFields;
78
+ declare const RECEIVE_AUTHORIZATION_TYPES: TypedFields;
79
+ /** The 402 or the channel state cannot be signed as asked. Never retried blindly. */
80
+ declare class BatchSettlementError extends Error {
81
+ constructor(message: string);
82
+ }
83
+ /**
84
+ * What this module signs with: EIP-712 over the full typed-data JSON string
85
+ * (`domain`, `types`, `primaryType`, `message`), the contract of
86
+ * `SigningWalletAdapter.signTypedData`, so `EnvKeyAdapter` and
87
+ * `OWSWalletAdapter` satisfy it structurally. The address comes from
88
+ * `getAddress()` or, failing that, `address`.
89
+ */
90
+ interface BatchSettlementSigner {
91
+ signTypedData(typedData: string): Promise<{
92
+ signature: string;
93
+ } | string>;
94
+ getAddress?: () => string;
95
+ address?: string;
96
+ }
97
+ /** The fields of a `batch-settlement` accept this module reads. Other fields travel untouched in `accepted`. */
98
+ interface BatchSettlementRequirements {
99
+ scheme: string;
100
+ network: string;
101
+ amount: string | number | bigint;
102
+ asset: string;
103
+ payTo: string;
104
+ maxTimeoutSeconds?: number | string;
105
+ extra?: unknown;
106
+ }
107
+ interface ChannelConfigFields {
108
+ payer: string;
109
+ payerAuthorizer: string;
110
+ receiver: string;
111
+ receiverAuthorizer: string;
112
+ token: string;
113
+ withdrawDelay: number;
114
+ salt?: string;
115
+ }
116
+ /** `channelConfig` as it travels in the payload: checksummed addresses, lowercase salt. */
117
+ interface ChannelConfigJSON {
118
+ payer: string;
119
+ payerAuthorizer: string;
120
+ receiver: string;
121
+ receiverAuthorizer: string;
122
+ token: string;
123
+ withdrawDelay: number;
124
+ salt: string;
125
+ }
126
+ interface SignedVoucher {
127
+ channelId: string;
128
+ maxClaimableAmount: string;
129
+ signature: string;
130
+ }
131
+ interface DepositPayload {
132
+ type: 'deposit';
133
+ channelConfig: ChannelConfigJSON;
134
+ voucher: SignedVoucher;
135
+ deposit: {
136
+ amount: string;
137
+ authorization: {
138
+ erc3009Authorization: {
139
+ validAfter: string;
140
+ validBefore: string;
141
+ salt: string;
142
+ signature: string;
143
+ };
144
+ };
145
+ };
146
+ }
147
+ interface VoucherPayload {
148
+ type: 'voucher';
149
+ channelConfig: ChannelConfigJSON;
150
+ voucher: SignedVoucher;
151
+ }
152
+ type BatchSettlementInnerPayload = DepositPayload | VoucherPayload;
153
+ /** The x402 v2 `PaymentPayload` envelope. */
154
+ interface BatchSettlementPaymentPayload {
155
+ x402Version: 2;
156
+ resource?: Record<string, unknown>;
157
+ accepted: BatchSettlementRequirements;
158
+ payload: BatchSettlementInnerPayload;
159
+ }
160
+ /**
161
+ * The immutable channel identity. `channelId = EIP712Hash(ChannelConfig)`.
162
+ *
163
+ * Addresses are kept as given and checksummed when the config is hashed or
164
+ * serialized, as the Python SDK's frozen dataclass does.
165
+ */
166
+ declare class ChannelConfig {
167
+ readonly payer: string;
168
+ readonly payerAuthorizer: string;
169
+ readonly receiver: string;
170
+ readonly receiverAuthorizer: string;
171
+ readonly token: string;
172
+ readonly withdrawDelay: number;
173
+ readonly salt: string;
174
+ constructor(fields: ChannelConfigFields);
175
+ /** The EIP-712 message and the payload's `channelConfig`: one shape for both. */
176
+ toDict(): ChannelConfigJSON;
177
+ toJSON(): ChannelConfigJSON;
178
+ }
179
+ declare function batchSettlementDomain(chainId: number): {
180
+ name: string;
181
+ version: string;
182
+ chainId: number;
183
+ verifyingContract: string;
184
+ };
185
+ /** The chain-bound channel id (`0x` + 32 bytes, lowercase): keccak(0x1901 || domainSeparator || structHash). */
186
+ declare function computeChannelId(config: ChannelConfig | ChannelConfigFields, network: string): string;
187
+ /** The ERC-3009 nonce the deposit collector expects: `keccak256(abi.encode(bytes32 channelId, uint256 salt))`. */
188
+ declare function erc3009DepositNonce(channelId: string, salt: string | bigint): string;
189
+ /** Sign a cumulative `Voucher`: the seller may claim up to `maxClaimableAmount` from the channel. */
190
+ declare function signVoucher(wallet: BatchSettlementSigner, channelId: string, maxClaimableAmount: bigint | number | string, network: string): Promise<SignedVoucher>;
191
+ interface BuildDepositOptions {
192
+ /** Signs the voucher instead of `wallet`; its address must be the config's `payerAuthorizer`. */
193
+ voucherWallet?: BatchSettlementSigner;
194
+ /** The deposit's ERC-3009 salt (32-byte hex). Random when omitted. */
195
+ depositSalt?: string;
196
+ /** Unix seconds; `validBefore = now + maxTimeoutSeconds`. The clock when omitted. */
197
+ now?: number;
198
+ /**
199
+ * `(address, network) -> target | true | false | null`: pass it when the payer
200
+ * may be an EIP-7702 delegated account (a Paybox wallet), so the deposit
201
+ * authorization is wrapped for ERC-1271 when needed. See
202
+ * {@link signDepositAuthorization}.
203
+ */
204
+ delegationResolver?: DelegationResolver;
205
+ }
206
+ /** The inner `payload` of a `deposit`: an ERC-3009 authorization to the collector plus the first voucher. */
207
+ declare function buildDepositPayload(wallet: BatchSettlementSigner, requirements: BatchSettlementRequirements, config: ChannelConfig | ChannelConfigFields, depositAmount: bigint | number | string, maxClaimableAmount: bigint | number | string, options?: BuildDepositOptions): Promise<DepositPayload>;
208
+ /** The inner `payload` of a `voucher`: the next cumulative voucher on an open channel. */
209
+ declare function buildVoucherPayload(wallet: BatchSettlementSigner, config: ChannelConfig | ChannelConfigFields, network: string, maxClaimableAmount: bigint | number | string): Promise<VoucherPayload>;
210
+ /** The x402 v2 `PaymentPayload` envelope around an inner deposit/voucher payload. */
211
+ declare function paymentPayload(requirements: BatchSettlementRequirements, inner: BatchSettlementInnerPayload, resource?: Record<string, unknown>): BatchSettlementPaymentPayload;
212
+ /**
213
+ * The base64 value for the `PAYMENT-SIGNATURE` request header.
214
+ *
215
+ * Byte for byte the Python SDK's `payment_header`: compact JSON with every
216
+ * character above `~` written as a JSON escape (Python's `json.dumps` with
217
+ * `ensure_ascii`), so a token named `USD₮0` gives the same header in both SDKs.
218
+ * The decoded JSON is the same value either way.
219
+ */
220
+ declare function paymentHeader(payload: BatchSettlementPaymentPayload): string;
221
+ /** What the buyer keeps per channel: decimal strings, `balance` and `chargedCumulativeAmount`. */
222
+ type ChannelState = Record<string, string>;
223
+ /**
224
+ * Where the buyer keeps its channels. A Fargate task dies between beats: persist
225
+ * somewhere that lives on. Both methods may return a promise (an S3 or database
226
+ * store); the client awaits them.
227
+ */
228
+ interface ChannelStorage {
229
+ get(channelId: string): ChannelState | null | undefined | Promise<ChannelState | null | undefined>;
230
+ set(channelId: string, state: ChannelState): void | Promise<void>;
231
+ }
232
+ declare class MemoryChannelStorage implements ChannelStorage {
233
+ private readonly data;
234
+ get(channelId: string): ChannelState | undefined;
235
+ set(channelId: string, state: ChannelState): void;
236
+ }
237
+ /**
238
+ * One JSON file for all channels, replaced atomically: written to a temporary
239
+ * file beside it and renamed over it (a half-written file would lose the
240
+ * cumulative). Node only; `node:fs` is loaded on its first use, not when this
241
+ * module is imported, so {@link MemoryChannelStorage} works without it.
242
+ */
243
+ declare class FileChannelStorage implements ChannelStorage {
244
+ readonly path: string;
245
+ constructor(path: string);
246
+ private read;
247
+ get(channelId: string): Promise<ChannelState | undefined>;
248
+ set(channelId: string, state: ChannelState): Promise<void>;
249
+ }
250
+ /** What {@link BatchSettlementClient.createPayment} returns: the payload, and what to confirm afterwards. */
251
+ declare class PreparedPayment {
252
+ readonly payload: BatchSettlementPaymentPayload;
253
+ readonly channelId: string;
254
+ readonly requestAmount: bigint;
255
+ readonly depositAmount: bigint | undefined;
256
+ constructor(fields: {
257
+ payload: BatchSettlementPaymentPayload;
258
+ channelId: string;
259
+ requestAmount: bigint;
260
+ depositAmount?: bigint;
261
+ });
262
+ header(): string;
263
+ }
264
+ interface BatchSettlementClientOptions {
265
+ /** Default: a {@link MemoryChannelStorage}, which dies with the process. */
266
+ storage?: ChannelStorage;
267
+ /** The channel config's `salt`. Default {@link ZERO_BYTES32}. */
268
+ salt?: string;
269
+ /** A second key that signs vouchers; its address becomes the channel's `payerAuthorizer`. */
270
+ voucherWallet?: BatchSettlementSigner;
271
+ /** Ceiling for one deposit, in atomic units. Default {@link DEFAULT_MAX_DEPOSIT}. */
272
+ maxDeposit?: bigint | number | string;
273
+ /** Deposit target in requests when the seller announces no `minDeposit`. Default {@link DEFAULT_DEPOSIT_MULTIPLIER}. */
274
+ depositMultiplier?: bigint | number | string;
275
+ /**
276
+ * `(address, network) -> target | true | false | null`: pass it when the payer
277
+ * may be an EIP-7702 delegated account (a Paybox wallet), so the deposit
278
+ * authorization is wrapped for ERC-1271 when needed. Vouchers are never wrapped.
279
+ */
280
+ delegationResolver?: DelegationResolver;
281
+ }
282
+ /** The response fields {@link BatchSettlementClient.confirm} reads: the settle response's `extra`. */
283
+ interface BatchSettlementResponseExtra {
284
+ chargedAmount?: unknown;
285
+ channelState?: unknown;
286
+ [key: string]: unknown;
287
+ }
288
+ /**
289
+ * Signs `batch-settlement` payments for one wallet and keeps its channels' state.
290
+ *
291
+ * `wallet` is anything with `getAddress()` (or `address`) and
292
+ * `signTypedData(typedDataJson)`. `voucherWallet` is an optional second key that
293
+ * signs vouchers; its address becomes the channel's `payerAuthorizer` so the
294
+ * seller can verify vouchers without an RPC call.
295
+ */
296
+ declare class BatchSettlementClient {
297
+ readonly wallet: BatchSettlementSigner;
298
+ readonly storage: ChannelStorage;
299
+ readonly salt: string;
300
+ readonly voucherWallet: BatchSettlementSigner | undefined;
301
+ readonly maxDeposit: bigint;
302
+ readonly depositMultiplier: bigint;
303
+ readonly delegationResolver: DelegationResolver | undefined;
304
+ constructor(wallet: BatchSettlementSigner, options?: BatchSettlementClientOptions);
305
+ channelConfig(requirements: BatchSettlementRequirements): ChannelConfig;
306
+ private depositAmountFor;
307
+ /** Sign a deposit (first request or top-up) or a voucher for one paid request. */
308
+ createPayment(requirements: BatchSettlementRequirements, resource?: Record<string, unknown>, options?: {
309
+ now?: number;
310
+ }): Promise<PreparedPayment>;
311
+ /**
312
+ * Record a SUCCESSFUL paid response. The new cumulative is the previous one plus `chargedAmount`.
313
+ *
314
+ * The seller's `extra.channelState` is untrusted and never copied: it is only
315
+ * compared with the number the buyer computed itself, and a mismatch leaves the
316
+ * local state untouched (the next request would then get a corrective 402).
317
+ * Resolves to whether the state was written.
318
+ */
319
+ confirm(prepared: PreparedPayment, responseExtra?: BatchSettlementResponseExtra | null): Promise<boolean>;
320
+ }
321
+
322
+ export { BATCH_SETTLEMENT_ADDRESS, BATCH_SETTLEMENT_DOMAIN_NAME, BATCH_SETTLEMENT_DOMAIN_VERSION, BatchSettlementClient, type BatchSettlementClientOptions, BatchSettlementError, type BatchSettlementInnerPayload, type BatchSettlementPaymentPayload, type BatchSettlementRequirements, type BatchSettlementResponseExtra, type BatchSettlementSigner, type BuildDepositOptions, CHANNEL_CONFIG_TYPES, ChannelConfig, type ChannelConfigFields, type ChannelConfigJSON, type ChannelState, type ChannelStorage, DEFAULT_DEPOSIT_MULTIPLIER, DEFAULT_MAX_DEPOSIT, type DepositPayload, ERC3009_DEPOSIT_COLLECTOR_ADDRESS, FileChannelStorage, MAX_WITHDRAW_DELAY, MIN_WITHDRAW_DELAY, MemoryChannelStorage, PERMIT2_DEPOSIT_COLLECTOR_ADDRESS, PreparedPayment, RECEIVE_AUTHORIZATION_TYPES, SCHEME, type SignedVoucher, VOUCHER_TYPES, type VoucherPayload, ZERO_ADDRESS, ZERO_BYTES32, batchSettlementDomain, buildDepositPayload, buildVoucherPayload, computeChannelId, erc3009DepositNonce, paymentHeader, paymentPayload, signVoucher };