@dexterai/x402 4.1.0 → 5.0.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 CHANGED
@@ -5,11 +5,11 @@
5
5
  <h1 align="center">@dexterai/x402</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Give your agent a spending limit it can't exceed.</strong>
8
+ <strong>Give your agent a spending limit it cannot exceed — without ever giving up your wallet.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- Open a tab, set a cap, and your agent pays as it works, with no signature on each charge. Your money stays in your own wallet, and the seller is still guaranteed payment. Buyer and seller SDKs, on Solana and the major EVM chains.
12
+ Open a tab, set a cap, and your agent pays as it works. No signature per charge, no escrow, no custodian. Your USDC stays in your own wallet the entire time, and the seller is still guaranteed payment.
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -25,187 +25,121 @@
25
25
 
26
26
  ---
27
27
 
28
- ## Why a tab
28
+ ## The problem
29
29
 
30
- A tab gives an agent a spending limit that the Solana program enforces at consensus. You set a cap, the agent pays against it call by call with no signature on each charge, and your USDC stays in your own wallet the entire time.
30
+ An agent that pays for things needs money it can reach without you in the loop for every charge. The two usual ways to give it that each cost you something you shouldn't have to give up.
31
31
 
32
- The two older ways to let an agent spend each give up something a tab keeps. Prefunding an escrow moves your money to a custodian, so your balance is on the table and you have paid a stranger in advance. Handing a wallet a spending delegate keeps your custody but lets you withdraw the funds mid-charge, so the seller can be left unpaid and serious sellers decline it. A tab keeps both halves: the money never leaves your wallet, and while the tab is open the chain blocks you from pulling it out from under accrued charges. The seller gets paid when they settle, and settlement is automatic.
32
+ - **Prefund an escrow** and your money leaves your wallet to sit with a custodian before you've bought anything. Your balance is on the table, and you've paid a stranger in advance.
33
+ - **Hand it a spending delegate** and you keep custody — but you can also pull the funds mid-charge, so the seller can be left unpaid. Serious sellers decline it.
33
34
 
34
- The closest familiar shape is an auth-and-capture card hold, with the hold enforced on-chain instead of by a processor.
35
+ ## The tab
35
36
 
36
- ---
37
-
38
- ## Install
39
-
40
- ```bash
41
- npm install @dexterai/x402
42
- ```
43
-
44
- One install is both sides: the buyer surface at `@dexterai/x402/tab`, the seller surface at `@dexterai/x402/tab/seller`.
45
-
46
- ## Open a tab and pay (buyer)
37
+ A **tab** keeps both halves. You open one against your own wallet with a single passkey tap and set a cap. Your agent spends against that cap call by call, with no signature on each charge. The money never leaves your wallet — and while the tab is open, the Solana program blocks you from pulling it out from under what the agent has already run up. The seller gets paid when they settle, automatically.
47
38
 
48
- A buyer drives tabs through a `vault` adapter over their passkey-rooted Solana vault. Build it once from the vault's addresses, which you receive when you enroll at [dexter.cash](https://dexter.cash), plus your passkey signer:
49
-
50
- ```ts
51
- import { createSolanaVaultAdapter } from '@dexterai/x402/tab/adapters/solana';
52
-
53
- const vault = createSolanaVaultAdapter({
54
- connection, // your Solana Connection (any RPC)
55
- swigAddress, // the vault's Swig state account, from enrollment
56
- vaultPda, // the vault's gate PDA, from enrollment
57
- passkeySigner, // browser: WebAuthnAssertion; server agent: passkeySignerFromP256Keypair(kp)
58
- feePayer, // lamport fee payer (a Signer)
59
- });
60
- ```
61
-
62
- Given only a URL, the buyer then reads the seller's terms from the URL's own `402` challenge, opens a lock-protected tab, and pays. The seller's address comes off the wire, never from your code:
39
+ The cap is enforced at consensus by an on-chain program not by this library, and not by Dexter. The closest familiar shape is an auth-and-capture card hold, except the hold lives on-chain instead of inside a processor, and no one ever takes your money.
63
40
 
64
41
  ```ts
65
42
  import { payUrlWithTab } from '@dexterai/x402/tab';
66
43
 
67
- const tabs = new Map(); // one open tab per seller, reused across calls
44
+ const tabs = new Map(); // one open tab per seller, reused across calls
68
45
  const { result, tab } = await payUrlWithTab(
69
46
  'https://api.example.com/paid/infer',
70
47
  { method: 'GET' },
71
48
  { vault, perUnitCap: '0.01', totalCap: '1.00', tabs },
72
49
  );
73
- // ...more payUrlWithTab calls reuse the same tab via `tabs`...
74
- await tab?.close(); // one on-chain settle for everything the agent spent
50
+ // ...the agent keeps calling; every call reuses the same tab, with no new prompt...
51
+ await tab?.close(); // one on-chain settle pays the seller for everything
75
52
  ```
76
53
 
77
- To decide before you pay, `resolveTabTerms(url)` reads a URL's price and settlement terms without paying, for consent screens, directories, or an agent that plans ahead:
78
-
79
- ```ts
80
- import { resolveTabTerms } from '@dexterai/x402/tab';
81
-
82
- const resolved = await resolveTabTerms('https://api.example.com/paid/tick');
83
- if (resolved.kind === 'terms') {
84
- console.log(resolved.terms.counterparty, resolved.terms.perRequest.human);
85
- // settlement: { custody: 'non-custodial', protection: 'lock', settleOn: 'close' }
86
- }
87
- ```
88
-
89
- ## Accept tabs on your API (seller)
90
-
91
- You get paid for what you serve. As an agent spends against its tab, accrued charges crystallize on-chain into a reservation against the buyer's wallet — sized to exactly what's accrued, not the whole wallet — so the buyer can't withdraw out from under your charges. One on-chain settle at close pays your `sellerPubkey` for everything metered; you hold no key and sign nothing.
92
-
93
- `tabOrExactMiddleware` is the recommended default: one middleware that advertises a tab and a one-shot price in a single 402 challenge, so agents pay by tab and one-shot callers pay exact, at the same price.
94
-
95
- ```ts
96
- import { tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
97
- import type { X402Request } from '@dexterai/x402/server';
98
-
99
- app.get('/paid/tick',
100
- tabOrExactMiddleware({ connection, sellerPubkey, network: 'solana:mainnet', perUnit: '0.01' }),
101
- async (req, res) => {
102
- if ((req as X402Request).x402) { res.json({ data: '...', paidVia: 'exact' }); return; } // exact rail
103
- const tab = requireTab(req); // tab rail
104
- const meter = openSse(res, { tab, perUnit: '0.01' });
105
- await meter.charge(1); // demand a fresh voucher; throws if the cap is exceeded
106
- meter.send(JSON.stringify({ data: '...' }));
107
- await meter.end(); // ALWAYS await — persists the final delivered amount
108
- });
109
- ```
110
-
111
- For a tab-only endpoint, compose the two middlewares directly: `tabChallengeMiddleware` (answers voucher-less requests with the standard x402 challenge, so any agent can discover you) before `tabMiddleware` (verifies the per-charge vouchers). Both are exported from `@dexterai/x402/tab/seller`.
54
+ That's the whole loop: one tap to open, unlimited calls under the cap, one settle to close. The seller's address comes off the wire from the URL's own `402` challenge never from your code. The `vault` is built once from your passkey-rooted wallet; see [Setup](#setup).
112
55
 
113
56
  ---
114
57
 
115
- ## How it works
58
+ ## Why you can trust it
116
59
 
117
- Three nouns and one actor.
60
+ "Unruggable" has to be earned, so here is what backs it. Every property below is enforced by the on-chain program, not by this SDK and not by Dexter.
118
61
 
119
- - **Vault:** your money, held in your own wallet and locked by your passkey. The program never takes custody.
120
- - **Tab:** a capped spending limit you open against your vault, for one agent and one counterparty. The agent draws against it; the vault enforces the cap.
121
- - **Passkey:** your key. You tap it to set up the vault and to open or approve a tab. Nothing else can authorize a withdrawal.
122
- - **Your agent:** who you open the tab for.
62
+ - **Non-custodial.** Your USDC never leaves your wallet. The program holds no funds — it records bindings and gates withdrawals. There is no escrow account and no custodian to fail.
63
+ - **The cap is consensus-enforced.** The limit is checked by the Solana program at consensus, not by this library. Read the program and verify it yourself: [`Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc`](https://solscan.io/account/Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc) on Solana mainnet.
64
+ - **Only your passkey moves money.** Withdrawals require a WebAuthn assertion verified by Solana's secp256r1 precompile. Neither the SDK nor the facilitator holds a key that can drain your wallet.
65
+ - **The seller is protected, surgically.** As the agent spends, accrued charges crystallize on-chain into a reservation sized to exactly what's been spent — never the whole wallet. The buyer keeps spending or withdrawing the rest of their balance freely, the seller is guaranteed what they're owed, and if a seller ever goes silent the buyer reclaims the abandoned reservation after a fixed grace period. No one's funds can be frozen indefinitely.
66
+ - **Live on mainnet, pre-audit, and we say so.** Tabs settle on Solana mainnet today; an external audit is funded and in flight. The report and any findings publish in the [`dexter-vault`](https://github.com/Dexter-DAO/dexter-vault) program repo. Responsible disclosure: branch@dexter.cash.
123
67
 
124
- A tab opens with one passkey tap, the agent spends against it with no further prompts, and one on-chain settle pays the seller and closes it. Each charge the agent makes is off-chain, so it costs no gas and no signature. As charges accrue, the seller crystallizes them on-chain into the reservation that guarantees its payment — keyless and automatic, with no action and no signature from you.
68
+ The full threat model and trust assumptions live in the program's [`SECURITY.md`](https://github.com/Dexter-DAO/dexter-vault).
125
69
 
126
70
  ---
127
71
 
128
- ## Why you can trust it
129
-
130
- The word "unruggable" has to be earned, so here is what actually backs it. The properties below are enforced by the on-chain program, not by this SDK.
72
+ ## Setup
131
73
 
132
- - **Non-custodial.** Your USDC stays in your own wallet. The program holds no funds; it records bindings and gates a withdrawal. There is no escrow account and no custodian to fail.
133
- - **The cap is enforced on-chain.** The limit is checked by the Solana program at consensus, not by this library and not by Dexter. You can read the program and verify the cap yourself: [`Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc`](https://solscan.io/account/Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc) on Solana mainnet.
134
- - **Only your passkey moves funds.** Withdrawals require a WebAuthn assertion verified by Solana's secp256r1 precompile. The SDK and the facilitator never hold a key that can drain your wallet.
135
- - **The seller is protected, surgically.** As the agent spends, accrued charges crystallize on-chain into a reservation the buyer cannot withdraw out from under. The reservation is exactly the amount accrued — not the whole wallet — so the buyer keeps spending or withdrawing the rest of their balance freely while the tab stays open. The seller is guaranteed the accrued amount; the buyer is never locked out of funds they haven't committed. If a seller ever goes silent, the buyer recovers an abandoned reservation themselves after a fixed grace period; nobody's funds can be frozen indefinitely.
136
- - **Live on Solana mainnet.** Tabs settle on mainnet today. We can demonstrate the program rejecting a forged passkey from a clone: see the [`dexter-vault`](https://github.com/Dexter-DAO/dexter-vault) program repo.
137
- - **Pre-audit, and we say so.** Not yet externally audited; funding is in flight. The report and any findings publish in the program repo. Responsible disclosure: branch@dexter.cash.
74
+ Install the SDK alongside `@dexterai/vault` it's a peer dependency, so your app and the tab adapter share one vault instance:
138
75
 
139
- The full threat model and trust assumptions live in the program's [`SECURITY.md`](https://github.com/Dexter-DAO/dexter-vault).
76
+ ```bash
77
+ npm install @dexterai/x402 @dexterai/vault
78
+ ```
140
79
 
141
- ---
80
+ Build a `vault` adapter once, from the wallet addresses you receive when you enroll at [dexter.cash](https://dexter.cash), plus your passkey signer:
142
81
 
143
- ## Approving a tab is one hosted screen
82
+ ```ts
83
+ import { createSolanaVaultAdapter } from '@dexterai/x402/tab/adapters/solana';
144
84
 
145
- When a partner's app opens a tab for a user, the approval runs on one Dexter-hosted consent screen, deep-linked from the partner's app. The user sees the counterparty, the cap, and the expiry, taps their passkey once, and control returns to the app. The partner builds no approval UI and never handles a passkey.
85
+ const vault = createSolanaVaultAdapter({
86
+ connection, // your Solana Connection (any RPC)
87
+ swigAddress, // the vault's Swig state account, from enrollment
88
+ vaultPda, // the vault's gate PDA, from enrollment
89
+ passkeySigner, // browser: vault's DexterApiBrowserPasskeySigner · agent: passkeySignerFromP256Keypair(kp)
90
+ feePayer, // lamport fee payer (a Signer)
91
+ });
92
+ ```
146
93
 
147
- The screen is hosted by Dexter for a structural reason, not a stylistic one: the vault's passkey can only sign on Dexter's own origin, so a user cannot be phished into approving on a look-alike page. The safety is a property of where the key will sign. Flow and routing: [docs.dexter.cash/tabs](https://docs.dexter.cash). **[TODO: confirm final docs path once #5 lands.]**
94
+ That `vault` drives [the tab loop above](#the-tab). To inspect a seller's price before you spend, `resolveTabTerms(url)` reads the terms without paying for consent screens, directories, or an agent that plans ahead.
148
95
 
149
96
  ---
150
97
 
151
- ## One-shot payments
152
-
153
- When a charge is a single discrete purchase rather than metered consumption, pay it one-shot. x402 is HTTP's payment protocol: a server returns `402 Payment Required` describing what it wants paid, the client signs and retries, and the resource comes back. USDC on Solana and the major EVM chains, behind one API.
154
-
155
- ```typescript
156
- import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
98
+ ## Get paid (sellers)
157
99
 
158
- const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
159
- const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
100
+ You get paid for exactly what you serve, and you hold no key. As an agent spends against its tab, charges crystallize on-chain into a reservation against the buyer's wallet; one settle at close pays your address for everything metered. One middleware advertises both a tab and a one-shot price in a single `402`, so tab-native agents and one-shot callers pay at the same rate.
160
101
 
161
- const result = await payAndFetch(
162
- 'https://api.example.com/protected',
163
- { method: 'GET' },
164
- { solana, evm },
165
- {},
166
- );
102
+ ```ts
103
+ import { tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
104
+ import type { X402Request } from '@dexterai/x402/server';
167
105
 
168
- if (result.ok && result.paid) {
169
- const data = await result.response.json();
170
- console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
171
- } else if (result.ok && !result.paid) {
172
- const data = await result.response.json(); // endpoint didn't demand payment; passed through
173
- } else {
174
- console.error(result.reason, result.detail);
175
- }
106
+ app.get('/paid/tick',
107
+ tabOrExactMiddleware({ connection, sellerPubkey, network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', perUnit: '0.01' }),
108
+ async (req, res) => {
109
+ if ((req as X402Request).x402) { res.json({ data: '...' }); return; } // one-shot caller, already paid
110
+ const meter = openSse(res, { tab: requireTab(req), perUnit: '0.01' }); // tab caller
111
+ await meter.charge(1); // demand a fresh voucher; throws if the cap is exceeded
112
+ meter.send(JSON.stringify({ data: '...' }));
113
+ await meter.end(); // always await — persists the final delivered amount
114
+ });
176
115
  ```
177
116
 
178
- `payAndFetch` handles x402 v1 and v2 transparently and returns a discriminated `PayResult`: `ok` splits into `paid: true | false`, so a free 200 is distinguishable from a paid one, and expected failures don't throw.
117
+ Want a tab-only endpoint, or to compose the pieces yourself? The full seller surface is in [REFERENCE.md](./REFERENCE.md#sellers).
179
118
 
180
- Protect an endpoint with `x402Middleware`; the handler runs only after payment settles. In React, `useX402Payment` takes wallets from `@solana/wallet-adapter-react` or `wagmi` and returns a `fetch` that pays automatically. Read a settled receipt off any paid response with `getPaymentReceipt(response)`.
119
+ ---
181
120
 
182
- ```typescript
183
- import { x402Middleware } from '@dexterai/x402/server';
121
+ ## Hosted approval (partners)
184
122
 
185
- app.get('/api/protected',
186
- x402Middleware({ payTo: 'YourReceivingAddress', amount: '0.01', network: 'eip155:8453' }),
187
- (req, res) => res.json({ data: 'protected content' }),
188
- );
189
- ```
123
+ When a partner's app opens a tab for a user, the approval runs on one Dexter-hosted consent screen, deep-linked from the partner's app. The user sees the counterparty, the cap, and the expiry, taps their passkey once, and control returns to the app. The partner builds no approval UI and never handles a passkey.
124
+
125
+ The screen is hosted for a structural reason, not a stylistic one: the vault's passkey can only sign on Dexter's own origin, so a user cannot be phished into approving on a look-alike page. The safety is a property of *where the key signs*. Flow and routing: [docs.dexter.cash](https://docs.dexter.cash).
190
126
 
191
127
  ---
192
128
 
193
129
  ## Also in this package
194
130
 
195
- Four supporting surfaces, each with its own reference below.
196
-
197
- - **Batch settlement (EVM).** Prepay an escrow once, make many discrete paid calls against it with off-chain vouchers, and settle in a handful of transactions to amortize gas. EVM only. `openBatchChannel` / `createBatchSettlementSeller`.
198
- - **Discovery (bazaar).** Make any `x402Middleware`-protected route discoverable through the official x402 bazaar spec, so agents find it by capability. `bazaarExtension()`.
199
- - **Sponsored access.** When an agent pays through Dexter's facilitator, a matched recommendation can ride along in the receipt; the agent's model may act on it. `sponsoredAccess: true`.
200
- - **Auto-listing.** Endpoints paid through the facilitator are auto-discovered, named, and quality-tested, then surfaced in `x402_search` across MCP clients. No registration step.
131
+ Tabs are the headline. The same install carries the rest of the x402 surface — each documented in full in [REFERENCE.md](./REFERENCE.md).
201
132
 
202
- Full examples for each are in the [reference](#reference) section.
133
+ - **One-shot payments** a single discrete purchase over HTTP `402`, USDC on Solana and the major EVM chains. `payAndFetch` (client), `x402Middleware` (server), `useX402Payment` (React).
134
+ - **Batch settlement (EVM)** — prepay an escrow once, make many paid calls with off-chain vouchers, settle in a handful of transactions to amortize gas. `openBatchChannel`.
135
+ - **Discovery** — make any protected route findable by capability through the x402 bazaar spec. `bazaarExtension()`.
136
+ - **Sponsored access & auto-listing** — endpoints paid through Dexter's facilitator are auto-discovered, named, quality-tested, and surfaced in `x402_search` across MCP clients, with no registration step.
203
137
 
204
138
  ---
205
139
 
206
140
  ## Supported networks
207
141
 
208
- All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supported). USDC on every chain. Tabs are Solana; one-shot and batch settlement span Solana and the EVM chains below.
142
+ Tabs are Solana. One-shot and batch settlement span Solana and the major EVM chains; USDC on every chain. Full live list: [Dexter facilitator](https://x402.dexter.cash/supported).
209
143
 
210
144
  | Network | CAIP-2 | Status |
211
145
  |---------|--------|--------|
@@ -218,128 +152,13 @@ All networks supported by the [Dexter facilitator](https://x402.dexter.cash/supp
218
152
  | BSC | `eip155:56` | Production |
219
153
  | SKALE Base | `eip155:1187947933` | Production (zero gas) |
220
154
 
221
- Testnets: Solana Devnet/Testnet, Base Sepolia, SKALE Base Sepolia. Multi-chain endpoints accept any chain in the list; the buyer picks. Pass `network` as an array to `x402Middleware`, with a `payTo` map for per-chain receivers.
222
-
223
155
  ---
224
156
 
225
- ## Reference
226
-
227
- ### Package exports
228
-
229
- ```typescript
230
- // Tabs (Solana): buyer
231
- import { payUrlWithTab, resolveTabTerms, resolveTabOffer } from '@dexterai/x402/tab';
232
-
233
- // Tabs (Solana): seller
234
- import { tabChallengeMiddleware, tabMiddleware, tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
235
-
236
- // One-shot client
237
- import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
238
-
239
- // React
240
- import { useX402Payment } from '@dexterai/x402/react';
241
-
242
- // Server middleware + discovery
243
- import { x402Middleware, bazaarExtension, declareDiscoveryExtension, createX402Server } from '@dexterai/x402/server';
244
-
245
- // Batch settlement
246
- import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
247
- import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
248
-
249
- // Adapters (advanced) + utilities
250
- import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
251
- import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
252
- ```
253
-
254
- ### `payUrlWithTab(url, init, opts) → Promise<{ result, tab }>`
255
-
256
- Opens (or reuses) a lock-protected tab to the seller discovered from the URL's `402` challenge, and pays. `opts`: `{ vault, perUnitCap, totalCap, tabs }`. Reuse one `tabs` map across calls to keep a single open tab per seller; `tab.close()` settles everything spent in one transaction.
257
-
258
- ### `resolveTabTerms(url) → Promise<TabResolution>`
259
-
260
- Reads a URL's tab terms without paying. Returns `{ kind: 'terms', terms: { counterparty, perRequest, network, settlement } }`, or a non-terms kind when the URL offers no tab.
261
-
262
- ### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
263
-
264
- | Argument | Type | Description |
265
- |---|---|---|
266
- | `url` | `string` | Endpoint to fetch |
267
- | `init` | `RequestInit` | Standard fetch init. Body must be a string. |
268
- | `wallets` | `WalletSet` | `{ solana?, evm? }`. The SDK picks the chain by what the merchant accepts and what you can pay |
269
- | `opts` | `PayAndFetchOptions` | `maxAmountAtomic`, `timeoutMs`, `solanaRpcUrl` |
270
-
271
- `PayResult` is a discriminated union. Narrow on `ok`, then on `paid`:
272
-
273
- ```typescript
274
- if (result.ok && result.paid) {
275
- result.response; result.amountPaid; result.network; result.txSignature;
276
- } else if (result.ok && !result.paid) {
277
- result.response; // merchant didn't demand payment; pass-through
278
- } else {
279
- result.reason; // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
280
- result.detail;
281
- }
282
- ```
283
-
284
- ### `x402Middleware(config)`
285
-
286
- | Option | Type | Required | Description |
287
- |---|---|---|---|
288
- | `payTo` | `string \| { 'solana:*'?, 'eip155:*'?, [caip2]? }` | Yes | Receiver address; map for per-chain receivers |
289
- | `amount` | `string` | Yes | USD amount, e.g., `'0.01'` |
290
- | `network` | `string \| string[]` | No | CAIP-2 network(s). Default: Solana mainnet |
291
- | `scheme` | `'exact' \| 'batch-settlement'` | No | `'batch-settlement'` mounts as a batch-settlement seller |
292
- | `extensions` | `ResourceServerExtension[]` | No | E.g., `[bazaarExtension()]` |
293
- | `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Instinct ad-network recommendation injection |
294
- | `facilitatorUrl` | `string` | No | Override facilitator (default: `x402.dexter.cash`) |
295
-
296
- ### Batch settlement
297
-
298
- ```ts
299
- import { openBatchChannel } from '@dexterai/x402/batch-settlement';
300
-
301
- const escrow = await openBatchChannel({ wallet: evmWallet, network: 'eip155:8453', deposit: '0.30' });
302
- await escrow.fetch('https://api.example.com/v1/data');
303
- console.log(escrow.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
304
- await escrow.close();
305
- ```
306
-
307
- State auto-persists and resumes with `resumeBatchChannel({ wallet, network, salt })`. If the seller never settles, reclaim unspent escrow with `forceWithdraw()` then `finalizeWithdraw()`. The seller mounts `createBatchSettlementSeller(config)` as an Express handler; Dexter operates the authorizer, so the seller manages no signing key. Returns a handler with `.stop()`, `.closeAll()`, `.closeChannel(id)`.
308
-
309
- ### Discovery, sponsored access
310
-
311
- `bazaarExtension()` plus `declareDiscoveryExtension(config)` attach a spec-compliant `extensions.bazaar` block to a route's 402; extensions are opt-in and failure-isolated, so the payment path is never affected. `sponsoredAccess` injects `_x402_sponsored` into responses; read it with `getSponsoredRecommendations(response)`. Campaign creation is x402-gated at `x402ads.io`.
312
-
313
- ### Removed in v4.0.0 (migration)
314
-
315
- The v1-era helpers were removed in `4.0.0`. The payment engine is unchanged — the canonical entrypoints have done their jobs since 3.x:
316
-
317
- | Removed | Use instead |
318
- | --- | --- |
319
- | `createX402Client(...).fetch(url)` | `payAndFetch(url, init, wallets)` (client) |
320
- | `wrapFetch(fetch, opts)` | `payAndFetch` (client) — or pass your `WalletSet` directly |
321
- | `x402AccessPass`, `x402BrowserSupport` | `x402Middleware` (server) |
322
- | `createDynamicPricing`, `formatPricing` | compute the price per request in your handler, pass it to `x402Middleware` |
323
- | `createTokenPricing`, `countTokens`, `MODEL_REGISTRY` + model getters | gone — these wrapped a hardcoded Jan-2026 OpenAI snapshot that goes stale; price requests with your model provider's live API and pass the amount to `x402Middleware` |
324
- | `stripePayTo` | a `PayToProvider` map on `x402Middleware` |
325
- | `useAccessPass` (react) | `useX402Payment` (react) |
326
-
327
- `payAndFetch` speaks both x402 v1 and v2 and returns a discriminated `PayResult`, so it covers everything the old clients did. **EVM one-shot pay-per-call is unchanged** — `payAndFetch` is the EVM path until Tabs reaches EVM. To pin the old surface, stay on `@dexterai/x402@^3`.
328
-
329
- ---
330
-
331
- ## Development
332
-
333
- ```bash
334
- npm run build # ESM + CJS
335
- npm run dev # Watch mode
336
- npm run typecheck
337
- npm test # 359 vitest tests
338
- ```
339
-
340
- ## License
157
+ ## More
341
158
 
342
- MIT. See [LICENSE](./LICENSE).
159
+ - **[REFERENCE.md](./REFERENCE.md)** — every export, option table, and example: tabs, one-shot, batch settlement, discovery.
160
+ - **Upgrading?** `5.0.0` makes `@dexterai/vault` a peer dependency (`>=0.19`) and unifies the passkey signer on `signOperation`. Migration from v4/v3: [REFERENCE.md](./REFERENCE.md#migration).
161
+ - **License** — MIT, see [LICENSE](./LICENSE).
343
162
 
344
163
  ---
345
164
 
package/REFERENCE.md ADDED
@@ -0,0 +1,275 @@
1
+ # @dexterai/x402 — Reference
2
+
3
+ The full API surface. For what the SDK *is* and why, start with the [README](./README.md).
4
+
5
+ - [Package exports](#package-exports)
6
+ - [Tabs (buyer)](#tabs-buyer)
7
+ - [Sellers](#sellers)
8
+ - [One-shot payments](#one-shot-payments)
9
+ - [Server middleware](#server-middleware)
10
+ - [Batch settlement (EVM)](#batch-settlement-evm)
11
+ - [Discovery & sponsored access](#discovery--sponsored-access)
12
+ - [Migration](#migration)
13
+ - [Development](#development)
14
+
15
+ ---
16
+
17
+ ## Package exports
18
+
19
+ ```typescript
20
+ // Tabs (Solana): buyer
21
+ import { payUrlWithTab, resolveTabTerms, resolveTabOffer } from '@dexterai/x402/tab';
22
+ import { createSolanaVaultAdapter, passkeySignerFromP256Keypair } from '@dexterai/x402/tab/adapters/solana';
23
+
24
+ // Tabs (Solana): seller
25
+ import { tabChallengeMiddleware, tabMiddleware, tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
26
+
27
+ // One-shot client
28
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet, getPaymentReceipt } from '@dexterai/x402/client';
29
+
30
+ // React
31
+ import { useX402Payment } from '@dexterai/x402/react';
32
+
33
+ // Server middleware + discovery
34
+ import { x402Middleware, bazaarExtension, declareDiscoveryExtension, createX402Server } from '@dexterai/x402/server';
35
+
36
+ // Batch settlement
37
+ import { openBatchChannel, resumeBatchChannel } from '@dexterai/x402/batch-settlement';
38
+ import { createBatchSettlementSeller } from '@dexterai/x402/batch-settlement/seller';
39
+
40
+ // Adapters (advanced) + utilities
41
+ import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
42
+ import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
43
+ ```
44
+
45
+ > `@dexterai/vault` is a **peer dependency** (`>=0.19`): install it alongside `@dexterai/x402` so the tab adapter and your app share ONE vault instance. The passkey signer the adapter consumes is vault's canonical `signOperation(operationMessage)` — the same type your app builds, with no bridge shim.
46
+
47
+ ---
48
+
49
+ ## Tabs (buyer)
50
+
51
+ ### `payUrlWithTab(url, init, opts) → Promise<{ result, tab }>`
52
+
53
+ Opens (or reuses) a lock-protected tab to the seller discovered from the URL's `402` challenge, and pays. `opts`: `{ vault, perUnitCap, totalCap, tabs }`. Reuse one `tabs` map across calls to keep a single open tab per seller; `tab.close()` settles everything spent in one transaction.
54
+
55
+ ```ts
56
+ import { payUrlWithTab } from '@dexterai/x402/tab';
57
+
58
+ const tabs = new Map();
59
+ const { result, tab } = await payUrlWithTab(
60
+ 'https://api.example.com/paid/infer',
61
+ { method: 'GET' },
62
+ { vault, perUnitCap: '0.01', totalCap: '1.00', tabs },
63
+ );
64
+ await tab?.close();
65
+ ```
66
+
67
+ ### `resolveTabTerms(url) → Promise<TabResolution>`
68
+
69
+ Reads a URL's tab terms without paying. Returns `{ kind: 'terms', terms: { counterparty, perRequest, network, settlement } }`, or a non-terms kind when the URL offers no tab.
70
+
71
+ ```ts
72
+ import { resolveTabTerms } from '@dexterai/x402/tab';
73
+
74
+ const resolved = await resolveTabTerms('https://api.example.com/paid/tick');
75
+ if (resolved.kind === 'terms') {
76
+ console.log(resolved.terms.counterparty, resolved.terms.perRequest.human);
77
+ // settlement: { custody: 'non-custodial', protection: 'lock', settleOn: 'close' }
78
+ }
79
+ ```
80
+
81
+ ### `createSolanaVaultAdapter(options)`
82
+
83
+ Builds the `vault` adapter the buyer calls drive through.
84
+
85
+ | Option | Type | Description |
86
+ |---|---|---|
87
+ | `connection` | `Connection` | Your Solana `Connection` (any RPC) |
88
+ | `swigAddress` | `string` | The vault's Swig state account, from enrollment |
89
+ | `vaultPda` | `string` | The vault's gate PDA, from enrollment |
90
+ | `passkeySigner` | `PasskeySignerWithPublicKey` | A `signOperation(operationMessage)` signer (see below) |
91
+ | `feePayer` | `Signer` | Lamport fee payer |
92
+
93
+ The `passkeySigner` is vault 0.19's canonical shape: `{ credentialId, publicKey, signOperation(operationMessage) }`. The signer hashes the operation message internally (`challenge = sha256(op)`, the on-chain `webauthn.rs` law) and the adapter owns only the precompile assembly.
94
+
95
+ - **Browser:** vault's `DexterApiBrowserPasskeySigner` — drops in with no shim.
96
+ - **CLI / server agent:** `passkeySignerFromP256Keypair(kp)` from `@dexterai/x402/tab/adapters/solana`, wrapping a locally-held P-256 keypair.
97
+
98
+ ---
99
+
100
+ ## Sellers
101
+
102
+ `tabOrExactMiddleware` is the recommended default: one middleware that advertises a tab and a one-shot price in a single 402 challenge, so agents pay by tab and one-shot callers pay exact, at the same price.
103
+
104
+ ```ts
105
+ import { tabOrExactMiddleware, requireTab, openSse } from '@dexterai/x402/tab/seller';
106
+ import type { X402Request } from '@dexterai/x402/server';
107
+
108
+ app.get('/paid/tick',
109
+ tabOrExactMiddleware({ connection, sellerPubkey, network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', perUnit: '0.01' }),
110
+ async (req, res) => {
111
+ if ((req as X402Request).x402) { res.json({ data: '...', paidVia: 'exact' }); return; } // exact rail
112
+ const tab = requireTab(req); // tab rail
113
+ const meter = openSse(res, { tab, perUnit: '0.01' });
114
+ await meter.charge(1); // demand a fresh voucher; throws if the cap is exceeded
115
+ meter.send(JSON.stringify({ data: '...' }));
116
+ await meter.end(); // ALWAYS await — persists the final delivered amount
117
+ });
118
+ ```
119
+
120
+ For a tab-only endpoint, compose the two middlewares directly: `tabChallengeMiddleware` (answers voucher-less requests with the standard x402 challenge, so any agent can discover you) before `tabMiddleware` (verifies the per-charge vouchers). Both are exported from `@dexterai/x402/tab/seller`.
121
+
122
+ How the protection works: as the agent spends, accrued charges crystallize on-chain into a reservation against the buyer's wallet — sized to exactly what's accrued, not the whole wallet — so the buyer can't withdraw out from under your charges. One on-chain settle at close pays your `sellerPubkey` for everything metered; you hold no key and sign nothing.
123
+
124
+ ---
125
+
126
+ ## One-shot payments
127
+
128
+ When a charge is a single discrete purchase rather than metered consumption, pay it one-shot. x402 is HTTP's payment protocol: a server returns `402 Payment Required` describing what it wants paid, the client signs and retries, and the resource comes back. USDC on Solana and the major EVM chains, behind one API.
129
+
130
+ ```typescript
131
+ import { payAndFetch, createKeypairWallet, createEvmKeypairWallet } from '@dexterai/x402/client';
132
+
133
+ const solana = await createKeypairWallet(process.env.SOLANA_PRIVATE_KEY);
134
+ const evm = await createEvmKeypairWallet(process.env.EVM_PRIVATE_KEY); // requires: npm install viem
135
+
136
+ const result = await payAndFetch(
137
+ 'https://api.example.com/protected',
138
+ { method: 'GET' },
139
+ { solana, evm },
140
+ {},
141
+ );
142
+
143
+ if (result.ok && result.paid) {
144
+ const data = await result.response.json();
145
+ console.log(`Paid ${result.amountPaid} on ${result.network.bare}, tx ${result.txSignature}`);
146
+ } else if (result.ok && !result.paid) {
147
+ const data = await result.response.json(); // endpoint didn't demand payment; passed through
148
+ } else {
149
+ console.error(result.reason, result.detail);
150
+ }
151
+ ```
152
+
153
+ `payAndFetch` handles x402 v1 and v2 transparently and returns a discriminated `PayResult`: `ok` splits into `paid: true | false`, so a free 200 is distinguishable from a paid one, and expected failures don't throw.
154
+
155
+ ### `payAndFetch(url, init, wallets, opts) → Promise<PayResult>`
156
+
157
+ | Argument | Type | Description |
158
+ |---|---|---|
159
+ | `url` | `string` | Endpoint to fetch |
160
+ | `init` | `RequestInit` | Standard fetch init. Body must be a string. |
161
+ | `wallets` | `WalletSet` | `{ solana?, evm? }`. The SDK picks the chain by what the merchant accepts and what you can pay |
162
+ | `opts` | `PayAndFetchOptions` | `maxAmountAtomic`, `timeoutMs`, `solanaRpcUrl` |
163
+
164
+ `PayResult` is a discriminated union. Narrow on `ok`, then on `paid`:
165
+
166
+ ```typescript
167
+ if (result.ok && result.paid) {
168
+ result.response; result.amountPaid; result.network; result.txSignature;
169
+ } else if (result.ok && !result.paid) {
170
+ result.response; // merchant didn't demand payment; pass-through
171
+ } else {
172
+ result.reason; // 'merchant_rejected' | 'settlement_failed' | 'timeout' | ...
173
+ result.detail;
174
+ }
175
+ ```
176
+
177
+ In React, `useX402Payment` takes wallets from `@solana/wallet-adapter-react` or `wagmi` and returns a `fetch` that pays automatically. Read a settled receipt off any paid response with `getPaymentReceipt(response)`.
178
+
179
+ ---
180
+
181
+ ## Server middleware
182
+
183
+ Protect an endpoint with `x402Middleware`; the handler runs only after payment settles.
184
+
185
+ ```typescript
186
+ import { x402Middleware } from '@dexterai/x402/server';
187
+
188
+ app.get('/api/protected',
189
+ x402Middleware({ payTo: 'YourReceivingAddress', amount: '0.01', network: 'eip155:8453' }),
190
+ (req, res) => res.json({ data: 'protected content' }),
191
+ );
192
+ ```
193
+
194
+ ### `x402Middleware(config)`
195
+
196
+ | Option | Type | Required | Description |
197
+ |---|---|---|---|
198
+ | `payTo` | `string \| { 'solana:*'?, 'eip155:*'?, [caip2]? }` | Yes | Receiver address; map for per-chain receivers |
199
+ | `amount` | `string` | Yes | USD amount, e.g., `'0.01'` |
200
+ | `network` | `string \| string[]` | No | CAIP-2 network(s). Default: Solana mainnet |
201
+ | `scheme` | `'exact' \| 'batch-settlement'` | No | `'batch-settlement'` mounts as a batch-settlement seller |
202
+ | `extensions` | `ResourceServerExtension[]` | No | E.g., `[bazaarExtension()]` |
203
+ | `sponsoredAccess` | `boolean \| { inject?, onMatch? }` | No | Instinct ad-network recommendation injection |
204
+ | `facilitatorUrl` | `string` | No | Override facilitator (default: `x402.dexter.cash`) |
205
+
206
+ Multi-chain endpoints accept any chain the buyer can pay; pass `network` as an array with a `payTo` map for per-chain receivers. Testnets supported: Solana Devnet/Testnet, Base Sepolia, SKALE Base Sepolia.
207
+
208
+ ---
209
+
210
+ ## Batch settlement (EVM)
211
+
212
+ Prepay an escrow once, make many discrete paid calls against it with off-chain vouchers, and settle in a handful of transactions to amortize gas. EVM only.
213
+
214
+ ```ts
215
+ import { openBatchChannel } from '@dexterai/x402/batch-settlement';
216
+
217
+ const escrow = await openBatchChannel({ wallet: evmWallet, network: 'eip155:8453', deposit: '0.30' });
218
+ await escrow.fetch('https://api.example.com/v1/data');
219
+ console.log(escrow.state); // { deposited: '0.3', spent: '0.16', remaining: '0.14' }
220
+ await escrow.close();
221
+ ```
222
+
223
+ State auto-persists and resumes with `resumeBatchChannel({ wallet, network, salt })`. If the seller never settles, reclaim unspent escrow with `forceWithdraw()` then `finalizeWithdraw()`. The seller mounts `createBatchSettlementSeller(config)` as an Express handler; Dexter operates the authorizer, so the seller manages no signing key. The returned handler exposes `.stop()`, `.closeAll()`, `.closeChannel(id)`.
224
+
225
+ ---
226
+
227
+ ## Discovery & sponsored access
228
+
229
+ `bazaarExtension()` plus `declareDiscoveryExtension(config)` attach a spec-compliant `extensions.bazaar` block to a route's 402; extensions are opt-in and failure-isolated, so the payment path is never affected.
230
+
231
+ `sponsoredAccess` injects `_x402_sponsored` into responses; read it with `getSponsoredRecommendations(response)`. When an agent pays through Dexter's facilitator, a matched recommendation can ride along in the receipt and the agent's model may act on it. Campaign creation is x402-gated at `x402ads.io`.
232
+
233
+ Endpoints paid through the facilitator are auto-discovered, named, and quality-tested, then surfaced in `x402_search` across MCP clients — no registration step.
234
+
235
+ ---
236
+
237
+ ## Migration
238
+
239
+ ### Migrating to 5.0.0 (breaking)
240
+
241
+ Two changes, both about packaging and the passkey signer — the payment path itself is unchanged.
242
+
243
+ 1. **`@dexterai/vault` is now a peer dependency** (`>=0.19`), not bundled. The tab adapter and your app share ONE vault instance, so there are no duplicate copies and no `instanceof`/type mismatches across packages. Install it alongside: `npm install @dexterai/x402 @dexterai/vault`.
244
+ 2. **The passkey signer contract is `signOperation(operationMessage)`**, replacing the old `sign(challenge)`. The adapter now hands the signer the RAW operation message and the signer hashes it internally (`challenge = sha256(op)`, the on-chain `webauthn.rs` law). If you wrote a custom signer against `sign(challenge)`, rename the method to `signOperation` and delete your pre-hash — pass the message straight through. Vault's `DexterApiBrowserPasskeySigner` (browser) and this package's `passkeySignerFromP256Keypair` (node/agent) already conform.
245
+
246
+ To pin the old surface, stay on `@dexterai/x402@^4`.
247
+
248
+ ### Removed in v4.0.0
249
+
250
+ The v1-era helpers were removed in `4.0.0`. The payment engine is unchanged — the canonical entrypoints have done their jobs since 3.x:
251
+
252
+ | Removed | Use instead |
253
+ | --- | --- |
254
+ | `createX402Client(...).fetch(url)` | `payAndFetch(url, init, wallets)` (client) |
255
+ | `wrapFetch(fetch, opts)` | `payAndFetch` (client) — or pass your `WalletSet` directly |
256
+ | `x402AccessPass`, `x402BrowserSupport` | `x402Middleware` (server) |
257
+ | `createDynamicPricing`, `formatPricing` | compute the price per request in your handler, pass it to `x402Middleware` |
258
+ | `createTokenPricing`, `countTokens`, `MODEL_REGISTRY` + model getters | gone — these wrapped a hardcoded Jan-2026 OpenAI snapshot that goes stale; price requests with your model provider's live API and pass the amount to `x402Middleware` |
259
+ | `stripePayTo` | a `PayToProvider` map on `x402Middleware` |
260
+ | `useAccessPass` (react) | `useX402Payment` (react) |
261
+
262
+ `payAndFetch` speaks both x402 v1 and v2 and returns a discriminated `PayResult`, so it covers everything the old clients did. **EVM one-shot pay-per-call is unchanged** — `payAndFetch` is the EVM path until Tabs reaches EVM.
263
+
264
+ ---
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ npm run build # ESM + CJS
270
+ npm run dev # Watch mode
271
+ npm run typecheck
272
+ npm test # vitest
273
+ ```
274
+
275
+ MIT. See [LICENSE](./LICENSE).
@@ -1 +1 @@
1
- "use strict";var te=Object.create;var f=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var re=Object.getPrototypeOf,se=Object.prototype.hasOwnProperty;var oe=(e,t)=>{for(var n in t)f(e,n,{get:t[n],enumerable:!0})},V=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ie(t))!se.call(e,r)&&r!==n&&f(e,r,{get:()=>t[r],enumerable:!(i=ne(t,r))||i.enumerable});return e};var L=(e,t,n)=>(n=e!=null?te(re(e)):{},V(t||!e||!e.__esModule?f(n,"default",{value:e,enumerable:!0}):n,e)),ae=e=>V(f({},"__esModule",{value:!0}),e);var Pe={};oe(Pe,{buildAdapterRegisterInstruction:()=>Q,createSolanaVaultAdapter:()=>he,deriveChannelId:()=>q,passkeySignerFromP256Keypair:()=>z});module.exports=ae(Pe);var c=require("@solana/web3.js"),Z=require("@solana/spl-token");var ce="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",pe="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",le="solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";var w="eip155:8453",U="eip155:84532",C="eip155:42161",R="eip155:137",O="eip155:10",D="eip155:43114",N="eip155:56",T="eip155:1187947933",_="eip155:324705682",I="eip155:1";var W="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";var ue="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",ye="0x55d398326f99059fF775485246999027B3197955",F="0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",Ee={[N]:F,[w]:ue,[U]:"0x036CbD53842c5426634e7929541eC2318f3dCF7e",[C]:"0xaf88d065e77c8cC2239327C5EDb3A432268e5831",[R]:"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",[O]:"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",[D]:"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",[T]:"0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",[_]:"0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",[I]:"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},Ke={[ye]:{symbol:"USDT",decimals:18},[F]:{symbol:"USDC",decimals:18}};var ve={[N]:56,[w]:8453,[U]:84532,[C]:42161,[R]:137,[O]:10,[D]:43114,[T]:1187947933,[_]:324705682,[I]:1},we={[ce]:"https://api.dexter.cash/api/solana/rpc",[pe]:"https://api.devnet.solana.com",[le]:"https://api.testnet.solana.com"},Ue={[N]:"https://api.dexter.cash/api/evm/bsc/rpc",[w]:"https://api.dexter.cash/api/base/rpc",[U]:"https://sepolia.base.org",[C]:"https://api.dexter.cash/api/evm/arbitrum/rpc",[R]:"https://api.dexter.cash/api/evm/polygon/rpc",[O]:"https://api.dexter.cash/api/evm/optimism/rpc",[D]:"https://api.dexter.cash/api/evm/avalanche/rpc",[T]:"https://skale-base.skalenodes.com/v1/base",[_]:"https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",[I]:"https://eth.llamarpc.com"};var a=require("@dexterai/vault/instructions"),P=require("@dexterai/vault/precompile"),m=require("@dexterai/vault/constants");var s=require("@dexterai/vault/messages");var k=L(require("tweetnacl"),1);var $=require("@noble/hashes/sha256");function J(){let e=k.default.sign.keyPair();return{publicKey:e.publicKey,privateKey:e.secretKey}}function H(e,t,n){return{publicKey:e.publicKey,privateKey:e.privateKey,scope:t,registration:n}}function G(e,t,n){if(n.length!==32)throw new Error(`channelIdBytes must be 32 bytes, got ${n.length}`);let i=BigInt(t.cumulativeAmount),r=BigInt(e.scope.maxAmountAtomic);if(i>r)throw new Error(`voucher cumulative ${i} exceeds session cap ${r}`);let o=Math.floor(Date.now()/1e3);if(o>=e.scope.expiresAtUnix)throw new Error(`session expired at ${e.scope.expiresAtUnix}, now ${o}`);let p=(0,s.voucherPayloadMessage)({channelId:n,cumulativeAmount:i,sequenceNumber:t.sequenceNumber}),l=k.default.sign.detached(p,e.privateKey);return{payload:t,sessionPublicKey:e.publicKey,sessionRegistration:e.registration,sessionSignature:l}}function b(e){if(!/^\d+$/.test(e))throw new Error(`atomic amount must be a non-negative integer string, got "${e}"`);return BigInt(e)}function q(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),i=$.sha256.create();return i.update(e.vaultPda.toBytes()),i.update(n),i.update(t),i.digest()}var d=require("@dexterai/vault/session"),g=require("@noble/hashes/sha256");var X=require("@noble/curves/p256"),E=require("@noble/hashes/sha256"),Y="dexter.cash";function me(e){return Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function de(e,t=`https://${Y}`){let i={type:"webauthn.get",challenge:me(e),origin:t,crossOrigin:!1};return new TextEncoder().encode(JSON.stringify(i))}function ge(e=1){let t=(0,E.sha256)(new TextEncoder().encode(Y)),n=new Uint8Array(37);return n.set(t,0),n[32]=5,new DataView(n.buffer).setUint32(33,e,!1),n}function Ae(e,t){let n=de(t),i=ge(1),r=new Uint8Array(i.length+32);r.set(i,0),r.set((0,E.sha256)(n),i.length);let o=(0,E.sha256)(r);return{signature:X.p256.sign(o,e.privateKey,{lowS:!0}).toCompactRawBytes(),clientDataJSON:n,authenticatorData:i}}function z(e){return{publicKey:e.publicKey,sign:async t=>Ae(e,t)}}function Q(e){let t=(0,a.deriveSwigWalletAddress)(e.swigAddress),n=(0,Z.getAssociatedTokenAddressSync)(new c.PublicKey(W),t,!0);return(0,a.buildRegisterSessionKeyInstruction)({vaultPda:e.vaultPda,sessionPubkey:e.sessionPubkey,maxAmount:e.maxAmount,maxRevolvingCapacity:e.maxRevolvingCapacity,expiresAt:e.expiresAt,allowedCounterparty:e.allowedCounterparty,nonce:e.nonce,swigAddress:e.swigAddress,vaultUsdcAta:n,clientDataJSON:e.clientDataJSON,authenticatorData:e.authenticatorData,payer:e.payer,siblingSessionPdas:e.siblingSessionPdas})}var B=class{network="solana:mainnet";swigAddress;vaultPda;connection;vaultPdaKey;passkey;feePayer;confirmOptions;constructor(t){this.connection=t.connection,this.swigAddress=typeof t.swigAddress=="string"?t.swigAddress:t.swigAddress.toBase58(),this.vaultPdaKey=typeof t.vaultPda=="string"?new c.PublicKey(t.vaultPda):t.vaultPda,this.vaultPda=this.vaultPdaKey.toBase58(),this.passkey=t.passkeySigner,this.feePayer=t.feePayer,this.confirmOptions=t.confirmOptions??{commitment:"confirmed"}}async authorizeSession(t){let n=new c.PublicKey(t.allowedCounterparty),i=b(t.revolvingCapacityAtomic??t.maxAmountAtomic),r=J(),o=Se(),p=(0,s.sessionRegisterMessage)({programId:m.DEXTER_VAULT_PROGRAM_ID,vaultPda:this.vaultPdaKey,sessionPubkey:r.publicKey,maxAmount:b(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:o}),l=(0,g.sha256)(p),{signature:A,clientDataJSON:h,authenticatorData:S}=await this.passkey.sign(l),K=j(S,(0,g.sha256)(h)),u=(0,P.buildSecp256r1VerifyInstruction)(this.passkey.publicKey,A,K),x=(0,d.sessionPdasOf)(await(0,d.fetchVaultSessionAccounts)(this.connection,this.vaultPdaKey)),v=Q({vaultPda:this.vaultPdaKey,swigAddress:new c.PublicKey(this.swigAddress),sessionPubkey:r.publicKey,maxAmount:b(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:o,clientDataJSON:h,authenticatorData:S,payer:this.feePayer.publicKey,siblingSessionPdas:x}),y=new c.Transaction().add(u,v);y.feePayer=this.feePayer.publicKey;let{blockhash:M}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);y.recentBlockhash=M,y.sign(this.feePayer);let ee=await this.connection.sendRawTransaction(y.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:ee,blockhash:M,lastValidBlockHeight:(await this.connection.getLatestBlockhash(this.confirmOptions.commitment)).lastValidBlockHeight},this.confirmOptions.commitment),await(0,d.waitForSession)(this.connection,this.vaultPdaKey,n,{expectedSessionPubkey:r.publicKey,timeoutMs:2e4}),H(r,t,p)}async signWithSession(t,n){let i=await xe(n.channelId);return G(t,n,i)}async signOpenTab(t,n){return t.registration}async signCloseTab(t,n,i){let r=(0,s.sessionRevokeMessage)({programId:m.DEXTER_VAULT_PROGRAM_ID,vaultPda:this.vaultPdaKey,sessionPubkey:t.publicKey}),o=(0,g.sha256)(r),{signature:p,clientDataJSON:l,authenticatorData:A}=await this.passkey.sign(o),h=j(A,(0,g.sha256)(l)),S=(0,P.buildSecp256r1VerifyInstruction)(this.passkey.publicKey,p,h),K=(0,a.buildRevokeSessionKeyInstruction)({vaultPda:this.vaultPdaKey,allowedCounterparty:new c.PublicKey(t.scope.allowedCounterparty),clientDataJSON:l,authenticatorData:A}),u=new c.Transaction().add(S,K);u.feePayer=this.feePayer.publicKey;let{blockhash:x,lastValidBlockHeight:v}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);u.recentBlockhash=x,u.sign(this.feePayer);let y=await this.connection.sendRawTransaction(u.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:y,blockhash:x,lastValidBlockHeight:v},this.confirmOptions.commitment),r}};function he(e){return new B(e)}function j(e,t){let n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}function Se(){return Math.floor(Math.random()*4294967295)>>>0}async function xe(e){if(/^[0-9a-f]{64}$/i.test(e))return fe(e);let{sha256:t}=await import("@noble/hashes/sha256");return t(new TextEncoder().encode(e))}function fe(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}0&&(module.exports={buildAdapterRegisterInstruction,createSolanaVaultAdapter,deriveChannelId,passkeySignerFromP256Keypair});
1
+ "use strict";var ee=Object.create;var x=Object.defineProperty;var te=Object.getOwnPropertyDescriptor;var ne=Object.getOwnPropertyNames;var ie=Object.getPrototypeOf,re=Object.prototype.hasOwnProperty;var se=(e,t)=>{for(var n in t)x(e,n,{get:t[n],enumerable:!0})},M=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ne(t))!re.call(e,r)&&r!==n&&x(e,r,{get:()=>t[r],enumerable:!(i=te(t,r))||i.enumerable});return e};var V=(e,t,n)=>(n=e!=null?ee(ie(e)):{},M(t||!e||!e.__esModule?x(n,"default",{value:e,enumerable:!0}):n,e)),oe=e=>M(x({},"__esModule",{value:!0}),e);var fe={};se(fe,{buildAdapterRegisterInstruction:()=>Z,createSolanaVaultAdapter:()=>Ae,deriveChannelId:()=>$,passkeySignerFromP256Keypair:()=>Y});module.exports=oe(fe);var c=require("@solana/web3.js"),j=require("@solana/spl-token");var ae="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",ce="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",pe="solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";var K="eip155:8453",v="eip155:84532",w="eip155:42161",C="eip155:137",R="eip155:10",O="eip155:43114",U="eip155:56",T="eip155:1187947933",_="eip155:324705682",N="eip155:1";var L="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";var ue="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",le="0x55d398326f99059fF775485246999027B3197955",W="0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",be={[U]:W,[K]:ue,[v]:"0x036CbD53842c5426634e7929541eC2318f3dCF7e",[w]:"0xaf88d065e77c8cC2239327C5EDb3A432268e5831",[C]:"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",[R]:"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",[O]:"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",[T]:"0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",[_]:"0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",[N]:"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},Ee={[le]:{symbol:"USDT",decimals:18},[W]:{symbol:"USDC",decimals:18}};var Ke={[U]:56,[K]:8453,[v]:84532,[w]:42161,[C]:137,[R]:10,[O]:43114,[T]:1187947933,[_]:324705682,[N]:1},ve={[ae]:"https://api.dexter.cash/api/solana/rpc",[ce]:"https://api.devnet.solana.com",[pe]:"https://api.testnet.solana.com"},we={[U]:"https://api.dexter.cash/api/evm/bsc/rpc",[K]:"https://api.dexter.cash/api/base/rpc",[v]:"https://sepolia.base.org",[w]:"https://api.dexter.cash/api/evm/arbitrum/rpc",[C]:"https://api.dexter.cash/api/evm/polygon/rpc",[R]:"https://api.dexter.cash/api/evm/optimism/rpc",[O]:"https://api.dexter.cash/api/evm/avalanche/rpc",[T]:"https://skale-base.skalenodes.com/v1/base",[_]:"https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",[N]:"https://eth.llamarpc.com"};var a=require("@dexterai/vault/instructions"),f=require("@dexterai/vault/precompile"),m=require("@dexterai/vault/constants");var s=require("@dexterai/vault/messages");var D=V(require("tweetnacl"),1);var G=require("@noble/hashes/sha256");function F(){let e=D.default.sign.keyPair();return{publicKey:e.publicKey,privateKey:e.secretKey}}function J(e,t,n){return{publicKey:e.publicKey,privateKey:e.privateKey,scope:t,registration:n}}function H(e,t,n){if(n.length!==32)throw new Error(`channelIdBytes must be 32 bytes, got ${n.length}`);let i=BigInt(t.cumulativeAmount),r=BigInt(e.scope.maxAmountAtomic);if(i>r)throw new Error(`voucher cumulative ${i} exceeds session cap ${r}`);let o=Math.floor(Date.now()/1e3);if(o>=e.scope.expiresAtUnix)throw new Error(`session expired at ${e.scope.expiresAtUnix}, now ${o}`);let p=(0,s.voucherPayloadMessage)({channelId:n,cumulativeAmount:i,sequenceNumber:t.sequenceNumber}),u=D.default.sign.detached(p,e.privateKey);return{payload:t,sessionPublicKey:e.publicKey,sessionRegistration:e.registration,sessionSignature:u}}function P(e){if(!/^\d+$/.test(e))throw new Error(`atomic amount must be a non-negative integer string, got "${e}"`);return BigInt(e)}function $(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),i=G.sha256.create();return i.update(e.vaultPda.toBytes()),i.update(n),i.update(t),i.digest()}var d=require("@dexterai/vault/session"),I=require("@noble/hashes/sha256");var q=require("@noble/curves/p256"),g=require("@noble/hashes/sha256"),X="dexter.cash";function ye(e){return Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function me(e,t=`https://${X}`){let i={type:"webauthn.get",challenge:ye(e),origin:t,crossOrigin:!1};return new TextEncoder().encode(JSON.stringify(i))}function de(e=1){let t=(0,g.sha256)(new TextEncoder().encode(X)),n=new Uint8Array(37);return n.set(t,0),n[32]=5,new DataView(n.buffer).setUint32(33,e,!1),n}function ge(e,t){let n=me(t),i=de(1),r=new Uint8Array(i.length+32);r.set(i,0),r.set((0,g.sha256)(n),i.length);let o=(0,g.sha256)(r);return{signature:q.p256.sign(o,e.privateKey,{lowS:!0}).toCompactRawBytes(),clientDataJSON:n,authenticatorData:i}}function Y(e){return{credentialId:new Uint8Array(0),publicKey:e.publicKey,signOperation:async t=>ge(e,(0,g.sha256)(t))}}function Z(e){let t=(0,a.deriveSwigWalletAddress)(e.swigAddress),n=(0,j.getAssociatedTokenAddressSync)(new c.PublicKey(L),t,!0);return(0,a.buildRegisterSessionKeyInstruction)({vaultPda:e.vaultPda,sessionPubkey:e.sessionPubkey,maxAmount:e.maxAmount,maxRevolvingCapacity:e.maxRevolvingCapacity,expiresAt:e.expiresAt,allowedCounterparty:e.allowedCounterparty,nonce:e.nonce,swigAddress:e.swigAddress,vaultUsdcAta:n,clientDataJSON:e.clientDataJSON,authenticatorData:e.authenticatorData,payer:e.payer,siblingSessionPdas:e.siblingSessionPdas})}var k=class{network="solana:mainnet";swigAddress;vaultPda;connection;vaultPdaKey;passkey;feePayer;confirmOptions;constructor(t){this.connection=t.connection,this.swigAddress=typeof t.swigAddress=="string"?t.swigAddress:t.swigAddress.toBase58(),this.vaultPdaKey=typeof t.vaultPda=="string"?new c.PublicKey(t.vaultPda):t.vaultPda,this.vaultPda=this.vaultPdaKey.toBase58(),this.passkey=t.passkeySigner,this.feePayer=t.feePayer,this.confirmOptions=t.confirmOptions??{commitment:"confirmed"}}async authorizeSession(t){let n=new c.PublicKey(t.allowedCounterparty),i=P(t.revolvingCapacityAtomic??t.maxAmountAtomic),r=F(),o=he(),p=(0,s.sessionRegisterMessage)({programId:m.DEXTER_VAULT_PROGRAM_ID,vaultPda:this.vaultPdaKey,sessionPubkey:r.publicKey,maxAmount:P(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:o}),{signature:u,clientDataJSON:A,authenticatorData:h}=await this.passkey.signOperation(p),b=z(h,(0,I.sha256)(A)),l=(0,f.buildSecp256r1VerifyInstruction)(this.passkey.publicKey,u,b),S=(0,d.sessionPdasOf)(await(0,d.fetchVaultSessionAccounts)(this.connection,this.vaultPdaKey)),E=Z({vaultPda:this.vaultPdaKey,swigAddress:new c.PublicKey(this.swigAddress),sessionPubkey:r.publicKey,maxAmount:P(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:o,clientDataJSON:A,authenticatorData:h,payer:this.feePayer.publicKey,siblingSessionPdas:S}),y=new c.Transaction().add(l,E);y.feePayer=this.feePayer.publicKey;let{blockhash:B}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);y.recentBlockhash=B,y.sign(this.feePayer);let Q=await this.connection.sendRawTransaction(y.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:Q,blockhash:B,lastValidBlockHeight:(await this.connection.getLatestBlockhash(this.confirmOptions.commitment)).lastValidBlockHeight},this.confirmOptions.commitment),await(0,d.waitForSession)(this.connection,this.vaultPdaKey,n,{expectedSessionPubkey:r.publicKey,timeoutMs:2e4}),J(r,t,p)}async signWithSession(t,n){let i=await Se(n.channelId);return H(t,n,i)}async signOpenTab(t,n){return t.registration}async signCloseTab(t,n,i){let r=(0,s.sessionRevokeMessage)({programId:m.DEXTER_VAULT_PROGRAM_ID,vaultPda:this.vaultPdaKey,sessionPubkey:t.publicKey}),{signature:o,clientDataJSON:p,authenticatorData:u}=await this.passkey.signOperation(r),A=z(u,(0,I.sha256)(p)),h=(0,f.buildSecp256r1VerifyInstruction)(this.passkey.publicKey,o,A),b=(0,a.buildRevokeSessionKeyInstruction)({vaultPda:this.vaultPdaKey,allowedCounterparty:new c.PublicKey(t.scope.allowedCounterparty),clientDataJSON:p,authenticatorData:u}),l=new c.Transaction().add(h,b);l.feePayer=this.feePayer.publicKey;let{blockhash:S,lastValidBlockHeight:E}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);l.recentBlockhash=S,l.sign(this.feePayer);let y=await this.connection.sendRawTransaction(l.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:y,blockhash:S,lastValidBlockHeight:E},this.confirmOptions.commitment),r}};function Ae(e){return new k(e)}function z(e,t){let n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}function he(){return Math.floor(Math.random()*4294967295)>>>0}async function Se(e){if(/^[0-9a-f]{64}$/i.test(e))return xe(e);let{sha256:t}=await import("@noble/hashes/sha256");return t(new TextEncoder().encode(e))}function xe(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}0&&(module.exports={buildAdapterRegisterInstruction,createSolanaVaultAdapter,deriveChannelId,passkeySignerFromP256Keypair});
@@ -49,6 +49,7 @@ declare function deriveChannelId(args: {
49
49
  * dexter-vault/tests/helpers/secp256r1.ts (signOperationWithPasskey).
50
50
  * Keep them in lockstep.
51
51
  */
52
+
52
53
  interface P256Keypair {
53
54
  /** 33-byte SEC1 compressed public key (the form the vault stores). */
54
55
  publicKey: Uint8Array;
@@ -56,20 +57,20 @@ interface P256Keypair {
56
57
  privateKey: Uint8Array;
57
58
  }
58
59
  /**
59
- * Build a unified `PasskeySignerWithPublicKey` (vault's canonical shape)
60
+ * Build a unified `PasskeySignerWithPublicKey` (vault's canonical 0.19 shape)
60
61
  * from a locally-held P-256 keypair — the node/CLI path. Returns
61
- * `{ publicKey, sign(challenge) }`; the adapter computes the challenge and
62
- * rebuilds the precompile message. Drop-in equivalent to vault's
63
- * DexterApiBrowserPasskeySigner.
62
+ * `{ credentialId, publicKey, signOperation(operationMessage) }`. The signer
63
+ * owns the hashing locus: it computes `challenge = sha256(operationMessage)`
64
+ * internally (mirroring vault's `DexterApiBrowserPasskeySigner.signOperation`),
65
+ * so the adapter hands it the RAW operation message and never pre-hashes.
66
+ * The adapter still rebuilds the precompile message from the returned bytes.
67
+ *
68
+ * `credentialId` is empty for the node path — there is no platform
69
+ * authenticator credential; the on-chain verifier authenticates via the
70
+ * secp256r1 precompile over the clientDataJSON/authenticatorData, not the
71
+ * credentialId, so an empty value is correct here.
64
72
  */
65
- declare function passkeySignerFromP256Keypair(kp: P256Keypair): {
66
- publicKey: Uint8Array;
67
- sign(challenge: Uint8Array): Promise<{
68
- signature: Uint8Array;
69
- clientDataJSON: Uint8Array;
70
- authenticatorData: Uint8Array;
71
- }>;
72
- };
73
+ declare function passkeySignerFromP256Keypair(kp: P256Keypair): PasskeySignerWithPublicKey;
73
74
 
74
75
  interface CreateSolanaVaultAdapterOptions {
75
76
  /** RPC the adapter uses to submit txs. The buyer can pass their own
@@ -49,6 +49,7 @@ declare function deriveChannelId(args: {
49
49
  * dexter-vault/tests/helpers/secp256r1.ts (signOperationWithPasskey).
50
50
  * Keep them in lockstep.
51
51
  */
52
+
52
53
  interface P256Keypair {
53
54
  /** 33-byte SEC1 compressed public key (the form the vault stores). */
54
55
  publicKey: Uint8Array;
@@ -56,20 +57,20 @@ interface P256Keypair {
56
57
  privateKey: Uint8Array;
57
58
  }
58
59
  /**
59
- * Build a unified `PasskeySignerWithPublicKey` (vault's canonical shape)
60
+ * Build a unified `PasskeySignerWithPublicKey` (vault's canonical 0.19 shape)
60
61
  * from a locally-held P-256 keypair — the node/CLI path. Returns
61
- * `{ publicKey, sign(challenge) }`; the adapter computes the challenge and
62
- * rebuilds the precompile message. Drop-in equivalent to vault's
63
- * DexterApiBrowserPasskeySigner.
62
+ * `{ credentialId, publicKey, signOperation(operationMessage) }`. The signer
63
+ * owns the hashing locus: it computes `challenge = sha256(operationMessage)`
64
+ * internally (mirroring vault's `DexterApiBrowserPasskeySigner.signOperation`),
65
+ * so the adapter hands it the RAW operation message and never pre-hashes.
66
+ * The adapter still rebuilds the precompile message from the returned bytes.
67
+ *
68
+ * `credentialId` is empty for the node path — there is no platform
69
+ * authenticator credential; the on-chain verifier authenticates via the
70
+ * secp256r1 precompile over the clientDataJSON/authenticatorData, not the
71
+ * credentialId, so an empty value is correct here.
64
72
  */
65
- declare function passkeySignerFromP256Keypair(kp: P256Keypair): {
66
- publicKey: Uint8Array;
67
- sign(challenge: Uint8Array): Promise<{
68
- signature: Uint8Array;
69
- clientDataJSON: Uint8Array;
70
- authenticatorData: Uint8Array;
71
- }>;
72
- };
73
+ declare function passkeySignerFromP256Keypair(kp: P256Keypair): PasskeySignerWithPublicKey;
73
74
 
74
75
  interface CreateSolanaVaultAdapterOptions {
75
76
  /** RPC the adapter uses to submit txs. The buyer can pass their own
@@ -1 +1 @@
1
- import{PublicKey as l,Transaction as q}from"@solana/web3.js";import{getAssociatedTokenAddressSync as pe}from"@solana/spl-token";var z="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",j="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",Z="solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";var x="eip155:8453",f="eip155:84532",P="eip155:42161",b="eip155:137",E="eip155:10",K="eip155:43114",v="eip155:56",w="eip155:1187947933",U="eip155:324705682",C="eip155:1";var _="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";var Q="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",ee="0x55d398326f99059fF775485246999027B3197955",I="0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",he={[v]:I,[x]:Q,[f]:"0x036CbD53842c5426634e7929541eC2318f3dCF7e",[P]:"0xaf88d065e77c8cC2239327C5EDb3A432268e5831",[b]:"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",[E]:"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",[K]:"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",[w]:"0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",[U]:"0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",[C]:"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},Se={[ee]:{symbol:"USDT",decimals:18},[I]:{symbol:"USDC",decimals:18}};var xe={[v]:56,[x]:8453,[f]:84532,[P]:42161,[b]:137,[E]:10,[K]:43114,[w]:1187947933,[U]:324705682,[C]:1},fe={[z]:"https://api.dexter.cash/api/solana/rpc",[j]:"https://api.devnet.solana.com",[Z]:"https://api.testnet.solana.com"},Pe={[v]:"https://api.dexter.cash/api/evm/bsc/rpc",[x]:"https://api.dexter.cash/api/base/rpc",[f]:"https://sepolia.base.org",[P]:"https://api.dexter.cash/api/evm/arbitrum/rpc",[b]:"https://api.dexter.cash/api/evm/polygon/rpc",[E]:"https://api.dexter.cash/api/evm/optimism/rpc",[K]:"https://api.dexter.cash/api/evm/avalanche/rpc",[w]:"https://skale-base.skalenodes.com/v1/base",[U]:"https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",[C]:"https://eth.llamarpc.com"};import{buildRegisterSessionKeyInstruction as k,buildRevokeSessionKeyInstruction as B,deriveSwigWalletAddress as M}from"@dexterai/vault/instructions";import{buildSecp256r1VerifyInstruction as R}from"@dexterai/vault/precompile";import{DEXTER_VAULT_PROGRAM_ID as O,SECP256R1_PROGRAM_ID as Ue,INSTRUCTIONS_SYSVAR_ID as Ce}from"@dexterai/vault/constants";import{sessionRegisterMessage as V,sessionRevokeMessage as L,voucherPayloadMessage as W,buildVoucherMessage as De}from"@dexterai/vault/messages";import F from"tweetnacl";import{sha256 as te}from"@noble/hashes/sha256";function J(){let e=F.sign.keyPair();return{publicKey:e.publicKey,privateKey:e.secretKey}}function H(e,t,n){return{publicKey:e.publicKey,privateKey:e.privateKey,scope:t,registration:n}}function G(e,t,n){if(n.length!==32)throw new Error(`channelIdBytes must be 32 bytes, got ${n.length}`);let i=BigInt(t.cumulativeAmount),r=BigInt(e.scope.maxAmountAtomic);if(i>r)throw new Error(`voucher cumulative ${i} exceeds session cap ${r}`);let s=Math.floor(Date.now()/1e3);if(s>=e.scope.expiresAtUnix)throw new Error(`session expired at ${e.scope.expiresAtUnix}, now ${s}`);let o=W({channelId:n,cumulativeAmount:i,sequenceNumber:t.sequenceNumber}),a=F.sign.detached(o,e.privateKey);return{payload:t,sessionPublicKey:e.publicKey,sessionRegistration:e.registration,sessionSignature:a}}function g(e){if(!/^\d+$/.test(e))throw new Error(`atomic amount must be a non-negative integer string, got "${e}"`);return BigInt(e)}function ne(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),i=te.create();return i.update(e.vaultPda.toBytes()),i.update(n),i.update(t),i.digest()}import{fetchVaultSessionAccounts as le,sessionPdasOf as ue,waitForSession as ye}from"@dexterai/vault/session";import{sha256 as A}from"@noble/hashes/sha256";import{p256 as ie}from"@noble/curves/p256";import{sha256 as D}from"@noble/hashes/sha256";var $="dexter.cash";function re(e){return Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function se(e,t=`https://${$}`){let i={type:"webauthn.get",challenge:re(e),origin:t,crossOrigin:!1};return new TextEncoder().encode(JSON.stringify(i))}function oe(e=1){let t=D(new TextEncoder().encode($)),n=new Uint8Array(37);return n.set(t,0),n[32]=5,new DataView(n.buffer).setUint32(33,e,!1),n}function ae(e,t){let n=se(t),i=oe(1),r=new Uint8Array(i.length+32);r.set(i,0),r.set(D(n),i.length);let s=D(r);return{signature:ie.sign(s,e.privateKey,{lowS:!0}).toCompactRawBytes(),clientDataJSON:n,authenticatorData:i}}function ce(e){return{publicKey:e.publicKey,sign:async t=>ae(e,t)}}function me(e){let t=M(e.swigAddress),n=pe(new l(_),t,!0);return k({vaultPda:e.vaultPda,sessionPubkey:e.sessionPubkey,maxAmount:e.maxAmount,maxRevolvingCapacity:e.maxRevolvingCapacity,expiresAt:e.expiresAt,allowedCounterparty:e.allowedCounterparty,nonce:e.nonce,swigAddress:e.swigAddress,vaultUsdcAta:n,clientDataJSON:e.clientDataJSON,authenticatorData:e.authenticatorData,payer:e.payer,siblingSessionPdas:e.siblingSessionPdas})}var N=class{network="solana:mainnet";swigAddress;vaultPda;connection;vaultPdaKey;passkey;feePayer;confirmOptions;constructor(t){this.connection=t.connection,this.swigAddress=typeof t.swigAddress=="string"?t.swigAddress:t.swigAddress.toBase58(),this.vaultPdaKey=typeof t.vaultPda=="string"?new l(t.vaultPda):t.vaultPda,this.vaultPda=this.vaultPdaKey.toBase58(),this.passkey=t.passkeySigner,this.feePayer=t.feePayer,this.confirmOptions=t.confirmOptions??{commitment:"confirmed"}}async authorizeSession(t){let n=new l(t.allowedCounterparty),i=g(t.revolvingCapacityAtomic??t.maxAmountAtomic),r=J(),s=de(),o=V({programId:O,vaultPda:this.vaultPdaKey,sessionPubkey:r.publicKey,maxAmount:g(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s}),a=A(o),{signature:u,clientDataJSON:y,authenticatorData:m}=await this.passkey.sign(a),h=X(m,A(y)),c=R(this.passkey.publicKey,u,h),d=ue(await le(this.connection,this.vaultPdaKey)),S=me({vaultPda:this.vaultPdaKey,swigAddress:new l(this.swigAddress),sessionPubkey:r.publicKey,maxAmount:g(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s,clientDataJSON:y,authenticatorData:m,payer:this.feePayer.publicKey,siblingSessionPdas:d}),p=new q().add(c,S);p.feePayer=this.feePayer.publicKey;let{blockhash:T}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);p.recentBlockhash=T,p.sign(this.feePayer);let Y=await this.connection.sendRawTransaction(p.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:Y,blockhash:T,lastValidBlockHeight:(await this.connection.getLatestBlockhash(this.confirmOptions.commitment)).lastValidBlockHeight},this.confirmOptions.commitment),await ye(this.connection,this.vaultPdaKey,n,{expectedSessionPubkey:r.publicKey,timeoutMs:2e4}),H(r,t,o)}async signWithSession(t,n){let i=await ge(n.channelId);return G(t,n,i)}async signOpenTab(t,n){return t.registration}async signCloseTab(t,n,i){let r=L({programId:O,vaultPda:this.vaultPdaKey,sessionPubkey:t.publicKey}),s=A(r),{signature:o,clientDataJSON:a,authenticatorData:u}=await this.passkey.sign(s),y=X(u,A(a)),m=R(this.passkey.publicKey,o,y),h=B({vaultPda:this.vaultPdaKey,allowedCounterparty:new l(t.scope.allowedCounterparty),clientDataJSON:a,authenticatorData:u}),c=new q().add(m,h);c.feePayer=this.feePayer.publicKey;let{blockhash:d,lastValidBlockHeight:S}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);c.recentBlockhash=d,c.sign(this.feePayer);let p=await this.connection.sendRawTransaction(c.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:p,blockhash:d,lastValidBlockHeight:S},this.confirmOptions.commitment),r}};function Xe(e){return new N(e)}function X(e,t){let n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}function de(){return Math.floor(Math.random()*4294967295)>>>0}async function ge(e){if(/^[0-9a-f]{64}$/i.test(e))return Ae(e);let{sha256:t}=await import("@noble/hashes/sha256");return t(new TextEncoder().encode(e))}function Ae(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}export{me as buildAdapterRegisterInstruction,Xe as createSolanaVaultAdapter,ne as deriveChannelId,ce as passkeySignerFromP256Keypair};
1
+ import{PublicKey as u,Transaction as G}from"@solana/web3.js";import{getAssociatedTokenAddressSync as ce}from"@solana/spl-token";var Y="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",z="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",j="solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";var S="eip155:8453",x="eip155:84532",f="eip155:42161",P="eip155:137",b="eip155:10",E="eip155:43114",K="eip155:56",v="eip155:1187947933",w="eip155:324705682",C="eip155:1";var _="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";var Z="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",Q="0x55d398326f99059fF775485246999027B3197955",N="0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",Ae={[K]:N,[S]:Z,[x]:"0x036CbD53842c5426634e7929541eC2318f3dCF7e",[f]:"0xaf88d065e77c8cC2239327C5EDb3A432268e5831",[P]:"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",[b]:"0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",[E]:"0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",[v]:"0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",[w]:"0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",[C]:"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"},he={[Q]:{symbol:"USDT",decimals:18},[N]:{symbol:"USDC",decimals:18}};var Se={[K]:56,[S]:8453,[x]:84532,[f]:42161,[P]:137,[b]:10,[E]:43114,[v]:1187947933,[w]:324705682,[C]:1},xe={[Y]:"https://api.dexter.cash/api/solana/rpc",[z]:"https://api.devnet.solana.com",[j]:"https://api.testnet.solana.com"},fe={[K]:"https://api.dexter.cash/api/evm/bsc/rpc",[S]:"https://api.dexter.cash/api/base/rpc",[x]:"https://sepolia.base.org",[f]:"https://api.dexter.cash/api/evm/arbitrum/rpc",[P]:"https://api.dexter.cash/api/evm/polygon/rpc",[b]:"https://api.dexter.cash/api/evm/optimism/rpc",[E]:"https://api.dexter.cash/api/evm/avalanche/rpc",[v]:"https://skale-base.skalenodes.com/v1/base",[w]:"https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",[C]:"https://eth.llamarpc.com"};import{buildRegisterSessionKeyInstruction as D,buildRevokeSessionKeyInstruction as I,deriveSwigWalletAddress as k}from"@dexterai/vault/instructions";import{buildSecp256r1VerifyInstruction as R}from"@dexterai/vault/precompile";import{DEXTER_VAULT_PROGRAM_ID as O,SECP256R1_PROGRAM_ID as we,INSTRUCTIONS_SYSVAR_ID as Ce}from"@dexterai/vault/constants";import{sessionRegisterMessage as B,sessionRevokeMessage as M,voucherPayloadMessage as V,buildVoucherMessage as Ue}from"@dexterai/vault/messages";import L from"tweetnacl";import{sha256 as ee}from"@noble/hashes/sha256";function W(){let e=L.sign.keyPair();return{publicKey:e.publicKey,privateKey:e.secretKey}}function F(e,t,n){return{publicKey:e.publicKey,privateKey:e.privateKey,scope:t,registration:n}}function J(e,t,n){if(n.length!==32)throw new Error(`channelIdBytes must be 32 bytes, got ${n.length}`);let i=BigInt(t.cumulativeAmount),r=BigInt(e.scope.maxAmountAtomic);if(i>r)throw new Error(`voucher cumulative ${i} exceeds session cap ${r}`);let s=Math.floor(Date.now()/1e3);if(s>=e.scope.expiresAtUnix)throw new Error(`session expired at ${e.scope.expiresAtUnix}, now ${s}`);let o=V({channelId:n,cumulativeAmount:i,sequenceNumber:t.sequenceNumber}),a=L.sign.detached(o,e.privateKey);return{payload:t,sessionPublicKey:e.publicKey,sessionRegistration:e.registration,sessionSignature:a}}function d(e){if(!/^\d+$/.test(e))throw new Error(`atomic amount must be a non-negative integer string, got "${e}"`);return BigInt(e)}function te(e){let t=new Uint8Array(8);new DataView(t.buffer).setBigUint64(0,e.nonce,!0);let n=new TextEncoder().encode(e.sellerUrl),i=ee.create();return i.update(e.vaultPda.toBytes()),i.update(n),i.update(t),i.digest()}import{fetchVaultSessionAccounts as pe,sessionPdasOf as ue,waitForSession as le}from"@dexterai/vault/session";import{sha256 as $}from"@noble/hashes/sha256";import{p256 as ne}from"@noble/curves/p256";import{sha256 as g}from"@noble/hashes/sha256";var H="dexter.cash";function ie(e){return Buffer.from(e).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function re(e,t=`https://${H}`){let i={type:"webauthn.get",challenge:ie(e),origin:t,crossOrigin:!1};return new TextEncoder().encode(JSON.stringify(i))}function se(e=1){let t=g(new TextEncoder().encode(H)),n=new Uint8Array(37);return n.set(t,0),n[32]=5,new DataView(n.buffer).setUint32(33,e,!1),n}function oe(e,t){let n=re(t),i=se(1),r=new Uint8Array(i.length+32);r.set(i,0),r.set(g(n),i.length);let s=g(r);return{signature:ne.sign(s,e.privateKey,{lowS:!0}).toCompactRawBytes(),clientDataJSON:n,authenticatorData:i}}function ae(e){return{credentialId:new Uint8Array(0),publicKey:e.publicKey,signOperation:async t=>oe(e,g(t))}}function ye(e){let t=k(e.swigAddress),n=ce(new u(_),t,!0);return D({vaultPda:e.vaultPda,sessionPubkey:e.sessionPubkey,maxAmount:e.maxAmount,maxRevolvingCapacity:e.maxRevolvingCapacity,expiresAt:e.expiresAt,allowedCounterparty:e.allowedCounterparty,nonce:e.nonce,swigAddress:e.swigAddress,vaultUsdcAta:n,clientDataJSON:e.clientDataJSON,authenticatorData:e.authenticatorData,payer:e.payer,siblingSessionPdas:e.siblingSessionPdas})}var U=class{network="solana:mainnet";swigAddress;vaultPda;connection;vaultPdaKey;passkey;feePayer;confirmOptions;constructor(t){this.connection=t.connection,this.swigAddress=typeof t.swigAddress=="string"?t.swigAddress:t.swigAddress.toBase58(),this.vaultPdaKey=typeof t.vaultPda=="string"?new u(t.vaultPda):t.vaultPda,this.vaultPda=this.vaultPdaKey.toBase58(),this.passkey=t.passkeySigner,this.feePayer=t.feePayer,this.confirmOptions=t.confirmOptions??{commitment:"confirmed"}}async authorizeSession(t){let n=new u(t.allowedCounterparty),i=d(t.revolvingCapacityAtomic??t.maxAmountAtomic),r=W(),s=me(),o=B({programId:O,vaultPda:this.vaultPdaKey,sessionPubkey:r.publicKey,maxAmount:d(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s}),{signature:a,clientDataJSON:l,authenticatorData:y}=await this.passkey.signOperation(o),A=q(y,$(l)),c=R(this.passkey.publicKey,a,A),m=ue(await pe(this.connection,this.vaultPdaKey)),h=ye({vaultPda:this.vaultPdaKey,swigAddress:new u(this.swigAddress),sessionPubkey:r.publicKey,maxAmount:d(t.maxAmountAtomic),maxRevolvingCapacity:i,expiresAt:BigInt(t.expiresAtUnix),allowedCounterparty:n,nonce:s,clientDataJSON:l,authenticatorData:y,payer:this.feePayer.publicKey,siblingSessionPdas:m}),p=new G().add(c,h);p.feePayer=this.feePayer.publicKey;let{blockhash:T}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);p.recentBlockhash=T,p.sign(this.feePayer);let X=await this.connection.sendRawTransaction(p.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:X,blockhash:T,lastValidBlockHeight:(await this.connection.getLatestBlockhash(this.confirmOptions.commitment)).lastValidBlockHeight},this.confirmOptions.commitment),await le(this.connection,this.vaultPdaKey,n,{expectedSessionPubkey:r.publicKey,timeoutMs:2e4}),F(r,t,o)}async signWithSession(t,n){let i=await de(n.channelId);return J(t,n,i)}async signOpenTab(t,n){return t.registration}async signCloseTab(t,n,i){let r=M({programId:O,vaultPda:this.vaultPdaKey,sessionPubkey:t.publicKey}),{signature:s,clientDataJSON:o,authenticatorData:a}=await this.passkey.signOperation(r),l=q(a,$(o)),y=R(this.passkey.publicKey,s,l),A=I({vaultPda:this.vaultPdaKey,allowedCounterparty:new u(t.scope.allowedCounterparty),clientDataJSON:o,authenticatorData:a}),c=new G().add(y,A);c.feePayer=this.feePayer.publicKey;let{blockhash:m,lastValidBlockHeight:h}=await this.connection.getLatestBlockhash(this.confirmOptions.commitment);c.recentBlockhash=m,c.sign(this.feePayer);let p=await this.connection.sendRawTransaction(c.serialize(),{skipPreflight:!1,preflightCommitment:this.confirmOptions.preflightCommitment??this.confirmOptions.commitment});return await this.connection.confirmTransaction({signature:p,blockhash:m,lastValidBlockHeight:h},this.confirmOptions.commitment),r}};function qe(e){return new U(e)}function q(e,t){let n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}function me(){return Math.floor(Math.random()*4294967295)>>>0}async function de(e){if(/^[0-9a-f]{64}$/i.test(e))return ge(e);let{sha256:t}=await import("@noble/hashes/sha256");return t(new TextEncoder().encode(e))}function ge(e){let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}export{ye as buildAdapterRegisterInstruction,qe as createSolanaVaultAdapter,te as deriveChannelId,ae as passkeySignerFromP256Keypair};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/x402",
3
- "version": "4.1.0",
3
+ "version": "5.0.1",
4
4
  "description": "Full-stack x402 SDK - add paid API monetization to any endpoint. Express middleware, React hooks, Access Pass, dynamic pricing. Solana, Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE.",
5
5
  "author": "Dexter",
6
6
  "license": "MIT",
@@ -60,6 +60,7 @@
60
60
  "files": [
61
61
  "dist",
62
62
  "README.md",
63
+ "REFERENCE.md",
63
64
  "LICENSE",
64
65
  "assets"
65
66
  ],
@@ -76,7 +77,6 @@
76
77
  "release:major": "npm version major && npm publish --access public"
77
78
  },
78
79
  "dependencies": {
79
- "@dexterai/vault": "^0.13.0",
80
80
  "@dexterai/x402-ads-types": "^0.2.0",
81
81
  "@dexterai/x402-core": "^1.4.0",
82
82
  "@noble/curves": "^1.9.7",
@@ -89,6 +89,7 @@
89
89
  "tweetnacl": "^1.0.3"
90
90
  },
91
91
  "devDependencies": {
92
+ "@dexterai/vault": "^0.19.0",
92
93
  "@types/aws-lambda": "^8.10.161",
93
94
  "@types/express": "^5.0.6",
94
95
  "@types/node": "^22.10.0",
@@ -103,6 +104,7 @@
103
104
  "vitest": "^2.1.8"
104
105
  },
105
106
  "peerDependencies": {
107
+ "@dexterai/vault": ">=0.19.0",
106
108
  "@solana/wallet-adapter-base": "^0.9.0",
107
109
  "react": "^18.0.0 || ^19.0.0",
108
110
  "stripe": "^20.0.0",