uvd-x402-sdk 2.104.2 → 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 +21 -0
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +135 -5
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +135 -5
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs.map +1 -1
- package/dist/batch-settlement/index.d.mts +322 -0
- package/dist/batch-settlement/index.d.ts +322 -0
- package/dist/batch-settlement/index.js +585 -0
- package/dist/batch-settlement/index.js.map +1 -0
- package/dist/batch-settlement/index.mjs +555 -0
- package/dist/batch-settlement/index.mjs.map +1 -0
- package/dist/erc7702-CC-UIL7E.d.mts +158 -0
- package/dist/erc7702-CC-UIL7E.d.ts +158 -0
- package/dist/index.d.mts +4 -159
- package/dist/index.d.ts +4 -159
- package/dist/index.js +135 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{ows-BB6Sgj0W.d.ts → ows-CW-g7qGw.d.ts} +9 -2
- package/dist/{ows-DazT4KQH.d.mts → ows-DJu424iz.d.mts} +9 -2
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/providers/sui/index.js.map +1 -1
- package/dist/providers/sui/index.mjs.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs.map +1 -1
- package/package.json +9 -1
- package/src/adapters/env-key.ts +9 -2
- package/src/adapters/ows.ts +12 -3
- package/src/batch-settlement.ts +882 -0
- package/src/utils/uint.ts +211 -3
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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { X as X402Version, a as PaymentResult } from '../index-DARXHuQl.mjs';
|
|
2
|
-
export { E as EnvKeyAdapter, O as OWSAccountInfo, a as OWSCore, b as OWSSignResult, c as OWSWallet, d as OWSWalletAdapter, e as OWSWalletAdapterOptions } from '../ows-
|
|
2
|
+
export { E as EnvKeyAdapter, O as OWSAccountInfo, a as OWSCore, b as OWSSignResult, c as OWSWallet, d as OWSWalletAdapter, e as OWSWalletAdapterOptions } from '../ows-DJu424iz.mjs';
|
|
3
3
|
import '../wallet-T0NFUUkT.mjs';
|
|
4
4
|
import 'ethers';
|
|
5
5
|
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { X as X402Version, a as PaymentResult } from '../index-DARXHuQl.js';
|
|
2
|
-
export { E as EnvKeyAdapter, O as OWSAccountInfo, a as OWSCore, b as OWSSignResult, c as OWSWallet, d as OWSWalletAdapter, e as OWSWalletAdapterOptions } from '../ows-
|
|
2
|
+
export { E as EnvKeyAdapter, O as OWSAccountInfo, a as OWSCore, b as OWSSignResult, c as OWSWallet, d as OWSWalletAdapter, e as OWSWalletAdapterOptions } from '../ows-CW-g7qGw.js';
|
|
3
3
|
import '../wallet-T0NFUUkT.js';
|
|
4
4
|
import 'ethers';
|
|
5
5
|
|
package/dist/adapters/index.js
CHANGED
|
@@ -1545,8 +1545,124 @@ function assertTypedDataIntegersExact(doc, where = "typed data", code = "INVALID
|
|
|
1545
1545
|
}
|
|
1546
1546
|
assertNoInexactNumber(message, `${where} message`, code);
|
|
1547
1547
|
}
|
|
1548
|
+
var MIN_EXACT_DIGITS = 22;
|
|
1549
|
+
var MAX_EXACT_DIGITS = 78;
|
|
1550
|
+
var MAX_DOUBLE_DIGITS = 17;
|
|
1551
|
+
function doubleShaped(literal) {
|
|
1552
|
+
const significant = literal.replace(/^-/, "").replace(/0+$/, "");
|
|
1553
|
+
return significant.length <= MAX_DOUBLE_DIGITS || BigInt(literal) === BigInt(Number(literal));
|
|
1554
|
+
}
|
|
1555
|
+
var JSON_NUMBER = /-?(?:0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/y;
|
|
1556
|
+
var JSON_SPACE = /[ \t\n\r]*/y;
|
|
1557
|
+
function dataDescriptor(value) {
|
|
1558
|
+
const descriptor = /* @__PURE__ */ Object.create(null);
|
|
1559
|
+
descriptor.value = value;
|
|
1560
|
+
descriptor.writable = true;
|
|
1561
|
+
descriptor.enumerable = true;
|
|
1562
|
+
descriptor.configurable = true;
|
|
1563
|
+
return descriptor;
|
|
1564
|
+
}
|
|
1565
|
+
function parseJsonExact(input) {
|
|
1566
|
+
const text = String(input);
|
|
1567
|
+
let pos = 0;
|
|
1568
|
+
let wide = false;
|
|
1569
|
+
const fail = (what) => {
|
|
1570
|
+
throw new SyntaxError(`${what} in JSON at position ${pos}`);
|
|
1571
|
+
};
|
|
1572
|
+
const space = () => {
|
|
1573
|
+
JSON_SPACE.lastIndex = pos;
|
|
1574
|
+
if (JSON_SPACE.exec(text)) pos = JSON_SPACE.lastIndex;
|
|
1575
|
+
};
|
|
1576
|
+
const string = () => {
|
|
1577
|
+
if (text[pos] !== '"') fail("Expected a string");
|
|
1578
|
+
let end = pos + 1;
|
|
1579
|
+
while (end < text.length && text[end] !== '"') end += text[end] === "\\" ? 2 : 1;
|
|
1580
|
+
if (end >= text.length) fail("Unterminated string");
|
|
1581
|
+
const value = JSON.parse(text.slice(pos, end + 1));
|
|
1582
|
+
pos = end + 1;
|
|
1583
|
+
return value;
|
|
1584
|
+
};
|
|
1585
|
+
const key = () => {
|
|
1586
|
+
const name = string();
|
|
1587
|
+
space();
|
|
1588
|
+
if (text[pos] !== ":") fail("Expected ':'");
|
|
1589
|
+
pos += 1;
|
|
1590
|
+
space();
|
|
1591
|
+
return name;
|
|
1592
|
+
};
|
|
1593
|
+
const scalar = () => {
|
|
1594
|
+
if (text[pos] === '"') return string();
|
|
1595
|
+
for (const [word, value] of [["true", true], ["false", false], ["null", null]]) {
|
|
1596
|
+
if (text.startsWith(word, pos)) {
|
|
1597
|
+
pos += word.length;
|
|
1598
|
+
return value;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
JSON_NUMBER.lastIndex = pos;
|
|
1602
|
+
const match = JSON_NUMBER.exec(text);
|
|
1603
|
+
if (!match) return fail("Unexpected token");
|
|
1604
|
+
pos = JSON_NUMBER.lastIndex;
|
|
1605
|
+
const literal = match[0];
|
|
1606
|
+
const digits = literal.length - (literal.startsWith("-") ? 1 : 0);
|
|
1607
|
+
if (match[1] === void 0 && match[2] === void 0 && digits >= MIN_EXACT_DIGITS && digits <= MAX_EXACT_DIGITS && !doubleShaped(literal)) {
|
|
1608
|
+
wide = true;
|
|
1609
|
+
return BigInt(literal);
|
|
1610
|
+
}
|
|
1611
|
+
return Number(literal);
|
|
1612
|
+
};
|
|
1613
|
+
const stack = [];
|
|
1614
|
+
space();
|
|
1615
|
+
for (; ; ) {
|
|
1616
|
+
let value;
|
|
1617
|
+
if (text[pos] === "[") {
|
|
1618
|
+
pos += 1;
|
|
1619
|
+
space();
|
|
1620
|
+
if (text[pos] !== "]") {
|
|
1621
|
+
stack.push({ kind: "array", items: [] });
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
pos += 1;
|
|
1625
|
+
value = [];
|
|
1626
|
+
} else if (text[pos] === "{") {
|
|
1627
|
+
pos += 1;
|
|
1628
|
+
space();
|
|
1629
|
+
if (text[pos] !== "}") {
|
|
1630
|
+
stack.push({ kind: "object", object: {}, key: key() });
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
pos += 1;
|
|
1634
|
+
value = {};
|
|
1635
|
+
} else {
|
|
1636
|
+
value = scalar();
|
|
1637
|
+
}
|
|
1638
|
+
for (; ; ) {
|
|
1639
|
+
space();
|
|
1640
|
+
const top = stack[stack.length - 1];
|
|
1641
|
+
if (top === void 0) {
|
|
1642
|
+
if (pos !== text.length) fail("Unexpected non-whitespace character after JSON");
|
|
1643
|
+
return wide ? value : JSON.parse(text);
|
|
1644
|
+
}
|
|
1645
|
+
const close = top.kind === "array" ? "]" : "}";
|
|
1646
|
+
if (top.kind === "array") {
|
|
1647
|
+
top.items.push(value);
|
|
1648
|
+
} else {
|
|
1649
|
+
Object.defineProperty(top.object, top.key, dataDescriptor(value));
|
|
1650
|
+
}
|
|
1651
|
+
if (text[pos] === ",") {
|
|
1652
|
+
pos += 1;
|
|
1653
|
+
space();
|
|
1654
|
+
if (top.kind === "object") top.key = key();
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
if (text[pos] !== close) fail(`Expected ',' or '${close}'`);
|
|
1658
|
+
pos += 1;
|
|
1659
|
+
stack.pop();
|
|
1660
|
+
value = top.kind === "array" ? top.items : top.object;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1548
1664
|
function parseTypedDataJson(text, where = "typed data", code = "INVALID_CONFIG") {
|
|
1549
|
-
const doc =
|
|
1665
|
+
const doc = parseJsonExact(text);
|
|
1550
1666
|
assertTypedDataIntegersExact(doc, where, code);
|
|
1551
1667
|
return doc;
|
|
1552
1668
|
}
|
|
@@ -1589,11 +1705,18 @@ var EnvKeyAdapter = class {
|
|
|
1589
1705
|
/**
|
|
1590
1706
|
* Sign EIP-712 typed structured data.
|
|
1591
1707
|
*
|
|
1708
|
+
* An integer of 22 to 78 digits written as a JSON number (a 32-byte salt)
|
|
1709
|
+
* is read with every digit, as Python's `json` reads it, not rounded the
|
|
1710
|
+
* way `JSON.parse` rounds it.
|
|
1711
|
+
*
|
|
1592
1712
|
* @param typedData - JSON string with `domain`, `types`, `primaryType`, and `message` fields
|
|
1593
1713
|
* @returns Object with signature and v/r/s components
|
|
1594
1714
|
* @throws {X402Error} `INVALID_CONFIG` when an integer field of `message`
|
|
1595
|
-
* (or `domain.chainId`) is a JSON number above 2**53 - 1
|
|
1596
|
-
*
|
|
1715
|
+
* (or `domain.chainId`) is a JSON number above 2**53 - 1 of up to 21
|
|
1716
|
+
* digits (a JavaScript hop may have rounded it and written it back in
|
|
1717
|
+
* digits) or written with an exponent or a fraction (`7.76e+76`): it is
|
|
1718
|
+
* refused instead of signed. Write uints as strings. An integer out of
|
|
1719
|
+
* its field's range is refused by ethers (`value out-of-bounds`).
|
|
1597
1720
|
*/
|
|
1598
1721
|
async signTypedData(typedData) {
|
|
1599
1722
|
const parsed = parseTypedDataJson(typedData);
|
|
@@ -1856,7 +1979,14 @@ function prepareTypedData(input) {
|
|
|
1856
1979
|
EIP712Domain: EIP712_DOMAIN_FIELDS.filter(([name]) => domain[name] != null).map(
|
|
1857
1980
|
([name, type]) => ({ name, type })
|
|
1858
1981
|
),
|
|
1859
|
-
|
|
1982
|
+
// Each field as ethers hashes it, name and type: any other key (a wide
|
|
1983
|
+
// number in it is a bigint) never reaches OWS or JSON.stringify.
|
|
1984
|
+
...Object.fromEntries(
|
|
1985
|
+
Object.entries(structs).map(([struct, fields]) => [
|
|
1986
|
+
struct,
|
|
1987
|
+
fields.map(({ name, type }) => ({ name, type }))
|
|
1988
|
+
])
|
|
1989
|
+
)
|
|
1860
1990
|
};
|
|
1861
1991
|
const owsDomain = owsValue("EIP712Domain", domain, all, "domain");
|
|
1862
1992
|
const owsMessage = owsValue(root, message, all, "message");
|
|
@@ -1988,7 +2118,7 @@ var OWSWalletAdapter = class {
|
|
|
1988
2118
|
async signTypedData(typedData) {
|
|
1989
2119
|
let parsed;
|
|
1990
2120
|
try {
|
|
1991
|
-
parsed =
|
|
2121
|
+
parsed = parseJsonExact(typedData);
|
|
1992
2122
|
} catch {
|
|
1993
2123
|
throw refuse("typedData is not a JSON string");
|
|
1994
2124
|
}
|