@t2000/x402 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 t2000
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # @t2000/x402
2
+
3
+ x402 payment protocol client and facilitator for AI agents on Sui. Pay for API resources with USDC micropayments — the first x402 implementation on Sui.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@t2000/x402)](https://www.npmjs.com/package/@t2000/x402)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **[Website](https://t2000.ai)** · **[GitHub](https://github.com/mission69b/t2000)** · **[SDK](https://www.npmjs.com/package/@t2000/sdk)** · **[CLI](https://www.npmjs.com/package/@t2000/cli)**
9
+
10
+ ## What is x402?
11
+
12
+ The [x402 protocol](https://www.x402.org/) enables machine-to-machine payments for API access. When a server returns HTTP `402 Payment Required`, the client automatically pays with USDC and retries — no API keys, no subscriptions, no human approval.
13
+
14
+ t2000 is the **first x402 client on Sui**, built on the [Sui Payment Kit](https://docs.sui.io/standards/payment-kit) for on-chain payment verification with Move-level replay protection.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @t2000/x402
20
+ # or
21
+ pnpm add @t2000/x402
22
+ # or
23
+ yarn add @t2000/x402
24
+ ```
25
+
26
+ **Requirements:** Node.js 18+ · `@t2000/sdk` (peer dependency)
27
+
28
+ ## Quick Start — Client
29
+
30
+ ```typescript
31
+ import { x402Client } from '@t2000/x402';
32
+ import { T2000 } from '@t2000/sdk';
33
+
34
+ const agent = await T2000.create({ passphrase: 'my-secret' });
35
+
36
+ const client = new x402Client(agent);
37
+
38
+ // Fetch a paid API — handles the full 402 handshake
39
+ const response = await client.fetch('https://api.example.com/data', {
40
+ maxPrice: 0.10, // max USDC per request (default: $1.00)
41
+ timeout: 30_000, // request timeout in ms (default: 30s)
42
+ });
43
+ const data = await response.json();
44
+ ```
45
+
46
+ ## Quick Start — CLI
47
+
48
+ ```bash
49
+ # Pay for an API request (handles 402 automatically)
50
+ t2000 pay https://api.example.com/data
51
+
52
+ # With max price limit
53
+ t2000 pay https://api.example.com/premium --max-price 0.10
54
+
55
+ # POST request with JSON body
56
+ t2000 pay https://api.example.com/analyze --method POST --data '{"text":"hello"}'
57
+ ```
58
+
59
+ ## How It Works
60
+
61
+ ```
62
+ Agent API Server Facilitator (t2000)
63
+ │ │ │
64
+ │── GET /data ────────────>│ │
65
+ │<── 402 Payment Required ─│ │
66
+ │ (amount, recipient) │ │
67
+ │ │ │
68
+ │── Sign & broadcast ─────────────────────────────────│
69
+ │ USDC payment on Sui │ │
70
+ │ │ │
71
+ │── GET /data ────────────>│ │
72
+ │ + X-PAYMENT header │── POST /x402/verify ────>│
73
+ │ │<── { valid: true } ──────│
74
+ │<── 200 OK + data ────────│ │
75
+ │ │── POST /x402/settle ────>│
76
+ │ │<── { settled: true } ────│
77
+ ```
78
+
79
+ Total round-trip: ~820ms.
80
+
81
+ ## Client API
82
+
83
+ ### `new x402Client(wallet)`
84
+
85
+ Creates an x402 client with the given wallet. Per-request options like `maxPrice` and `timeout` are passed to `client.fetch()`.
86
+
87
+ ```typescript
88
+ const client = new x402Client(wallet);
89
+ ```
90
+
91
+ ### `client.fetch(url, init?)`
92
+
93
+ Makes an HTTP request, automatically handling `402 Payment Required` responses.
94
+
95
+ ```typescript
96
+ const res = await client.fetch('https://api.example.com/data', {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({ query: 'hello' }),
100
+ });
101
+ ```
102
+
103
+ ### `parsePaymentRequired(response)`
104
+
105
+ Parses a 402 response into structured payment terms.
106
+
107
+ ```typescript
108
+ import { parsePaymentRequired } from '@t2000/x402';
109
+
110
+ const terms = parsePaymentRequired(response);
111
+ // { amount: 10000, recipient: '0x...', network: 'sui', ... }
112
+ ```
113
+
114
+ ## Facilitator API (Server-Side)
115
+
116
+ For API providers who want to accept x402 payments:
117
+
118
+ ### `verifyPayment(params)`
119
+
120
+ Verify that an on-chain payment transaction is valid.
121
+
122
+ ```typescript
123
+ import { verifyPayment } from '@t2000/x402';
124
+
125
+ const result = await verifyPayment({
126
+ txDigest: 'ABC123...',
127
+ expectedAmount: 10000, // 0.01 USDC in raw units (6 decimals)
128
+ expectedRecipient: '0x...',
129
+ client: suiClient, // SuiClient instance
130
+ });
131
+
132
+ if (result.valid) {
133
+ // Payment verified — serve the resource
134
+ }
135
+ ```
136
+
137
+ ### Settlement
138
+
139
+ Settlement (marking payments as used) is handled server-side by the t2000 facilitator API. The `/x402/settle` endpoint records the payment in the database to prevent double-use. See the [server source](https://github.com/mission69b/t2000/tree/main/apps/server) for implementation details.
140
+
141
+ ## Payment Kit Integration
142
+
143
+ Payments are executed on-chain via the [Sui Payment Kit](https://docs.sui.io/standards/payment-kit), which provides:
144
+
145
+ - **Atomic payments** — pay and verify in a single Sui transaction
146
+ - **Move-level nonce enforcement** — `EDuplicatePayment` abort prevents replay attacks
147
+ - **On-chain receipts** — `PaymentReceipt` events for auditing
148
+
149
+ ```typescript
150
+ import { buildPaymentTransaction } from '@t2000/x402';
151
+
152
+ // Build a payment transaction (advanced usage)
153
+ const tx = buildPaymentTransaction({
154
+ registryId: '0x...',
155
+ recipient: '0x...',
156
+ amount: 10000n, // raw USDC units
157
+ nonce: 'unique-nonce',
158
+ coinObjectId: '0x...',
159
+ });
160
+ ```
161
+
162
+ ## Configuration
163
+
164
+ | Environment Variable | Description | Default |
165
+ |---------------------|-------------|---------|
166
+ | `PAYMENT_KIT_PACKAGE` | Payment Kit Move package ID | Mainnet default |
167
+ | `T2000_PAYMENT_REGISTRY_ID` | PaymentRegistry object ID | t2000 mainnet registry |
168
+ | `X402_FACILITATOR_URL` | Facilitator base URL | `https://api.t2000.ai/x402` |
169
+
170
+ ## Safety
171
+
172
+ - **Price limits** — payments refused if amount exceeds `maxPrice` (default $1.00)
173
+ - **Network validation** — only Sui payments are accepted
174
+ - **Replay protection** — on-chain nonce enforcement via Sui Payment Kit
175
+ - **Timeout** — requests abort after configurable timeout (default 30s)
176
+
177
+ ## Wallet Interface
178
+
179
+ Any wallet implementing `X402Wallet` can be used as a payment source:
180
+
181
+ ```typescript
182
+ interface X402Wallet {
183
+ client: SuiClient;
184
+ keypair: Ed25519Keypair;
185
+ address(): string;
186
+ signAndExecute(tx: unknown): Promise<{ digest: string }>;
187
+ }
188
+ ```
189
+
190
+ The `T2000` class from `@t2000/sdk` implements this interface.
191
+
192
+ ## Testing
193
+
194
+ ```bash
195
+ # Unit tests (27 tests)
196
+ pnpm --filter @t2000/x402 test
197
+
198
+ # Integration tests (requires funded mainnet wallet)
199
+ T2000_PRIVATE_KEY='suiprivkey1q...' INTEGRATION=true pnpm --filter @t2000/x402 test
200
+ ```
201
+
202
+ Integration tests execute real on-chain transactions to verify the full payment flow and replay protection. See the [root README](https://github.com/mission69b/t2000#integration-tests-local-only) for details.
203
+
204
+ ## License
205
+
206
+ MIT — see [LICENSE](https://github.com/mission69b/t2000/blob/main/LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,302 @@
1
+ 'use strict';
2
+
3
+ var sdk = require('@t2000/sdk');
4
+ var transactions = require('@mysten/sui/transactions');
5
+
6
+ // src/client.ts
7
+
8
+ // src/types.ts
9
+ var X402_HEADERS = {
10
+ PAYMENT_REQUIRED: "payment-required",
11
+ X_PAYMENT: "x-payment"
12
+ };
13
+ var DEFAULT_MAX_PRICE = 1;
14
+ var DEFAULT_TIMEOUT = 3e4;
15
+
16
+ // src/constants.ts
17
+ var PAYMENT_KIT_PACKAGE = process.env.PAYMENT_KIT_PACKAGE ?? "0xbc126f1535fba7d641cb9150ad9eae93b104972586ba20f3c60bfe0e53b69bc6";
18
+ var T2000_PAYMENT_REGISTRY_ID = process.env.T2000_PAYMENT_REGISTRY_ID ?? "0x4009dd17305ed1b33352b808e9d0e9eb94d09085b2d5ec0f395c5cdfa2271291";
19
+ var USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
20
+ var CLOCK_ID = "0x6";
21
+ var DEFAULT_FACILITATOR_URL = "https://api.t2000.ai/x402";
22
+ var PAYMENT_KIT_MODULE = "payment_kit";
23
+ var PAYMENT_KIT_FUNCTION = "process_registry_payment";
24
+ var PAYMENT_RECEIPT_EVENT_TYPE = `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::PaymentReceipt`;
25
+
26
+ // src/payment-kit.ts
27
+ async function buildPaymentTransaction(client, senderAddress, params) {
28
+ const { nonce, amount, payTo } = params;
29
+ const rawAmount = sdk.usdcToRaw(Number(amount));
30
+ if (!T2000_PAYMENT_REGISTRY_ID) {
31
+ throw new Error(
32
+ "T2000_PAYMENT_REGISTRY_ID is not set. Create a PaymentRegistry<USDC> via Payment Kit before using x402."
33
+ );
34
+ }
35
+ const coins = await client.getCoins({
36
+ owner: senderAddress,
37
+ coinType: USDC_TYPE
38
+ });
39
+ if (coins.data.length === 0) {
40
+ throw new Error("No USDC coins found in wallet");
41
+ }
42
+ const tx = new transactions.Transaction();
43
+ let primaryCoin = tx.object(coins.data[0].coinObjectId);
44
+ if (coins.data.length > 1) {
45
+ const otherCoins = coins.data.slice(1).map((c) => tx.object(c.coinObjectId));
46
+ tx.mergeCoins(primaryCoin, otherCoins);
47
+ }
48
+ const [paymentCoin] = tx.splitCoins(primaryCoin, [tx.pure.u64(rawAmount)]);
49
+ tx.moveCall({
50
+ target: `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::${PAYMENT_KIT_FUNCTION}`,
51
+ arguments: [
52
+ tx.object(T2000_PAYMENT_REGISTRY_ID),
53
+ tx.pure.string(nonce),
54
+ tx.pure.u64(rawAmount),
55
+ paymentCoin,
56
+ tx.pure.option("address", payTo),
57
+ tx.object(CLOCK_ID)
58
+ ],
59
+ typeArguments: [USDC_TYPE]
60
+ });
61
+ return tx;
62
+ }
63
+
64
+ // src/client.ts
65
+ function parsePaymentRequired(headerValue, maxPrice = DEFAULT_MAX_PRICE) {
66
+ if (!headerValue) {
67
+ throw new sdk.T2000Error(
68
+ "TRANSACTION_FAILED",
69
+ "402 response missing PAYMENT-REQUIRED header"
70
+ );
71
+ }
72
+ let parsed;
73
+ try {
74
+ parsed = JSON.parse(headerValue);
75
+ } catch {
76
+ throw new sdk.T2000Error(
77
+ "TRANSACTION_FAILED",
78
+ "Malformed PAYMENT-REQUIRED header: invalid JSON",
79
+ { raw: headerValue }
80
+ );
81
+ }
82
+ if (!parsed.network || !parsed.amount || !parsed.payTo || !parsed.nonce || parsed.expiresAt == null) {
83
+ throw new sdk.T2000Error(
84
+ "TRANSACTION_FAILED",
85
+ "PAYMENT-REQUIRED header missing required fields",
86
+ { parsed }
87
+ );
88
+ }
89
+ if (parsed.network !== "sui") {
90
+ throw new sdk.T2000Error(
91
+ "UNSUPPORTED_NETWORK",
92
+ `x402 requires network "${parsed.network}" but only Sui is supported`,
93
+ { network: parsed.network }
94
+ );
95
+ }
96
+ if (parsed.asset && parsed.asset !== "USDC") {
97
+ throw new sdk.T2000Error(
98
+ "TRANSACTION_FAILED",
99
+ `x402 requires asset "${parsed.asset}" but only USDC is supported`,
100
+ { asset: parsed.asset }
101
+ );
102
+ }
103
+ if (parsed.expiresAt < Date.now() / 1e3) {
104
+ throw new sdk.T2000Error(
105
+ "PAYMENT_EXPIRED",
106
+ "x402 payment challenge has expired",
107
+ { expiresAt: parsed.expiresAt }
108
+ );
109
+ }
110
+ const price = Number(parsed.amount);
111
+ if (price > maxPrice) {
112
+ throw new sdk.T2000Error(
113
+ "PRICE_EXCEEDS_LIMIT",
114
+ `Requested price $${parsed.amount} exceeds max price $${maxPrice}`,
115
+ { requested: parsed.amount, limit: maxPrice }
116
+ );
117
+ }
118
+ return parsed;
119
+ }
120
+ var x402Client = class {
121
+ wallet;
122
+ constructor(wallet) {
123
+ this.wallet = wallet;
124
+ }
125
+ /**
126
+ * Makes an HTTP request, handling x402 payment if required.
127
+ * Non-402 responses pass through unmodified.
128
+ */
129
+ async fetch(url, options = {}) {
130
+ const {
131
+ maxPrice = DEFAULT_MAX_PRICE,
132
+ method = "GET",
133
+ headers = {},
134
+ body,
135
+ timeout = DEFAULT_TIMEOUT,
136
+ dryRun = false,
137
+ onPayment
138
+ } = options;
139
+ const controller = new AbortController();
140
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
141
+ try {
142
+ const initial = await globalThis.fetch(url, {
143
+ method,
144
+ headers,
145
+ body,
146
+ signal: controller.signal
147
+ });
148
+ if (initial.status !== 402) {
149
+ return initial;
150
+ }
151
+ const paymentRequired = parsePaymentRequired(
152
+ initial.headers.get(X402_HEADERS.PAYMENT_REQUIRED),
153
+ maxPrice
154
+ );
155
+ if (dryRun) {
156
+ return new Response(
157
+ JSON.stringify({
158
+ dryRun: true,
159
+ amount: paymentRequired.amount,
160
+ asset: paymentRequired.asset,
161
+ payTo: paymentRequired.payTo,
162
+ network: paymentRequired.network
163
+ }),
164
+ { status: 200, headers: { "content-type": "application/json" } }
165
+ );
166
+ }
167
+ const tx = await buildPaymentTransaction(
168
+ this.wallet.client,
169
+ this.wallet.address(),
170
+ {
171
+ nonce: paymentRequired.nonce,
172
+ amount: paymentRequired.amount,
173
+ payTo: paymentRequired.payTo
174
+ }
175
+ );
176
+ let result;
177
+ try {
178
+ result = await this.wallet.signAndExecute(tx);
179
+ } catch (err) {
180
+ const msg = err instanceof Error ? err.message : String(err);
181
+ if (msg.includes("EDuplicatePayment") || msg.includes("duplicate")) {
182
+ throw new sdk.T2000Error(
183
+ "DUPLICATE_PAYMENT",
184
+ "Payment nonce already used on-chain",
185
+ { nonce: paymentRequired.nonce }
186
+ );
187
+ }
188
+ if (msg.includes("InsufficientCoinBalance") || msg.includes("Insufficient")) {
189
+ throw new sdk.T2000Error(
190
+ "INSUFFICIENT_BALANCE",
191
+ "Not enough USDC to complete payment",
192
+ { required: paymentRequired.amount }
193
+ );
194
+ }
195
+ throw new sdk.T2000Error(
196
+ "TRANSACTION_FAILED",
197
+ `Payment transaction failed: ${msg}`,
198
+ { nonce: paymentRequired.nonce }
199
+ );
200
+ }
201
+ const paymentDetails = {
202
+ amount: paymentRequired.amount,
203
+ asset: paymentRequired.asset || "USDC",
204
+ payTo: paymentRequired.payTo,
205
+ nonce: paymentRequired.nonce,
206
+ txHash: result.digest
207
+ };
208
+ onPayment?.(paymentDetails);
209
+ const paymentPayload = {
210
+ txHash: result.digest,
211
+ network: "sui",
212
+ amount: paymentRequired.amount,
213
+ nonce: paymentRequired.nonce
214
+ };
215
+ const retry = await globalThis.fetch(url, {
216
+ method,
217
+ headers: {
218
+ ...headers,
219
+ [X402_HEADERS.X_PAYMENT]: JSON.stringify(paymentPayload)
220
+ },
221
+ body,
222
+ signal: controller.signal
223
+ });
224
+ return retry;
225
+ } finally {
226
+ clearTimeout(timeoutId);
227
+ }
228
+ }
229
+ };
230
+ function isPaymentReceipt(event) {
231
+ return event.type === PAYMENT_RECEIPT_EVENT_TYPE;
232
+ }
233
+ function parsePaymentReceiptFields(event) {
234
+ const json = event.parsedJson;
235
+ if (!json) return null;
236
+ return {
237
+ payment_amount: String(json.payment_amount ?? ""),
238
+ receiver: String(json.receiver ?? ""),
239
+ nonce: String(json.nonce ?? ""),
240
+ coin_type: json.coin_type ? String(json.coin_type) : void 0,
241
+ timestamp_ms: json.timestamp_ms ? String(json.timestamp_ms) : void 0
242
+ };
243
+ }
244
+ async function verifyPayment(client, req) {
245
+ if (Date.now() / 1e3 > req.expiresAt) {
246
+ return { verified: false, reason: "expired" };
247
+ }
248
+ let tx;
249
+ try {
250
+ tx = await client.getTransactionBlock({
251
+ digest: req.txHash,
252
+ options: { showEffects: true, showEvents: true }
253
+ });
254
+ } catch {
255
+ return { verified: false, reason: "tx_not_found" };
256
+ }
257
+ if (!tx) {
258
+ return { verified: false, reason: "tx_not_found" };
259
+ }
260
+ const receiptEvent = tx.events?.find(isPaymentReceipt);
261
+ if (!receiptEvent) {
262
+ return { verified: false, reason: "no_payment_event" };
263
+ }
264
+ const fields = parsePaymentReceiptFields(receiptEvent);
265
+ if (!fields) {
266
+ return { verified: false, reason: "no_payment_event" };
267
+ }
268
+ const expectedAmount = sdk.usdcToRaw(Number(req.amount));
269
+ if (BigInt(fields.payment_amount) !== expectedAmount) {
270
+ return { verified: false, reason: "amount_mismatch" };
271
+ }
272
+ if (fields.receiver !== req.payTo) {
273
+ return { verified: false, reason: "wrong_recipient" };
274
+ }
275
+ if (fields.nonce !== req.nonce) {
276
+ return { verified: false, reason: "nonce_mismatch" };
277
+ }
278
+ return {
279
+ verified: true,
280
+ txHash: req.txHash,
281
+ settledAmount: req.amount,
282
+ settledAt: Math.floor(Date.now() / 1e3)
283
+ };
284
+ }
285
+
286
+ exports.CLOCK_ID = CLOCK_ID;
287
+ exports.DEFAULT_FACILITATOR_URL = DEFAULT_FACILITATOR_URL;
288
+ exports.DEFAULT_MAX_PRICE = DEFAULT_MAX_PRICE;
289
+ exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
290
+ exports.PAYMENT_KIT_FUNCTION = PAYMENT_KIT_FUNCTION;
291
+ exports.PAYMENT_KIT_MODULE = PAYMENT_KIT_MODULE;
292
+ exports.PAYMENT_KIT_PACKAGE = PAYMENT_KIT_PACKAGE;
293
+ exports.PAYMENT_RECEIPT_EVENT_TYPE = PAYMENT_RECEIPT_EVENT_TYPE;
294
+ exports.T2000_PAYMENT_REGISTRY_ID = T2000_PAYMENT_REGISTRY_ID;
295
+ exports.USDC_TYPE = USDC_TYPE;
296
+ exports.X402_HEADERS = X402_HEADERS;
297
+ exports.buildPaymentTransaction = buildPaymentTransaction;
298
+ exports.parsePaymentRequired = parsePaymentRequired;
299
+ exports.verifyPayment = verifyPayment;
300
+ exports.x402Client = x402Client;
301
+ //# sourceMappingURL=index.cjs.map
302
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/constants.ts","../src/payment-kit.ts","../src/client.ts","../src/facilitator.ts"],"names":["usdcToRaw","Transaction","T2000Error"],"mappings":"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","file":"index.cjs","sourcesContent":["export interface PaymentRequired {\n amount: string;\n asset: string;\n network: string;\n payTo: string;\n nonce: string;\n expiresAt: number;\n description?: string;\n}\n\nexport interface PaymentPayload {\n txHash: string;\n network: string;\n amount: string;\n nonce: string;\n}\n\nexport interface VerifyRequest {\n txHash: string;\n network: string;\n amount: string;\n asset: string;\n payTo: string;\n nonce: string;\n expiresAt: number;\n}\n\nexport interface VerifyResponse {\n verified: boolean;\n txHash?: string;\n settledAmount?: string;\n settledAt?: number;\n reason?: VerifyFailureReason;\n}\n\nexport type VerifyFailureReason =\n | 'expired'\n | 'tx_not_found'\n | 'no_payment_event'\n | 'amount_mismatch'\n | 'wrong_recipient'\n | 'nonce_mismatch';\n\nexport interface SettleRequest {\n txHash: string;\n nonce: string;\n}\n\nexport interface SettleResponse {\n settled: boolean;\n}\n\nexport interface X402ClientOptions {\n maxPrice?: number;\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeout?: number;\n dryRun?: boolean;\n onPayment?: (details: PaymentDetails) => void;\n}\n\nexport interface PaymentDetails {\n amount: string;\n asset: string;\n payTo: string;\n nonce: string;\n txHash: string;\n}\n\nexport type X402FetchOptions = X402ClientOptions;\n\nexport const X402_HEADERS = {\n PAYMENT_REQUIRED: 'payment-required',\n X_PAYMENT: 'x-payment',\n} as const;\n\nexport const DEFAULT_MAX_PRICE = 1.0;\nexport const DEFAULT_TIMEOUT = 30_000;\n","/**\n * Sui Payment Kit package ID (mainnet).\n * Source: github.com/MystenLabs/sui-payment-kit Move.lock `original-published-id`.\n */\nexport const PAYMENT_KIT_PACKAGE =\n process.env.PAYMENT_KIT_PACKAGE ??\n '0xbc126f1535fba7d641cb9150ad9eae93b104972586ba20f3c60bfe0e53b69bc6';\n\n/**\n * t2000's PaymentRegistry object ID (mainnet).\n * Created via `create_registry` on Payment Kit Namespace.\n * Tx: 666ZX1PhfV3PVJtqVZfuToHoVGoTDeWCxzZb1u8aRgmL\n */\nexport const T2000_PAYMENT_REGISTRY_ID =\n process.env.T2000_PAYMENT_REGISTRY_ID ??\n '0x4009dd17305ed1b33352b808e9d0e9eb94d09085b2d5ec0f395c5cdfa2271291';\n\nexport const USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n\nexport const CLOCK_ID = '0x6';\n\nexport const DEFAULT_FACILITATOR_URL = 'https://api.t2000.ai/x402';\n\nexport const PAYMENT_KIT_MODULE = 'payment_kit';\nexport const PAYMENT_KIT_FUNCTION = 'process_registry_payment';\nexport const PAYMENT_RECEIPT_EVENT_TYPE = `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::PaymentReceipt`;\n","import { Transaction } from '@mysten/sui/transactions';\nimport type { SuiClient } from '@mysten/sui/client';\nimport { usdcToRaw } from '@t2000/sdk';\nimport {\n PAYMENT_KIT_PACKAGE,\n PAYMENT_KIT_MODULE,\n PAYMENT_KIT_FUNCTION,\n T2000_PAYMENT_REGISTRY_ID,\n USDC_TYPE,\n CLOCK_ID,\n} from './constants.js';\n\nexport interface PaymentPTBParams {\n nonce: string;\n amount: string;\n payTo: string;\n}\n\n/**\n * Builds a payment PTB using USDC coins from the wallet.\n *\n * The Move function signature:\n * process_registry_payment<CoinType>(\n * registry: &mut PaymentRegistry<CoinType>,\n * nonce: String,\n * amount: u64,\n * coin: Coin<CoinType>,\n * receiver: Option<address>,\n * clock: &Clock\n * )\n *\n * Move enforces nonce uniqueness atomically via EDuplicatePayment.\n */\nexport async function buildPaymentTransaction(\n client: SuiClient,\n senderAddress: string,\n params: PaymentPTBParams,\n): Promise<Transaction> {\n const { nonce, amount, payTo } = params;\n const rawAmount = usdcToRaw(Number(amount));\n\n if (!T2000_PAYMENT_REGISTRY_ID) {\n throw new Error(\n 'T2000_PAYMENT_REGISTRY_ID is not set. ' +\n 'Create a PaymentRegistry<USDC> via Payment Kit before using x402.'\n );\n }\n\n const coins = await client.getCoins({\n owner: senderAddress,\n coinType: USDC_TYPE,\n });\n\n if (coins.data.length === 0) {\n throw new Error('No USDC coins found in wallet');\n }\n\n const tx = new Transaction();\n\n let primaryCoin = tx.object(coins.data[0].coinObjectId);\n if (coins.data.length > 1) {\n const otherCoins = coins.data.slice(1).map(c => tx.object(c.coinObjectId));\n tx.mergeCoins(primaryCoin, otherCoins);\n }\n\n const [paymentCoin] = tx.splitCoins(primaryCoin, [tx.pure.u64(rawAmount)]);\n\n tx.moveCall({\n target: `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::${PAYMENT_KIT_FUNCTION}`,\n arguments: [\n tx.object(T2000_PAYMENT_REGISTRY_ID),\n tx.pure.string(nonce),\n tx.pure.u64(rawAmount),\n paymentCoin,\n tx.pure.option('address', payTo),\n tx.object(CLOCK_ID),\n ],\n typeArguments: [USDC_TYPE],\n });\n\n return tx;\n}\n","import type { SuiClient } from '@mysten/sui/client';\nimport type { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';\nimport { T2000Error } from '@t2000/sdk';\nimport type {\n PaymentRequired,\n PaymentPayload,\n X402FetchOptions,\n PaymentDetails,\n} from './types.js';\nimport {\n X402_HEADERS,\n DEFAULT_MAX_PRICE,\n DEFAULT_TIMEOUT,\n} from './types.js';\nimport { buildPaymentTransaction } from './payment-kit.js';\n\n/**\n * Parses the PAYMENT-REQUIRED header from a 402 response.\n * Validates network, asset, expiry, and price constraints.\n */\nexport function parsePaymentRequired(\n headerValue: string | null,\n maxPrice: number = DEFAULT_MAX_PRICE,\n): PaymentRequired {\n if (!headerValue) {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n '402 response missing PAYMENT-REQUIRED header',\n );\n }\n\n let parsed: PaymentRequired;\n try {\n parsed = JSON.parse(headerValue);\n } catch {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n 'Malformed PAYMENT-REQUIRED header: invalid JSON',\n { raw: headerValue },\n );\n }\n\n if (!parsed.network || !parsed.amount || !parsed.payTo || !parsed.nonce || parsed.expiresAt == null) {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n 'PAYMENT-REQUIRED header missing required fields',\n { parsed },\n );\n }\n\n if (parsed.network !== 'sui') {\n throw new T2000Error(\n 'UNSUPPORTED_NETWORK',\n `x402 requires network \"${parsed.network}\" but only Sui is supported`,\n { network: parsed.network },\n );\n }\n\n if (parsed.asset && parsed.asset !== 'USDC') {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n `x402 requires asset \"${parsed.asset}\" but only USDC is supported`,\n { asset: parsed.asset },\n );\n }\n\n if (parsed.expiresAt < Date.now() / 1000) {\n throw new T2000Error(\n 'PAYMENT_EXPIRED',\n 'x402 payment challenge has expired',\n { expiresAt: parsed.expiresAt },\n );\n }\n\n const price = Number(parsed.amount);\n if (price > maxPrice) {\n throw new T2000Error(\n 'PRICE_EXCEEDS_LIMIT',\n `Requested price $${parsed.amount} exceeds max price $${maxPrice}`,\n { requested: parsed.amount, limit: maxPrice },\n );\n }\n\n return parsed;\n}\n\nexport interface X402Wallet {\n client: SuiClient;\n keypair: Ed25519Keypair;\n address(): string;\n signAndExecute(tx: unknown): Promise<{ digest: string }>;\n}\n\n/**\n * x402 client for Sui. Handles the full 402 payment flow:\n * 1. Sends initial request\n * 2. Detects 402 Payment Required\n * 3. Parses payment terms\n * 4. Builds and executes Payment Kit PTB\n * 5. Retries with X-PAYMENT proof header\n */\nexport class x402Client {\n private wallet: X402Wallet;\n\n constructor(wallet: X402Wallet) {\n this.wallet = wallet;\n }\n\n /**\n * Makes an HTTP request, handling x402 payment if required.\n * Non-402 responses pass through unmodified.\n */\n async fetch(url: string, options: X402FetchOptions = {}): Promise<Response> {\n const {\n maxPrice = DEFAULT_MAX_PRICE,\n method = 'GET',\n headers = {},\n body,\n timeout = DEFAULT_TIMEOUT,\n dryRun = false,\n onPayment,\n } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const initial = await globalThis.fetch(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n\n if (initial.status !== 402) {\n return initial;\n }\n\n const paymentRequired = parsePaymentRequired(\n initial.headers.get(X402_HEADERS.PAYMENT_REQUIRED),\n maxPrice,\n );\n\n if (dryRun) {\n return new Response(\n JSON.stringify({\n dryRun: true,\n amount: paymentRequired.amount,\n asset: paymentRequired.asset,\n payTo: paymentRequired.payTo,\n network: paymentRequired.network,\n }),\n { status: 200, headers: { 'content-type': 'application/json' } },\n );\n }\n\n const tx = await buildPaymentTransaction(\n this.wallet.client,\n this.wallet.address(),\n {\n nonce: paymentRequired.nonce,\n amount: paymentRequired.amount,\n payTo: paymentRequired.payTo,\n },\n );\n\n let result: { digest: string };\n try {\n result = await this.wallet.signAndExecute(tx);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('EDuplicatePayment') || msg.includes('duplicate')) {\n throw new T2000Error(\n 'DUPLICATE_PAYMENT',\n 'Payment nonce already used on-chain',\n { nonce: paymentRequired.nonce },\n );\n }\n if (msg.includes('InsufficientCoinBalance') || msg.includes('Insufficient')) {\n throw new T2000Error(\n 'INSUFFICIENT_BALANCE',\n 'Not enough USDC to complete payment',\n { required: paymentRequired.amount },\n );\n }\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n `Payment transaction failed: ${msg}`,\n { nonce: paymentRequired.nonce },\n );\n }\n\n const paymentDetails: PaymentDetails = {\n amount: paymentRequired.amount,\n asset: paymentRequired.asset || 'USDC',\n payTo: paymentRequired.payTo,\n nonce: paymentRequired.nonce,\n txHash: result.digest,\n };\n\n onPayment?.(paymentDetails);\n\n const paymentPayload: PaymentPayload = {\n txHash: result.digest,\n network: 'sui',\n amount: paymentRequired.amount,\n nonce: paymentRequired.nonce,\n };\n\n const retry = await globalThis.fetch(url, {\n method,\n headers: {\n ...headers,\n [X402_HEADERS.X_PAYMENT]: JSON.stringify(paymentPayload),\n },\n body,\n signal: controller.signal,\n });\n\n return retry;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","import type { SuiClient, SuiEvent } from '@mysten/sui/client';\nimport { usdcToRaw } from '@t2000/sdk';\nimport type {\n VerifyRequest,\n VerifyResponse,\n VerifyFailureReason,\n} from './types.js';\nimport { PAYMENT_RECEIPT_EVENT_TYPE } from './constants.js';\n\ninterface PaymentReceiptFields {\n payment_amount: string;\n receiver: string;\n nonce: string;\n coin_type?: string;\n timestamp_ms?: string;\n}\n\nfunction isPaymentReceipt(event: SuiEvent): boolean {\n return event.type === PAYMENT_RECEIPT_EVENT_TYPE;\n}\n\nfunction parsePaymentReceiptFields(event: SuiEvent): PaymentReceiptFields | null {\n const json = event.parsedJson as Record<string, unknown> | undefined;\n if (!json) return null;\n\n return {\n payment_amount: String(json.payment_amount ?? ''),\n receiver: String(json.receiver ?? ''),\n nonce: String(json.nonce ?? ''),\n coin_type: json.coin_type ? String(json.coin_type) : undefined,\n timestamp_ms: json.timestamp_ms ? String(json.timestamp_ms) : undefined,\n };\n}\n\n/**\n * Verifies an x402 payment by checking the Sui transaction on-chain.\n *\n * Verification steps:\n * 1. Check if the challenge has expired\n * 2. Fetch the transaction from Sui RPC\n * 3. Find the PaymentReceipt event emitted by Payment Kit\n * 4. Validate amount, recipient, and nonce match the request\n *\n * Duplicate nonces are already prevented by Move's EDuplicatePayment.\n * This function only verifies payments that successfully landed on-chain.\n */\nexport async function verifyPayment(\n client: SuiClient,\n req: VerifyRequest,\n): Promise<VerifyResponse> {\n if (Date.now() / 1000 > req.expiresAt) {\n return { verified: false, reason: 'expired' };\n }\n\n let tx;\n try {\n tx = await client.getTransactionBlock({\n digest: req.txHash,\n options: { showEffects: true, showEvents: true },\n });\n } catch {\n return { verified: false, reason: 'tx_not_found' };\n }\n\n if (!tx) {\n return { verified: false, reason: 'tx_not_found' };\n }\n\n const receiptEvent = tx.events?.find(isPaymentReceipt);\n if (!receiptEvent) {\n return { verified: false, reason: 'no_payment_event' };\n }\n\n const fields = parsePaymentReceiptFields(receiptEvent);\n if (!fields) {\n return { verified: false, reason: 'no_payment_event' };\n }\n\n const expectedAmount = usdcToRaw(Number(req.amount));\n if (BigInt(fields.payment_amount) !== expectedAmount) {\n return { verified: false, reason: 'amount_mismatch' };\n }\n\n if (fields.receiver !== req.payTo) {\n return { verified: false, reason: 'wrong_recipient' };\n }\n\n if (fields.nonce !== req.nonce) {\n return { verified: false, reason: 'nonce_mismatch' as VerifyFailureReason };\n }\n\n return {\n verified: true,\n txHash: req.txHash,\n settledAmount: req.amount,\n settledAt: Math.floor(Date.now() / 1000),\n };\n}\n"]}
@@ -0,0 +1,153 @@
1
+ import { SuiClient } from '@mysten/sui/client';
2
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
3
+ import { Transaction } from '@mysten/sui/transactions';
4
+
5
+ interface PaymentRequired {
6
+ amount: string;
7
+ asset: string;
8
+ network: string;
9
+ payTo: string;
10
+ nonce: string;
11
+ expiresAt: number;
12
+ description?: string;
13
+ }
14
+ interface PaymentPayload {
15
+ txHash: string;
16
+ network: string;
17
+ amount: string;
18
+ nonce: string;
19
+ }
20
+ interface VerifyRequest {
21
+ txHash: string;
22
+ network: string;
23
+ amount: string;
24
+ asset: string;
25
+ payTo: string;
26
+ nonce: string;
27
+ expiresAt: number;
28
+ }
29
+ interface VerifyResponse {
30
+ verified: boolean;
31
+ txHash?: string;
32
+ settledAmount?: string;
33
+ settledAt?: number;
34
+ reason?: VerifyFailureReason;
35
+ }
36
+ type VerifyFailureReason = 'expired' | 'tx_not_found' | 'no_payment_event' | 'amount_mismatch' | 'wrong_recipient' | 'nonce_mismatch';
37
+ interface SettleRequest {
38
+ txHash: string;
39
+ nonce: string;
40
+ }
41
+ interface SettleResponse {
42
+ settled: boolean;
43
+ }
44
+ interface X402ClientOptions {
45
+ maxPrice?: number;
46
+ method?: string;
47
+ headers?: Record<string, string>;
48
+ body?: string;
49
+ timeout?: number;
50
+ dryRun?: boolean;
51
+ onPayment?: (details: PaymentDetails) => void;
52
+ }
53
+ interface PaymentDetails {
54
+ amount: string;
55
+ asset: string;
56
+ payTo: string;
57
+ nonce: string;
58
+ txHash: string;
59
+ }
60
+ type X402FetchOptions = X402ClientOptions;
61
+ declare const X402_HEADERS: {
62
+ readonly PAYMENT_REQUIRED: "payment-required";
63
+ readonly X_PAYMENT: "x-payment";
64
+ };
65
+ declare const DEFAULT_MAX_PRICE = 1;
66
+ declare const DEFAULT_TIMEOUT = 30000;
67
+
68
+ /**
69
+ * Parses the PAYMENT-REQUIRED header from a 402 response.
70
+ * Validates network, asset, expiry, and price constraints.
71
+ */
72
+ declare function parsePaymentRequired(headerValue: string | null, maxPrice?: number): PaymentRequired;
73
+ interface X402Wallet {
74
+ client: SuiClient;
75
+ keypair: Ed25519Keypair;
76
+ address(): string;
77
+ signAndExecute(tx: unknown): Promise<{
78
+ digest: string;
79
+ }>;
80
+ }
81
+ /**
82
+ * x402 client for Sui. Handles the full 402 payment flow:
83
+ * 1. Sends initial request
84
+ * 2. Detects 402 Payment Required
85
+ * 3. Parses payment terms
86
+ * 4. Builds and executes Payment Kit PTB
87
+ * 5. Retries with X-PAYMENT proof header
88
+ */
89
+ declare class x402Client {
90
+ private wallet;
91
+ constructor(wallet: X402Wallet);
92
+ /**
93
+ * Makes an HTTP request, handling x402 payment if required.
94
+ * Non-402 responses pass through unmodified.
95
+ */
96
+ fetch(url: string, options?: X402FetchOptions): Promise<Response>;
97
+ }
98
+
99
+ interface PaymentPTBParams {
100
+ nonce: string;
101
+ amount: string;
102
+ payTo: string;
103
+ }
104
+ /**
105
+ * Builds a payment PTB using USDC coins from the wallet.
106
+ *
107
+ * The Move function signature:
108
+ * process_registry_payment<CoinType>(
109
+ * registry: &mut PaymentRegistry<CoinType>,
110
+ * nonce: String,
111
+ * amount: u64,
112
+ * coin: Coin<CoinType>,
113
+ * receiver: Option<address>,
114
+ * clock: &Clock
115
+ * )
116
+ *
117
+ * Move enforces nonce uniqueness atomically via EDuplicatePayment.
118
+ */
119
+ declare function buildPaymentTransaction(client: SuiClient, senderAddress: string, params: PaymentPTBParams): Promise<Transaction>;
120
+
121
+ /**
122
+ * Verifies an x402 payment by checking the Sui transaction on-chain.
123
+ *
124
+ * Verification steps:
125
+ * 1. Check if the challenge has expired
126
+ * 2. Fetch the transaction from Sui RPC
127
+ * 3. Find the PaymentReceipt event emitted by Payment Kit
128
+ * 4. Validate amount, recipient, and nonce match the request
129
+ *
130
+ * Duplicate nonces are already prevented by Move's EDuplicatePayment.
131
+ * This function only verifies payments that successfully landed on-chain.
132
+ */
133
+ declare function verifyPayment(client: SuiClient, req: VerifyRequest): Promise<VerifyResponse>;
134
+
135
+ /**
136
+ * Sui Payment Kit package ID (mainnet).
137
+ * Source: github.com/MystenLabs/sui-payment-kit Move.lock `original-published-id`.
138
+ */
139
+ declare const PAYMENT_KIT_PACKAGE: string;
140
+ /**
141
+ * t2000's PaymentRegistry object ID (mainnet).
142
+ * Created via `create_registry` on Payment Kit Namespace.
143
+ * Tx: 666ZX1PhfV3PVJtqVZfuToHoVGoTDeWCxzZb1u8aRgmL
144
+ */
145
+ declare const T2000_PAYMENT_REGISTRY_ID: string;
146
+ declare const USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
147
+ declare const CLOCK_ID = "0x6";
148
+ declare const DEFAULT_FACILITATOR_URL = "https://api.t2000.ai/x402";
149
+ declare const PAYMENT_KIT_MODULE = "payment_kit";
150
+ declare const PAYMENT_KIT_FUNCTION = "process_registry_payment";
151
+ declare const PAYMENT_RECEIPT_EVENT_TYPE: string;
152
+
153
+ export { CLOCK_ID, DEFAULT_FACILITATOR_URL, DEFAULT_MAX_PRICE, DEFAULT_TIMEOUT, PAYMENT_KIT_FUNCTION, PAYMENT_KIT_MODULE, PAYMENT_KIT_PACKAGE, PAYMENT_RECEIPT_EVENT_TYPE, type PaymentDetails, type PaymentPTBParams, type PaymentPayload, type PaymentRequired, type SettleRequest, type SettleResponse, T2000_PAYMENT_REGISTRY_ID, USDC_TYPE, type VerifyFailureReason, type VerifyRequest, type VerifyResponse, type X402ClientOptions, type X402FetchOptions, type X402Wallet, X402_HEADERS, buildPaymentTransaction, parsePaymentRequired, verifyPayment, x402Client };
@@ -0,0 +1,153 @@
1
+ import { SuiClient } from '@mysten/sui/client';
2
+ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
3
+ import { Transaction } from '@mysten/sui/transactions';
4
+
5
+ interface PaymentRequired {
6
+ amount: string;
7
+ asset: string;
8
+ network: string;
9
+ payTo: string;
10
+ nonce: string;
11
+ expiresAt: number;
12
+ description?: string;
13
+ }
14
+ interface PaymentPayload {
15
+ txHash: string;
16
+ network: string;
17
+ amount: string;
18
+ nonce: string;
19
+ }
20
+ interface VerifyRequest {
21
+ txHash: string;
22
+ network: string;
23
+ amount: string;
24
+ asset: string;
25
+ payTo: string;
26
+ nonce: string;
27
+ expiresAt: number;
28
+ }
29
+ interface VerifyResponse {
30
+ verified: boolean;
31
+ txHash?: string;
32
+ settledAmount?: string;
33
+ settledAt?: number;
34
+ reason?: VerifyFailureReason;
35
+ }
36
+ type VerifyFailureReason = 'expired' | 'tx_not_found' | 'no_payment_event' | 'amount_mismatch' | 'wrong_recipient' | 'nonce_mismatch';
37
+ interface SettleRequest {
38
+ txHash: string;
39
+ nonce: string;
40
+ }
41
+ interface SettleResponse {
42
+ settled: boolean;
43
+ }
44
+ interface X402ClientOptions {
45
+ maxPrice?: number;
46
+ method?: string;
47
+ headers?: Record<string, string>;
48
+ body?: string;
49
+ timeout?: number;
50
+ dryRun?: boolean;
51
+ onPayment?: (details: PaymentDetails) => void;
52
+ }
53
+ interface PaymentDetails {
54
+ amount: string;
55
+ asset: string;
56
+ payTo: string;
57
+ nonce: string;
58
+ txHash: string;
59
+ }
60
+ type X402FetchOptions = X402ClientOptions;
61
+ declare const X402_HEADERS: {
62
+ readonly PAYMENT_REQUIRED: "payment-required";
63
+ readonly X_PAYMENT: "x-payment";
64
+ };
65
+ declare const DEFAULT_MAX_PRICE = 1;
66
+ declare const DEFAULT_TIMEOUT = 30000;
67
+
68
+ /**
69
+ * Parses the PAYMENT-REQUIRED header from a 402 response.
70
+ * Validates network, asset, expiry, and price constraints.
71
+ */
72
+ declare function parsePaymentRequired(headerValue: string | null, maxPrice?: number): PaymentRequired;
73
+ interface X402Wallet {
74
+ client: SuiClient;
75
+ keypair: Ed25519Keypair;
76
+ address(): string;
77
+ signAndExecute(tx: unknown): Promise<{
78
+ digest: string;
79
+ }>;
80
+ }
81
+ /**
82
+ * x402 client for Sui. Handles the full 402 payment flow:
83
+ * 1. Sends initial request
84
+ * 2. Detects 402 Payment Required
85
+ * 3. Parses payment terms
86
+ * 4. Builds and executes Payment Kit PTB
87
+ * 5. Retries with X-PAYMENT proof header
88
+ */
89
+ declare class x402Client {
90
+ private wallet;
91
+ constructor(wallet: X402Wallet);
92
+ /**
93
+ * Makes an HTTP request, handling x402 payment if required.
94
+ * Non-402 responses pass through unmodified.
95
+ */
96
+ fetch(url: string, options?: X402FetchOptions): Promise<Response>;
97
+ }
98
+
99
+ interface PaymentPTBParams {
100
+ nonce: string;
101
+ amount: string;
102
+ payTo: string;
103
+ }
104
+ /**
105
+ * Builds a payment PTB using USDC coins from the wallet.
106
+ *
107
+ * The Move function signature:
108
+ * process_registry_payment<CoinType>(
109
+ * registry: &mut PaymentRegistry<CoinType>,
110
+ * nonce: String,
111
+ * amount: u64,
112
+ * coin: Coin<CoinType>,
113
+ * receiver: Option<address>,
114
+ * clock: &Clock
115
+ * )
116
+ *
117
+ * Move enforces nonce uniqueness atomically via EDuplicatePayment.
118
+ */
119
+ declare function buildPaymentTransaction(client: SuiClient, senderAddress: string, params: PaymentPTBParams): Promise<Transaction>;
120
+
121
+ /**
122
+ * Verifies an x402 payment by checking the Sui transaction on-chain.
123
+ *
124
+ * Verification steps:
125
+ * 1. Check if the challenge has expired
126
+ * 2. Fetch the transaction from Sui RPC
127
+ * 3. Find the PaymentReceipt event emitted by Payment Kit
128
+ * 4. Validate amount, recipient, and nonce match the request
129
+ *
130
+ * Duplicate nonces are already prevented by Move's EDuplicatePayment.
131
+ * This function only verifies payments that successfully landed on-chain.
132
+ */
133
+ declare function verifyPayment(client: SuiClient, req: VerifyRequest): Promise<VerifyResponse>;
134
+
135
+ /**
136
+ * Sui Payment Kit package ID (mainnet).
137
+ * Source: github.com/MystenLabs/sui-payment-kit Move.lock `original-published-id`.
138
+ */
139
+ declare const PAYMENT_KIT_PACKAGE: string;
140
+ /**
141
+ * t2000's PaymentRegistry object ID (mainnet).
142
+ * Created via `create_registry` on Payment Kit Namespace.
143
+ * Tx: 666ZX1PhfV3PVJtqVZfuToHoVGoTDeWCxzZb1u8aRgmL
144
+ */
145
+ declare const T2000_PAYMENT_REGISTRY_ID: string;
146
+ declare const USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
147
+ declare const CLOCK_ID = "0x6";
148
+ declare const DEFAULT_FACILITATOR_URL = "https://api.t2000.ai/x402";
149
+ declare const PAYMENT_KIT_MODULE = "payment_kit";
150
+ declare const PAYMENT_KIT_FUNCTION = "process_registry_payment";
151
+ declare const PAYMENT_RECEIPT_EVENT_TYPE: string;
152
+
153
+ export { CLOCK_ID, DEFAULT_FACILITATOR_URL, DEFAULT_MAX_PRICE, DEFAULT_TIMEOUT, PAYMENT_KIT_FUNCTION, PAYMENT_KIT_MODULE, PAYMENT_KIT_PACKAGE, PAYMENT_RECEIPT_EVENT_TYPE, type PaymentDetails, type PaymentPTBParams, type PaymentPayload, type PaymentRequired, type SettleRequest, type SettleResponse, T2000_PAYMENT_REGISTRY_ID, USDC_TYPE, type VerifyFailureReason, type VerifyRequest, type VerifyResponse, type X402ClientOptions, type X402FetchOptions, type X402Wallet, X402_HEADERS, buildPaymentTransaction, parsePaymentRequired, verifyPayment, x402Client };
package/dist/index.js ADDED
@@ -0,0 +1,286 @@
1
+ import { usdcToRaw, T2000Error } from '@t2000/sdk';
2
+ import { Transaction } from '@mysten/sui/transactions';
3
+
4
+ // src/client.ts
5
+
6
+ // src/types.ts
7
+ var X402_HEADERS = {
8
+ PAYMENT_REQUIRED: "payment-required",
9
+ X_PAYMENT: "x-payment"
10
+ };
11
+ var DEFAULT_MAX_PRICE = 1;
12
+ var DEFAULT_TIMEOUT = 3e4;
13
+
14
+ // src/constants.ts
15
+ var PAYMENT_KIT_PACKAGE = process.env.PAYMENT_KIT_PACKAGE ?? "0xbc126f1535fba7d641cb9150ad9eae93b104972586ba20f3c60bfe0e53b69bc6";
16
+ var T2000_PAYMENT_REGISTRY_ID = process.env.T2000_PAYMENT_REGISTRY_ID ?? "0x4009dd17305ed1b33352b808e9d0e9eb94d09085b2d5ec0f395c5cdfa2271291";
17
+ var USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
18
+ var CLOCK_ID = "0x6";
19
+ var DEFAULT_FACILITATOR_URL = "https://api.t2000.ai/x402";
20
+ var PAYMENT_KIT_MODULE = "payment_kit";
21
+ var PAYMENT_KIT_FUNCTION = "process_registry_payment";
22
+ var PAYMENT_RECEIPT_EVENT_TYPE = `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::PaymentReceipt`;
23
+
24
+ // src/payment-kit.ts
25
+ async function buildPaymentTransaction(client, senderAddress, params) {
26
+ const { nonce, amount, payTo } = params;
27
+ const rawAmount = usdcToRaw(Number(amount));
28
+ if (!T2000_PAYMENT_REGISTRY_ID) {
29
+ throw new Error(
30
+ "T2000_PAYMENT_REGISTRY_ID is not set. Create a PaymentRegistry<USDC> via Payment Kit before using x402."
31
+ );
32
+ }
33
+ const coins = await client.getCoins({
34
+ owner: senderAddress,
35
+ coinType: USDC_TYPE
36
+ });
37
+ if (coins.data.length === 0) {
38
+ throw new Error("No USDC coins found in wallet");
39
+ }
40
+ const tx = new Transaction();
41
+ let primaryCoin = tx.object(coins.data[0].coinObjectId);
42
+ if (coins.data.length > 1) {
43
+ const otherCoins = coins.data.slice(1).map((c) => tx.object(c.coinObjectId));
44
+ tx.mergeCoins(primaryCoin, otherCoins);
45
+ }
46
+ const [paymentCoin] = tx.splitCoins(primaryCoin, [tx.pure.u64(rawAmount)]);
47
+ tx.moveCall({
48
+ target: `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::${PAYMENT_KIT_FUNCTION}`,
49
+ arguments: [
50
+ tx.object(T2000_PAYMENT_REGISTRY_ID),
51
+ tx.pure.string(nonce),
52
+ tx.pure.u64(rawAmount),
53
+ paymentCoin,
54
+ tx.pure.option("address", payTo),
55
+ tx.object(CLOCK_ID)
56
+ ],
57
+ typeArguments: [USDC_TYPE]
58
+ });
59
+ return tx;
60
+ }
61
+
62
+ // src/client.ts
63
+ function parsePaymentRequired(headerValue, maxPrice = DEFAULT_MAX_PRICE) {
64
+ if (!headerValue) {
65
+ throw new T2000Error(
66
+ "TRANSACTION_FAILED",
67
+ "402 response missing PAYMENT-REQUIRED header"
68
+ );
69
+ }
70
+ let parsed;
71
+ try {
72
+ parsed = JSON.parse(headerValue);
73
+ } catch {
74
+ throw new T2000Error(
75
+ "TRANSACTION_FAILED",
76
+ "Malformed PAYMENT-REQUIRED header: invalid JSON",
77
+ { raw: headerValue }
78
+ );
79
+ }
80
+ if (!parsed.network || !parsed.amount || !parsed.payTo || !parsed.nonce || parsed.expiresAt == null) {
81
+ throw new T2000Error(
82
+ "TRANSACTION_FAILED",
83
+ "PAYMENT-REQUIRED header missing required fields",
84
+ { parsed }
85
+ );
86
+ }
87
+ if (parsed.network !== "sui") {
88
+ throw new T2000Error(
89
+ "UNSUPPORTED_NETWORK",
90
+ `x402 requires network "${parsed.network}" but only Sui is supported`,
91
+ { network: parsed.network }
92
+ );
93
+ }
94
+ if (parsed.asset && parsed.asset !== "USDC") {
95
+ throw new T2000Error(
96
+ "TRANSACTION_FAILED",
97
+ `x402 requires asset "${parsed.asset}" but only USDC is supported`,
98
+ { asset: parsed.asset }
99
+ );
100
+ }
101
+ if (parsed.expiresAt < Date.now() / 1e3) {
102
+ throw new T2000Error(
103
+ "PAYMENT_EXPIRED",
104
+ "x402 payment challenge has expired",
105
+ { expiresAt: parsed.expiresAt }
106
+ );
107
+ }
108
+ const price = Number(parsed.amount);
109
+ if (price > maxPrice) {
110
+ throw new T2000Error(
111
+ "PRICE_EXCEEDS_LIMIT",
112
+ `Requested price $${parsed.amount} exceeds max price $${maxPrice}`,
113
+ { requested: parsed.amount, limit: maxPrice }
114
+ );
115
+ }
116
+ return parsed;
117
+ }
118
+ var x402Client = class {
119
+ wallet;
120
+ constructor(wallet) {
121
+ this.wallet = wallet;
122
+ }
123
+ /**
124
+ * Makes an HTTP request, handling x402 payment if required.
125
+ * Non-402 responses pass through unmodified.
126
+ */
127
+ async fetch(url, options = {}) {
128
+ const {
129
+ maxPrice = DEFAULT_MAX_PRICE,
130
+ method = "GET",
131
+ headers = {},
132
+ body,
133
+ timeout = DEFAULT_TIMEOUT,
134
+ dryRun = false,
135
+ onPayment
136
+ } = options;
137
+ const controller = new AbortController();
138
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
139
+ try {
140
+ const initial = await globalThis.fetch(url, {
141
+ method,
142
+ headers,
143
+ body,
144
+ signal: controller.signal
145
+ });
146
+ if (initial.status !== 402) {
147
+ return initial;
148
+ }
149
+ const paymentRequired = parsePaymentRequired(
150
+ initial.headers.get(X402_HEADERS.PAYMENT_REQUIRED),
151
+ maxPrice
152
+ );
153
+ if (dryRun) {
154
+ return new Response(
155
+ JSON.stringify({
156
+ dryRun: true,
157
+ amount: paymentRequired.amount,
158
+ asset: paymentRequired.asset,
159
+ payTo: paymentRequired.payTo,
160
+ network: paymentRequired.network
161
+ }),
162
+ { status: 200, headers: { "content-type": "application/json" } }
163
+ );
164
+ }
165
+ const tx = await buildPaymentTransaction(
166
+ this.wallet.client,
167
+ this.wallet.address(),
168
+ {
169
+ nonce: paymentRequired.nonce,
170
+ amount: paymentRequired.amount,
171
+ payTo: paymentRequired.payTo
172
+ }
173
+ );
174
+ let result;
175
+ try {
176
+ result = await this.wallet.signAndExecute(tx);
177
+ } catch (err) {
178
+ const msg = err instanceof Error ? err.message : String(err);
179
+ if (msg.includes("EDuplicatePayment") || msg.includes("duplicate")) {
180
+ throw new T2000Error(
181
+ "DUPLICATE_PAYMENT",
182
+ "Payment nonce already used on-chain",
183
+ { nonce: paymentRequired.nonce }
184
+ );
185
+ }
186
+ if (msg.includes("InsufficientCoinBalance") || msg.includes("Insufficient")) {
187
+ throw new T2000Error(
188
+ "INSUFFICIENT_BALANCE",
189
+ "Not enough USDC to complete payment",
190
+ { required: paymentRequired.amount }
191
+ );
192
+ }
193
+ throw new T2000Error(
194
+ "TRANSACTION_FAILED",
195
+ `Payment transaction failed: ${msg}`,
196
+ { nonce: paymentRequired.nonce }
197
+ );
198
+ }
199
+ const paymentDetails = {
200
+ amount: paymentRequired.amount,
201
+ asset: paymentRequired.asset || "USDC",
202
+ payTo: paymentRequired.payTo,
203
+ nonce: paymentRequired.nonce,
204
+ txHash: result.digest
205
+ };
206
+ onPayment?.(paymentDetails);
207
+ const paymentPayload = {
208
+ txHash: result.digest,
209
+ network: "sui",
210
+ amount: paymentRequired.amount,
211
+ nonce: paymentRequired.nonce
212
+ };
213
+ const retry = await globalThis.fetch(url, {
214
+ method,
215
+ headers: {
216
+ ...headers,
217
+ [X402_HEADERS.X_PAYMENT]: JSON.stringify(paymentPayload)
218
+ },
219
+ body,
220
+ signal: controller.signal
221
+ });
222
+ return retry;
223
+ } finally {
224
+ clearTimeout(timeoutId);
225
+ }
226
+ }
227
+ };
228
+ function isPaymentReceipt(event) {
229
+ return event.type === PAYMENT_RECEIPT_EVENT_TYPE;
230
+ }
231
+ function parsePaymentReceiptFields(event) {
232
+ const json = event.parsedJson;
233
+ if (!json) return null;
234
+ return {
235
+ payment_amount: String(json.payment_amount ?? ""),
236
+ receiver: String(json.receiver ?? ""),
237
+ nonce: String(json.nonce ?? ""),
238
+ coin_type: json.coin_type ? String(json.coin_type) : void 0,
239
+ timestamp_ms: json.timestamp_ms ? String(json.timestamp_ms) : void 0
240
+ };
241
+ }
242
+ async function verifyPayment(client, req) {
243
+ if (Date.now() / 1e3 > req.expiresAt) {
244
+ return { verified: false, reason: "expired" };
245
+ }
246
+ let tx;
247
+ try {
248
+ tx = await client.getTransactionBlock({
249
+ digest: req.txHash,
250
+ options: { showEffects: true, showEvents: true }
251
+ });
252
+ } catch {
253
+ return { verified: false, reason: "tx_not_found" };
254
+ }
255
+ if (!tx) {
256
+ return { verified: false, reason: "tx_not_found" };
257
+ }
258
+ const receiptEvent = tx.events?.find(isPaymentReceipt);
259
+ if (!receiptEvent) {
260
+ return { verified: false, reason: "no_payment_event" };
261
+ }
262
+ const fields = parsePaymentReceiptFields(receiptEvent);
263
+ if (!fields) {
264
+ return { verified: false, reason: "no_payment_event" };
265
+ }
266
+ const expectedAmount = usdcToRaw(Number(req.amount));
267
+ if (BigInt(fields.payment_amount) !== expectedAmount) {
268
+ return { verified: false, reason: "amount_mismatch" };
269
+ }
270
+ if (fields.receiver !== req.payTo) {
271
+ return { verified: false, reason: "wrong_recipient" };
272
+ }
273
+ if (fields.nonce !== req.nonce) {
274
+ return { verified: false, reason: "nonce_mismatch" };
275
+ }
276
+ return {
277
+ verified: true,
278
+ txHash: req.txHash,
279
+ settledAmount: req.amount,
280
+ settledAt: Math.floor(Date.now() / 1e3)
281
+ };
282
+ }
283
+
284
+ export { CLOCK_ID, DEFAULT_FACILITATOR_URL, DEFAULT_MAX_PRICE, DEFAULT_TIMEOUT, PAYMENT_KIT_FUNCTION, PAYMENT_KIT_MODULE, PAYMENT_KIT_PACKAGE, PAYMENT_RECEIPT_EVENT_TYPE, T2000_PAYMENT_REGISTRY_ID, USDC_TYPE, X402_HEADERS, buildPaymentTransaction, parsePaymentRequired, verifyPayment, x402Client };
285
+ //# sourceMappingURL=index.js.map
286
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/constants.ts","../src/payment-kit.ts","../src/client.ts","../src/facilitator.ts"],"names":["usdcToRaw"],"mappings":"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","file":"index.js","sourcesContent":["export interface PaymentRequired {\n amount: string;\n asset: string;\n network: string;\n payTo: string;\n nonce: string;\n expiresAt: number;\n description?: string;\n}\n\nexport interface PaymentPayload {\n txHash: string;\n network: string;\n amount: string;\n nonce: string;\n}\n\nexport interface VerifyRequest {\n txHash: string;\n network: string;\n amount: string;\n asset: string;\n payTo: string;\n nonce: string;\n expiresAt: number;\n}\n\nexport interface VerifyResponse {\n verified: boolean;\n txHash?: string;\n settledAmount?: string;\n settledAt?: number;\n reason?: VerifyFailureReason;\n}\n\nexport type VerifyFailureReason =\n | 'expired'\n | 'tx_not_found'\n | 'no_payment_event'\n | 'amount_mismatch'\n | 'wrong_recipient'\n | 'nonce_mismatch';\n\nexport interface SettleRequest {\n txHash: string;\n nonce: string;\n}\n\nexport interface SettleResponse {\n settled: boolean;\n}\n\nexport interface X402ClientOptions {\n maxPrice?: number;\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n timeout?: number;\n dryRun?: boolean;\n onPayment?: (details: PaymentDetails) => void;\n}\n\nexport interface PaymentDetails {\n amount: string;\n asset: string;\n payTo: string;\n nonce: string;\n txHash: string;\n}\n\nexport type X402FetchOptions = X402ClientOptions;\n\nexport const X402_HEADERS = {\n PAYMENT_REQUIRED: 'payment-required',\n X_PAYMENT: 'x-payment',\n} as const;\n\nexport const DEFAULT_MAX_PRICE = 1.0;\nexport const DEFAULT_TIMEOUT = 30_000;\n","/**\n * Sui Payment Kit package ID (mainnet).\n * Source: github.com/MystenLabs/sui-payment-kit Move.lock `original-published-id`.\n */\nexport const PAYMENT_KIT_PACKAGE =\n process.env.PAYMENT_KIT_PACKAGE ??\n '0xbc126f1535fba7d641cb9150ad9eae93b104972586ba20f3c60bfe0e53b69bc6';\n\n/**\n * t2000's PaymentRegistry object ID (mainnet).\n * Created via `create_registry` on Payment Kit Namespace.\n * Tx: 666ZX1PhfV3PVJtqVZfuToHoVGoTDeWCxzZb1u8aRgmL\n */\nexport const T2000_PAYMENT_REGISTRY_ID =\n process.env.T2000_PAYMENT_REGISTRY_ID ??\n '0x4009dd17305ed1b33352b808e9d0e9eb94d09085b2d5ec0f395c5cdfa2271291';\n\nexport const USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n\nexport const CLOCK_ID = '0x6';\n\nexport const DEFAULT_FACILITATOR_URL = 'https://api.t2000.ai/x402';\n\nexport const PAYMENT_KIT_MODULE = 'payment_kit';\nexport const PAYMENT_KIT_FUNCTION = 'process_registry_payment';\nexport const PAYMENT_RECEIPT_EVENT_TYPE = `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::PaymentReceipt`;\n","import { Transaction } from '@mysten/sui/transactions';\nimport type { SuiClient } from '@mysten/sui/client';\nimport { usdcToRaw } from '@t2000/sdk';\nimport {\n PAYMENT_KIT_PACKAGE,\n PAYMENT_KIT_MODULE,\n PAYMENT_KIT_FUNCTION,\n T2000_PAYMENT_REGISTRY_ID,\n USDC_TYPE,\n CLOCK_ID,\n} from './constants.js';\n\nexport interface PaymentPTBParams {\n nonce: string;\n amount: string;\n payTo: string;\n}\n\n/**\n * Builds a payment PTB using USDC coins from the wallet.\n *\n * The Move function signature:\n * process_registry_payment<CoinType>(\n * registry: &mut PaymentRegistry<CoinType>,\n * nonce: String,\n * amount: u64,\n * coin: Coin<CoinType>,\n * receiver: Option<address>,\n * clock: &Clock\n * )\n *\n * Move enforces nonce uniqueness atomically via EDuplicatePayment.\n */\nexport async function buildPaymentTransaction(\n client: SuiClient,\n senderAddress: string,\n params: PaymentPTBParams,\n): Promise<Transaction> {\n const { nonce, amount, payTo } = params;\n const rawAmount = usdcToRaw(Number(amount));\n\n if (!T2000_PAYMENT_REGISTRY_ID) {\n throw new Error(\n 'T2000_PAYMENT_REGISTRY_ID is not set. ' +\n 'Create a PaymentRegistry<USDC> via Payment Kit before using x402.'\n );\n }\n\n const coins = await client.getCoins({\n owner: senderAddress,\n coinType: USDC_TYPE,\n });\n\n if (coins.data.length === 0) {\n throw new Error('No USDC coins found in wallet');\n }\n\n const tx = new Transaction();\n\n let primaryCoin = tx.object(coins.data[0].coinObjectId);\n if (coins.data.length > 1) {\n const otherCoins = coins.data.slice(1).map(c => tx.object(c.coinObjectId));\n tx.mergeCoins(primaryCoin, otherCoins);\n }\n\n const [paymentCoin] = tx.splitCoins(primaryCoin, [tx.pure.u64(rawAmount)]);\n\n tx.moveCall({\n target: `${PAYMENT_KIT_PACKAGE}::${PAYMENT_KIT_MODULE}::${PAYMENT_KIT_FUNCTION}`,\n arguments: [\n tx.object(T2000_PAYMENT_REGISTRY_ID),\n tx.pure.string(nonce),\n tx.pure.u64(rawAmount),\n paymentCoin,\n tx.pure.option('address', payTo),\n tx.object(CLOCK_ID),\n ],\n typeArguments: [USDC_TYPE],\n });\n\n return tx;\n}\n","import type { SuiClient } from '@mysten/sui/client';\nimport type { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';\nimport { T2000Error } from '@t2000/sdk';\nimport type {\n PaymentRequired,\n PaymentPayload,\n X402FetchOptions,\n PaymentDetails,\n} from './types.js';\nimport {\n X402_HEADERS,\n DEFAULT_MAX_PRICE,\n DEFAULT_TIMEOUT,\n} from './types.js';\nimport { buildPaymentTransaction } from './payment-kit.js';\n\n/**\n * Parses the PAYMENT-REQUIRED header from a 402 response.\n * Validates network, asset, expiry, and price constraints.\n */\nexport function parsePaymentRequired(\n headerValue: string | null,\n maxPrice: number = DEFAULT_MAX_PRICE,\n): PaymentRequired {\n if (!headerValue) {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n '402 response missing PAYMENT-REQUIRED header',\n );\n }\n\n let parsed: PaymentRequired;\n try {\n parsed = JSON.parse(headerValue);\n } catch {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n 'Malformed PAYMENT-REQUIRED header: invalid JSON',\n { raw: headerValue },\n );\n }\n\n if (!parsed.network || !parsed.amount || !parsed.payTo || !parsed.nonce || parsed.expiresAt == null) {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n 'PAYMENT-REQUIRED header missing required fields',\n { parsed },\n );\n }\n\n if (parsed.network !== 'sui') {\n throw new T2000Error(\n 'UNSUPPORTED_NETWORK',\n `x402 requires network \"${parsed.network}\" but only Sui is supported`,\n { network: parsed.network },\n );\n }\n\n if (parsed.asset && parsed.asset !== 'USDC') {\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n `x402 requires asset \"${parsed.asset}\" but only USDC is supported`,\n { asset: parsed.asset },\n );\n }\n\n if (parsed.expiresAt < Date.now() / 1000) {\n throw new T2000Error(\n 'PAYMENT_EXPIRED',\n 'x402 payment challenge has expired',\n { expiresAt: parsed.expiresAt },\n );\n }\n\n const price = Number(parsed.amount);\n if (price > maxPrice) {\n throw new T2000Error(\n 'PRICE_EXCEEDS_LIMIT',\n `Requested price $${parsed.amount} exceeds max price $${maxPrice}`,\n { requested: parsed.amount, limit: maxPrice },\n );\n }\n\n return parsed;\n}\n\nexport interface X402Wallet {\n client: SuiClient;\n keypair: Ed25519Keypair;\n address(): string;\n signAndExecute(tx: unknown): Promise<{ digest: string }>;\n}\n\n/**\n * x402 client for Sui. Handles the full 402 payment flow:\n * 1. Sends initial request\n * 2. Detects 402 Payment Required\n * 3. Parses payment terms\n * 4. Builds and executes Payment Kit PTB\n * 5. Retries with X-PAYMENT proof header\n */\nexport class x402Client {\n private wallet: X402Wallet;\n\n constructor(wallet: X402Wallet) {\n this.wallet = wallet;\n }\n\n /**\n * Makes an HTTP request, handling x402 payment if required.\n * Non-402 responses pass through unmodified.\n */\n async fetch(url: string, options: X402FetchOptions = {}): Promise<Response> {\n const {\n maxPrice = DEFAULT_MAX_PRICE,\n method = 'GET',\n headers = {},\n body,\n timeout = DEFAULT_TIMEOUT,\n dryRun = false,\n onPayment,\n } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const initial = await globalThis.fetch(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n\n if (initial.status !== 402) {\n return initial;\n }\n\n const paymentRequired = parsePaymentRequired(\n initial.headers.get(X402_HEADERS.PAYMENT_REQUIRED),\n maxPrice,\n );\n\n if (dryRun) {\n return new Response(\n JSON.stringify({\n dryRun: true,\n amount: paymentRequired.amount,\n asset: paymentRequired.asset,\n payTo: paymentRequired.payTo,\n network: paymentRequired.network,\n }),\n { status: 200, headers: { 'content-type': 'application/json' } },\n );\n }\n\n const tx = await buildPaymentTransaction(\n this.wallet.client,\n this.wallet.address(),\n {\n nonce: paymentRequired.nonce,\n amount: paymentRequired.amount,\n payTo: paymentRequired.payTo,\n },\n );\n\n let result: { digest: string };\n try {\n result = await this.wallet.signAndExecute(tx);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('EDuplicatePayment') || msg.includes('duplicate')) {\n throw new T2000Error(\n 'DUPLICATE_PAYMENT',\n 'Payment nonce already used on-chain',\n { nonce: paymentRequired.nonce },\n );\n }\n if (msg.includes('InsufficientCoinBalance') || msg.includes('Insufficient')) {\n throw new T2000Error(\n 'INSUFFICIENT_BALANCE',\n 'Not enough USDC to complete payment',\n { required: paymentRequired.amount },\n );\n }\n throw new T2000Error(\n 'TRANSACTION_FAILED',\n `Payment transaction failed: ${msg}`,\n { nonce: paymentRequired.nonce },\n );\n }\n\n const paymentDetails: PaymentDetails = {\n amount: paymentRequired.amount,\n asset: paymentRequired.asset || 'USDC',\n payTo: paymentRequired.payTo,\n nonce: paymentRequired.nonce,\n txHash: result.digest,\n };\n\n onPayment?.(paymentDetails);\n\n const paymentPayload: PaymentPayload = {\n txHash: result.digest,\n network: 'sui',\n amount: paymentRequired.amount,\n nonce: paymentRequired.nonce,\n };\n\n const retry = await globalThis.fetch(url, {\n method,\n headers: {\n ...headers,\n [X402_HEADERS.X_PAYMENT]: JSON.stringify(paymentPayload),\n },\n body,\n signal: controller.signal,\n });\n\n return retry;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","import type { SuiClient, SuiEvent } from '@mysten/sui/client';\nimport { usdcToRaw } from '@t2000/sdk';\nimport type {\n VerifyRequest,\n VerifyResponse,\n VerifyFailureReason,\n} from './types.js';\nimport { PAYMENT_RECEIPT_EVENT_TYPE } from './constants.js';\n\ninterface PaymentReceiptFields {\n payment_amount: string;\n receiver: string;\n nonce: string;\n coin_type?: string;\n timestamp_ms?: string;\n}\n\nfunction isPaymentReceipt(event: SuiEvent): boolean {\n return event.type === PAYMENT_RECEIPT_EVENT_TYPE;\n}\n\nfunction parsePaymentReceiptFields(event: SuiEvent): PaymentReceiptFields | null {\n const json = event.parsedJson as Record<string, unknown> | undefined;\n if (!json) return null;\n\n return {\n payment_amount: String(json.payment_amount ?? ''),\n receiver: String(json.receiver ?? ''),\n nonce: String(json.nonce ?? ''),\n coin_type: json.coin_type ? String(json.coin_type) : undefined,\n timestamp_ms: json.timestamp_ms ? String(json.timestamp_ms) : undefined,\n };\n}\n\n/**\n * Verifies an x402 payment by checking the Sui transaction on-chain.\n *\n * Verification steps:\n * 1. Check if the challenge has expired\n * 2. Fetch the transaction from Sui RPC\n * 3. Find the PaymentReceipt event emitted by Payment Kit\n * 4. Validate amount, recipient, and nonce match the request\n *\n * Duplicate nonces are already prevented by Move's EDuplicatePayment.\n * This function only verifies payments that successfully landed on-chain.\n */\nexport async function verifyPayment(\n client: SuiClient,\n req: VerifyRequest,\n): Promise<VerifyResponse> {\n if (Date.now() / 1000 > req.expiresAt) {\n return { verified: false, reason: 'expired' };\n }\n\n let tx;\n try {\n tx = await client.getTransactionBlock({\n digest: req.txHash,\n options: { showEffects: true, showEvents: true },\n });\n } catch {\n return { verified: false, reason: 'tx_not_found' };\n }\n\n if (!tx) {\n return { verified: false, reason: 'tx_not_found' };\n }\n\n const receiptEvent = tx.events?.find(isPaymentReceipt);\n if (!receiptEvent) {\n return { verified: false, reason: 'no_payment_event' };\n }\n\n const fields = parsePaymentReceiptFields(receiptEvent);\n if (!fields) {\n return { verified: false, reason: 'no_payment_event' };\n }\n\n const expectedAmount = usdcToRaw(Number(req.amount));\n if (BigInt(fields.payment_amount) !== expectedAmount) {\n return { verified: false, reason: 'amount_mismatch' };\n }\n\n if (fields.receiver !== req.payTo) {\n return { verified: false, reason: 'wrong_recipient' };\n }\n\n if (fields.nonce !== req.nonce) {\n return { verified: false, reason: 'nonce_mismatch' as VerifyFailureReason };\n }\n\n return {\n verified: true,\n txHash: req.txHash,\n settledAmount: req.amount,\n settledAt: Math.floor(Date.now() / 1000),\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@t2000/x402",
3
+ "version": "0.1.0",
4
+ "description": "x402 payment protocol client for AI agents on Sui — pay for API resources with USDC",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "keywords": [
21
+ "x402",
22
+ "sui",
23
+ "payment",
24
+ "micropayment",
25
+ "usdc",
26
+ "ai",
27
+ "agent",
28
+ "402"
29
+ ],
30
+ "author": "t2000",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/mission69b/t2000",
34
+ "directory": "packages/x402"
35
+ },
36
+ "homepage": "https://t2000.ai",
37
+ "dependencies": {
38
+ "@mysten/sui": "^1",
39
+ "@t2000/sdk": "0.1.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22",
43
+ "eslint": "^9",
44
+ "tsup": "^8",
45
+ "typescript": "^5",
46
+ "vitest": "^3"
47
+ },
48
+ "license": "MIT",
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "dev": "tsup --watch",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest",
54
+ "typecheck": "tsc --noEmit",
55
+ "lint": "eslint src/",
56
+ "clean": "rm -rf dist"
57
+ }
58
+ }