uvd-x402-sdk 2.99.1 → 2.101.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.
Files changed (73) hide show
  1. package/README.md +105 -6
  2. package/dist/adapters/index.d.mts +4 -3
  3. package/dist/adapters/index.d.ts +4 -3
  4. package/dist/adapters/index.js +413 -125
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/adapters/index.mjs +413 -125
  7. package/dist/adapters/index.mjs.map +1 -1
  8. package/dist/backend/index.d.mts +4 -4
  9. package/dist/backend/index.d.ts +4 -4
  10. package/dist/backend/index.js +485 -49
  11. package/dist/backend/index.js.map +1 -1
  12. package/dist/backend/index.mjs +477 -50
  13. package/dist/backend/index.mjs.map +1 -1
  14. package/dist/erc8128/index.d.mts +1 -1
  15. package/dist/erc8128/index.d.ts +1 -1
  16. package/dist/{index-vMFUhX5W.d.ts → index-49h53dGB.d.ts} +239 -23
  17. package/dist/{index-CnHVH3uK.d.mts → index-Bc91DzIt.d.mts} +239 -23
  18. package/dist/{index-Bu8DosJ4.d.mts → index-CS4qStqy.d.mts} +2 -2
  19. package/dist/{index-D8JpL_D5.d.ts → index-DagQ74ZQ.d.mts} +1 -1
  20. package/dist/{index-D8JpL_D5.d.mts → index-DagQ74ZQ.d.ts} +1 -1
  21. package/dist/{index-Cp01jrhF.d.ts → index-QswKm6BF.d.ts} +2 -2
  22. package/dist/index.d.mts +28 -13
  23. package/dist/index.d.ts +28 -13
  24. package/dist/index.js +714 -151
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +709 -152
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/ows-9uNT7ZSZ.d.mts +339 -0
  29. package/dist/ows-Tt33WE0s.d.ts +339 -0
  30. package/dist/providers/algorand/index.d.mts +1 -1
  31. package/dist/providers/algorand/index.d.ts +1 -1
  32. package/dist/providers/evm/index.d.mts +1 -1
  33. package/dist/providers/evm/index.d.ts +1 -1
  34. package/dist/providers/hedera/index.d.mts +1 -1
  35. package/dist/providers/hedera/index.d.ts +1 -1
  36. package/dist/providers/near/index.d.mts +1 -1
  37. package/dist/providers/near/index.d.ts +1 -1
  38. package/dist/providers/solana/index.d.mts +1 -1
  39. package/dist/providers/solana/index.d.ts +1 -1
  40. package/dist/providers/stellar/index.d.mts +1 -1
  41. package/dist/providers/stellar/index.d.ts +1 -1
  42. package/dist/providers/sui/index.d.mts +1 -1
  43. package/dist/providers/sui/index.d.ts +1 -1
  44. package/dist/providers/xrpl/index.d.mts +1 -1
  45. package/dist/providers/xrpl/index.d.ts +1 -1
  46. package/dist/react/index.d.mts +4 -4
  47. package/dist/react/index.d.ts +4 -4
  48. package/dist/react/index.js.map +1 -1
  49. package/dist/react/index.mjs.map +1 -1
  50. package/dist/react/picker/index.d.mts +1 -1
  51. package/dist/react/picker/index.d.ts +1 -1
  52. package/dist/receipts-8XsMeo1y.d.ts +191 -0
  53. package/dist/receipts-COPJ_BY9.d.mts +191 -0
  54. package/dist/utils/index.d.mts +2 -2
  55. package/dist/utils/index.d.ts +2 -2
  56. package/dist/{validity-BPV_IcqI.d.ts → validity-BpwXbfdj.d.ts} +1 -1
  57. package/dist/{validity-B6CmORz0.d.mts → validity-eGGXxzpi.d.mts} +1 -1
  58. package/dist/{wallet-w7BnImDG.d.ts → wallet-T0NFUUkT.d.mts} +3 -2
  59. package/dist/{wallet-w7BnImDG.d.mts → wallet-T0NFUUkT.d.ts} +3 -2
  60. package/package.json +3 -2
  61. package/src/adapters/index.ts +8 -1
  62. package/src/adapters/ows.ts +661 -154
  63. package/src/backend/index.ts +678 -61
  64. package/src/backend/stack-key.ts +263 -0
  65. package/src/dx402.ts +26 -9
  66. package/src/events.ts +12 -2
  67. package/src/index.ts +25 -1
  68. package/src/receipts.ts +12 -2
  69. package/src/wallet.ts +3 -2
  70. package/dist/ows-C-KmORG9.d.ts +0 -221
  71. package/dist/ows-Z9v4GxOZ.d.mts +0 -221
  72. package/dist/receipts-cLnL2c1l.d.ts +0 -111
  73. package/dist/receipts-hnXkpEYi.d.mts +0 -111
package/README.md CHANGED
@@ -17,7 +17,7 @@ Users sign a message or transaction, and the Ultravioleta facilitator handles on
17
17
  - **Buyer Policy**: Per-payment and cumulative budgets, payee allowlist and offer expiry, evaluated against the offer in hand **before signing** — six closed refusal codes in a fixed order
18
18
  - **Type-Safe**: Full TypeScript support
19
19
  - **React & Wagmi**: First-class integrations
20
- - **Signing Wallet Adapters**: EnvKeyAdapter (server/CLI), OWSWalletAdapter (Open Wallet Standard), or bring your own
20
+ - **Signing Wallet Adapters**: EnvKeyAdapter (server/CLI), OWSWalletAdapter (Open Wallet Standard vault, Node), or bring your own
21
21
  - **ERC-8128 Signed Requests**: Authenticate HTTP requests with a wallet (RFC 9421 + EIP-191) — no API keys
22
22
  - **ERC-8004 Trustless Agents**: On-chain reputation and identity across 23 networks (21 EVM + 2 Solana)
23
23
  - **Escrow & Refunds**: Hold payments with dispute resolution
@@ -560,16 +560,22 @@ const result = await wallet.signTypedData(JSON.stringify({
560
560
 
561
561
  ### OWSWalletAdapter (Open Wallet Standard)
562
562
 
563
- Delegates signing to any wallet that implements the [Open Wallet Standard](https://github.com/open-wallet-standard/open-wallet-standard). Works with browser wallets, agent vaults, and hardware-backed signers.
563
+ Signs with a wallet kept in an [Open Wallet Standard](https://github.com/open-wallet-standard/core) vault. OWS stores the keys encrypted on disk and signs inside the library, so the key never reaches your process. Node only: `@open-wallet-standard/core` is a native module with prebuilt binaries for Linux (glibc) and macOS, x64 and arm64.
564
564
 
565
565
  ```bash
566
- npm install @open-wallet-standard/core # optional peer dependency
566
+ npm install @open-wallet-standard/core # optional peer dependency, 1.4.2 or later 1.x
567
567
  ```
568
568
 
569
569
  ```typescript
570
+ import * as ows from '@open-wallet-standard/core';
570
571
  import { OWSWalletAdapter } from 'uvd-x402-sdk';
571
572
 
572
- const wallet = new OWSWalletAdapter(owsWalletInstance);
573
+ const wallet = new OWSWalletAdapter(ows, {
574
+ wallet: 'agent-treasury', // name or id of the wallet in the vault
575
+ passphrase: process.env.OWS_PASSPHRASE, // or an OWS API key; this is also the default
576
+ network: 'base', // chain used when the payload names none
577
+ // vaultPath: '/path/to/vault', // default: the OWS default (~/.ows)
578
+ });
573
579
 
574
580
  const auth = await wallet.signEIP3009({
575
581
  to: '0xRecipient...',
@@ -578,6 +584,29 @@ const auth = await wallet.signEIP3009({
578
584
  });
579
585
  ```
580
586
 
587
+ What it guarantees, tested against `@open-wallet-standard/core` 1.4.2 with an ephemeral key imported into a temporary vault:
588
+
589
+ - For inputs `EnvKeyAdapter` signs, it signs the same bytes: messages, EIP-3009 on every EVM network of the registry, typed data with uint256 up to `2**256 - 1` given as numbers, decimal strings or hex (the lifecycle order's `salt` included), and EIP-1559, EIP-2930 and legacy transactions (EIP-155 or not).
590
+ - Integers are checked against their EIP-712 type before anything is signed. OWS itself signs `2**256` as a uint256 as if it were `0`; the adapter throws `INVALID_AMOUNT` instead.
591
+ - Every signature is recovered with ethers over the digest `EnvKeyAdapter` signs. One that does not recover to `getAddress()` is not returned.
592
+ - `signTransaction` takes the hex ethers serializes, an `ethers.Transaction`, or a transaction object with `from`. A `from` that is not this wallet is refused before signing. A transaction without a `chainId` is signed pre-EIP-155, as `EnvKeyAdapter` signs it.
593
+ - The passphrase is never in the adapter's `JSON.stringify` / `util.inspect` output nor in its errors.
594
+
595
+ It refuses (`INVALID_CONFIG`, nothing signed) some inputs that ethers signs:
596
+
597
+ - Typed data: an integer string with spaces; a bool that is not a boolean (`1`, `0`, `"true"`, `"false"`; ethers signs `"false"` as true); `uint` / `int` without a width; a `primaryType` that is not the root struct (ethers ignores `primaryType` and signs the root).
598
+ - Transaction objects (`ethers.Wallet` signs these): a `type` given as `'0x2'`, `'2'` or a bigint; `to: ''`; `data: ''`; and any key ethers' `Transaction.from` does not read, other than `from` and `hash`. The error names the key, and for `input` / `gas` it names the ethers field (`data` / `gasLimit`).
599
+
600
+ A `vaultPath` that does not exist is created by OWS, so a typo gives "wallet not found".
601
+
602
+ **Migrating from the old form.** Up to 2.100.0 the adapter took a wallet object with `accounts` (the `OWSWallet` type). That shape never worked with `@open-wallet-standard/core` 1.x. It still compiles but is deprecated, and `new OWSWalletAdapter(wallet)` now throws `INVALID_CONFIG`. If you wrapped the library in such an object, drop the wrapper and pass the module and the wallet:
603
+
604
+ ```typescript
605
+ // before: new OWSWalletAdapter(createOWSWalletBridge(walletName, passphrase))
606
+ import * as ows from '@open-wallet-standard/core';
607
+ const wallet = new OWSWalletAdapter(ows, { wallet: walletName, passphrase });
608
+ ```
609
+
581
610
  ### Custom Adapter
582
611
 
583
612
  Implement the `SigningWalletAdapter` interface for your own signer:
@@ -861,6 +890,16 @@ app.post('/api/premium', async (req, res) => {
861
890
 
862
891
  `createPaymentMiddleware()` and `createHonoMiddleware()` verify and settle automatically by default (`before-handler`). Use `settlementStrategy: 'manual'` if you need to control when settlement happens (e.g., settle only after confirming you can fulfill the request).
863
892
 
893
+ ### Stack key (services run by Ultravioleta DAO)
894
+
895
+ Services run by Ultravioleta DAO carry a per-service credential, the stack key, so that the facilitator's rate-limit policy does not answer them `429`. Everything in this SDK that calls the facilitator sends it as `X-UVD-Stack-Key`: the clients `FacilitatorClient` (and the two middlewares), `Erc8004Client`, `BazaarClient`, `EscrowClient` and `AdvancedEscrowClient`, and the functions `anchorEvidence`, `availableBackends`, `streamTrafficEvents` and `getFacilitatorReceipt`, which take the same `stackKey` / `stackKeyHosts` options. It changes nothing else about a payment, and a facilitator that does not know the header ignores it.
896
+
897
+ - **Where it comes from:** the facilitator's operator issues one per service: `uvdsk_` followed by 43-128 base64url characters. The process receives it as `UVD_STACK_KEY` (read by default) or as the `stackKey` option, from a secret store, never from code.
898
+ - **Not for third parties:** if you integrate this SDK in your own product you have no key and need none. Leave both unset and no header is sent.
899
+ - **Only to a house facilitator:** the key goes over `https://` to `facilitator.ultravioletadao.xyz` and nowhere else, checked on the URL of every request. A request to any other host, or over plain `http://`, carries no header, whether the key came from the option or from `UVD_STACK_KEY`, and warns once, naming the host and never the key. `stackKeyHosts: ['facilitator-staging.example']` ADDS hosts (hostnames only, matched exactly); `facilitator.ultravioletadao.xyz` stays in. Plain `http://` is accepted only for `127.0.0.1` or `localhost`, and only when listed there. `EscrowClient`'s default base URL, `escrow.ultravioletadao.xyz`, is not the facilitator and gets the key only if listed.
900
+ - **The key does not follow redirects:** a redirect answered to a request that carries it fails with `StackKeyRedirectError` (exported), and the key is not sent again. Point the client at the facilitator's final URL.
901
+ - **A bad key never breaks a payment:** one leading BOM (U+FEFF), then spaces, tabs, CR and LF at both ends, are removed, and nothing else. A value that still does not match is not sent: the client warns once, without the value, and pays like any other client. The key never appears in a result, an error, a log line or a printed client.
902
+
864
903
  ## React
865
904
 
866
905
  ```tsx
@@ -1528,10 +1567,11 @@ const state = await client.queryEscrowState(paymentInfo);
1528
1567
  ### With SigningWalletAdapter (OWS)
1529
1568
 
1530
1569
  ```typescript
1570
+ import * as ows from '@open-wallet-standard/core';
1531
1571
  import { AdvancedEscrowClient } from 'uvd-x402-sdk/backend';
1532
1572
  import { OWSWalletAdapter } from 'uvd-x402-sdk';
1533
1573
 
1534
- const wallet = new OWSWalletAdapter(owsWalletInstance);
1574
+ const wallet = new OWSWalletAdapter(ows, { wallet: 'agent-treasury', network: 'base' });
1535
1575
  const client = new AdvancedEscrowClient(null, {
1536
1576
  wallet,
1537
1577
  rpcUrl: 'https://mainnet.base.org',
@@ -1625,7 +1665,7 @@ Register and discover paid x402 resources across the network.
1625
1665
  The Bazaar is served by the facilitator itself under `/discovery/*`. No API key, no separate host.
1626
1666
 
1627
1667
  ```typescript
1628
- import { BazaarClient, isAlive } from 'uvd-x402-sdk/backend';
1668
+ import { BazaarClient, bazaarExtension, isAlive } from 'uvd-x402-sdk/backend';
1629
1669
 
1630
1670
  const bazaar = new BazaarClient();
1631
1671
 
@@ -1647,6 +1687,16 @@ console.log(`${page.items.length} of ${page.pagination.total}`);
1647
1687
  // Free-text search. The parameter is `q`; anything else is rejected with a 400.
1648
1688
  const hits = await bazaar.listResources({ q: 'logs' });
1649
1689
 
1690
+ // Routing filters. Each one is sent only when you pass it.
1691
+ const posts = await bazaar.listResources({
1692
+ q: 'look up who owns a phone number, callable by an agent with a POST',
1693
+ method: 'POST', // GET | POST | PUT | PATCH, sent uppercased
1694
+ maxPriceUsd: 0.05, // price at most 5 cents
1695
+ hasInputSchema: true, // only resources that declare their input
1696
+ kind: 'api', // 'api' | 'content'
1697
+ excludeHost: 'tenjin.blog' // host names, comma-separated; not a URL, no port
1698
+ });
1699
+
1650
1700
  // Walk the whole filtered catalog, one page at a time
1651
1701
  for await (const r of bazaar.iterateResources({ health: 'alive' })) {
1652
1702
  if (isAlive(r)) console.log(r.url);
@@ -1665,6 +1715,9 @@ await bazaar.registerResource({
1665
1715
  maxTimeoutSeconds: 60,
1666
1716
  }],
1667
1717
  metadata: { category: 'ai', tags: ['image'] },
1718
+ // How to call it (see bazaarExtension below). Without it the listing
1719
+ // says hasInputSchema: false.
1720
+ extensions: bazaarExtension({ method: 'POST', body: { prompt: 'a red fox' } }),
1668
1721
  });
1669
1722
 
1670
1723
  // Aggregate catalog metrics
@@ -1674,6 +1727,52 @@ console.log(stats.total, stats.visible, stats.byHealth.alive);
1674
1727
 
1675
1728
  Timestamps (`firstSeen`, `lastSeen`, `lastUpdated`, `health.lastChecked`) are Unix epoch **seconds**. Use `epochToDate()` to get a `Date`.
1676
1729
 
1730
+ **Search length.** `q` can be up to 400 characters (`DEFAULT_MAX_SEARCH_LEN`), long enough for a request in natural language. Characters are counted as the facilitator counts them (code points: an emoji is one, not two). A longer `q` is refused before the request. Change the cap with `new BazaarClient({ maxSearchLen })`; against an older facilitator whose substring search takes at most 128 (up to 2.46.1, and some 2.47.0 builds), pass `maxSearchLen: MAX_SEARCH_LEN` to fail before the request instead of getting its 400. `getResourceByUrl()` searches with the URL's first 128 characters, which every facilitator accepts.
1731
+
1732
+ **Routing filters** (`maxPriceUsd`, `method`, `hasInputSchema`, `kind`, `excludeHost`) need facilitator 2.48.0 or later (some 2.47.0 builds have them and some do not). A facilitator without them (every one up to 2.46.1) answers each, as any query parameter it does not know, with a 400 that names it, so the client sends each of these only when you pass it: a call that sets none of them sends exactly what it sent before. A value you pass is sent or refused before the request, never dropped, by the rules the facilitator reads it with: `maxPriceUsd` is a number `>= 0`, sent in decimal (`1e-7` goes out as `0.0000001`) and refused if that takes more than 32 characters; `method` is `GET`, `POST`, `PUT` or `PATCH` in any case; `kind` is `api` or `content`; `excludeHost` is a host name, or up to 20 separated by commas, each matched with its subdomains, with no scheme, path or port. `null` counts as not passed.
1733
+
1734
+ **Registering with `extensions`.** `registerResource({ ..., extensions })` sends the object as the body's `extensions`, which the facilitator keeps as it comes: from `extensions.bazaar.info.input` the listing gets `hasInputSchema: true`, and it is found by `method: 'POST'` when that is what it declares. Pass the same `bazaarExtension(...)` you put in your 402. It must be a plain object of at most 16 KiB as JSON and 64 levels deep (the facilitator drops a bigger one without an error, so the client refuses it before the request). Without `extensions` the request body is exactly what it was. `extensions` only counts on a URL's first registration: registering a URL that is already in the catalog is answered with a 409 (`registerResource` throws) and leaves its record as it was, and the facilitator serves no public route that updates one. A URL listed without `extensions` does not gain them by registering again, so pass them the first time.
1735
+
1736
+ ### Declaring your endpoint to the Bazaar (`bazaarExtension`)
1737
+
1738
+ A seller can tell the facilitator how its endpoint is called inside the 402 itself, with the `bazaar` extension of x402 v2 ([spec](https://github.com/coinbase/x402/blob/main/specs/extensions/bazaar.md)). The facilitator catalogs the endpoint from it. The method travels in `info.input.method` and the example body in `info.input.body`: that is what a facilitator needs to probe a POST endpoint with a POST instead of a GET. Pass the same object as `extensions` to `BazaarClient.registerResource()` (above) when you register the endpoint yourself.
1739
+
1740
+ ```typescript
1741
+ import { bazaarExtension, create402Response } from 'uvd-x402-sdk';
1742
+
1743
+ // POST (or PUT / PATCH) with a JSON body
1744
+ const { status, headers, body } = create402Response(
1745
+ {
1746
+ amount: '0.05',
1747
+ recipient: '0xYourWallet...',
1748
+ resource: 'https://api.example.com/phone-lookup',
1749
+ chainName: 'base',
1750
+ x402Version: 2,
1751
+ },
1752
+ {
1753
+ extensions: bazaarExtension({
1754
+ method: 'POST',
1755
+ body: { phone: '+573001234567' }, // example body, required for POST/PUT/PATCH
1756
+ bodySchema: { // optional JSON Schema of the body
1757
+ type: 'object',
1758
+ properties: { phone: { type: 'string' } },
1759
+ required: ['phone'],
1760
+ },
1761
+ output: { example: { owner: 'ACME S.A.S.', carrier: 'Claro' } },
1762
+ }),
1763
+ }
1764
+ );
1765
+
1766
+ // GET (or HEAD / DELETE) with query parameters
1767
+ bazaarExtension({
1768
+ method: 'GET',
1769
+ queryParams: { city: 'Bogota' },
1770
+ output: { example: { city: 'Bogota', temperature: 18 } },
1771
+ });
1772
+ ```
1773
+
1774
+ The result is `{ bazaar: { info, schema } }`: `info.input` (`type: "http"`, `method`, then `bodyType` and `body` for a body method, or `queryParams` for a query method, plus optional `headers`), `info.output` (`type`, default `json`, and `example`), and `schema`, the JSON Schema (draft 2020-12) that `info` validates against. `bodyType` is `json` (default), `form-data` or `text` (then `body` is a string). A body method without `body`, a query method with one, or a method the spec does not name (`CONNECT`, `OPTIONS`, ...) throws. `extensions` is a v2 field: `create402Response` throws if the response would be v1 rather than dropping it, and without `extensions` it returns exactly what it returned before.
1775
+
1677
1776
  ## x402 v2 requests (`buildVerifyRequestV2` / `buildSettleRequestV2`)
1678
1777
 
1679
1778
  If your 402 advertises CAIP-2 networks (`eip155:8453`), you are speaking v2 and
@@ -1,6 +1,7 @@
1
- import { X as X402Version, a as PaymentResult } from '../index-D8JpL_D5.mjs';
2
- export { E as EnvKeyAdapter, O as OWSWallet, a as OWSWalletAdapter } from '../ows-Z9v4GxOZ.mjs';
3
- import '../wallet-w7BnImDG.mjs';
1
+ import { X as X402Version, a as PaymentResult } from '../index-DagQ74ZQ.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-9uNT7ZSZ.mjs';
3
+ import '../wallet-T0NFUUkT.mjs';
4
+ import 'ethers';
4
5
 
5
6
  /**
6
7
  * uvd-x402-sdk - Wagmi/Viem Adapter
@@ -1,6 +1,7 @@
1
- import { X as X402Version, a as PaymentResult } from '../index-D8JpL_D5.js';
2
- export { E as EnvKeyAdapter, O as OWSWallet, a as OWSWalletAdapter } from '../ows-C-KmORG9.js';
3
- import '../wallet-w7BnImDG.js';
1
+ import { X as X402Version, a as PaymentResult } from '../index-DagQ74ZQ.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-Tt33WE0s.js';
3
+ import '../wallet-T0NFUUkT.js';
4
+ import 'ethers';
4
5
 
5
6
  /**
6
7
  * uvd-x402-sdk - Wagmi/Viem Adapter