@x402x/client 0.2.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 +190 -0
- package/README.md +712 -0
- package/dist/index.cjs +705 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5126 -0
- package/dist/index.d.ts +5126 -0
- package/dist/index.js +676 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
# @x402x/client
|
|
2
|
+
|
|
3
|
+
> **Client SDK for x402x Serverless Mode** - Execute on-chain contracts directly via facilitator without needing a resource server.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@x402x/client)
|
|
6
|
+
[](https://github.com/nuwa-protocol/x402-exec/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
## What is x402x Serverless Mode?
|
|
9
|
+
|
|
10
|
+
x402x extends the [x402 protocol](https://github.com/coinbase/x402) with two integration modes:
|
|
11
|
+
|
|
12
|
+
### 🏢 Server Mode (Traditional x402)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Client → Resource Server → Facilitator → Blockchain
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Requires deploying and maintaining a backend server
|
|
19
|
+
- Suitable for complex business logic (dynamic pricing, inventory management)
|
|
20
|
+
|
|
21
|
+
### ⚡ Serverless Mode (x402x - This SDK)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Client → Facilitator → Smart Contract (Hook)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **Zero servers** - No backend needed
|
|
28
|
+
- **Zero runtime** - Business logic in smart contracts (Hooks)
|
|
29
|
+
- **Zero complexity** - 3 lines of code to integrate
|
|
30
|
+
- **Permissionless** - Facilitators are completely trustless
|
|
31
|
+
|
|
32
|
+
## Why Use This SDK?
|
|
33
|
+
|
|
34
|
+
### Before (Manual Implementation)
|
|
35
|
+
|
|
36
|
+
200+ lines of boilerplate code to:
|
|
37
|
+
|
|
38
|
+
- Handle 402 responses
|
|
39
|
+
- Calculate commitment hashes
|
|
40
|
+
- Sign EIP-3009 authorizations
|
|
41
|
+
- Encode payment payloads
|
|
42
|
+
- Call facilitator APIs
|
|
43
|
+
|
|
44
|
+
### After (@x402x/client)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { parseDefaultAssetAmount } from "@x402x/core";
|
|
48
|
+
|
|
49
|
+
const atomicAmount = parseDefaultAssetAmount("1", network); // '1000000'
|
|
50
|
+
const client = new X402Client({ wallet, network, facilitatorUrl });
|
|
51
|
+
const result = await client.execute({
|
|
52
|
+
hook: TransferHook.address,
|
|
53
|
+
amount: atomicAmount, // Must be atomic units
|
|
54
|
+
payTo: "0x...",
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**98% less code. 100% type-safe. Production-ready.**
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install @x402x/client @x402x/core
|
|
68
|
+
# or
|
|
69
|
+
pnpm add @x402x/client @x402x/core
|
|
70
|
+
# or
|
|
71
|
+
yarn add @x402x/client @x402x/core
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Basic Usage (React + wagmi)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { X402Client } from '@x402x/client';
|
|
78
|
+
import { TransferHook, parseDefaultAssetAmount } from '@x402x/core';
|
|
79
|
+
import { useWalletClient } from 'wagmi';
|
|
80
|
+
import { publicActions } from 'viem';
|
|
81
|
+
|
|
82
|
+
function PayButton() {
|
|
83
|
+
const { data: wallet } = useWalletClient();
|
|
84
|
+
|
|
85
|
+
const handlePay = async () => {
|
|
86
|
+
// Extend wallet with public actions (required for transaction confirmation)
|
|
87
|
+
const extendedWallet = wallet.extend(publicActions);
|
|
88
|
+
|
|
89
|
+
// Uses default facilitator at https://facilitator.x402x.dev/
|
|
90
|
+
const client = new X402Client({
|
|
91
|
+
wallet: extendedWallet,
|
|
92
|
+
network: 'base-sepolia'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Convert USD amount to atomic units
|
|
96
|
+
const atomicAmount = parseDefaultAssetAmount('1', 'base-sepolia'); // '1000000'
|
|
97
|
+
|
|
98
|
+
const result = await client.execute({
|
|
99
|
+
hook: TransferHook.getAddress('base-sepolia'),
|
|
100
|
+
hookData: TransferHook.encode(),
|
|
101
|
+
amount: atomicAmount, // Must be atomic units
|
|
102
|
+
payTo: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log('Transaction:', result.txHash);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return <button onClick={handlePay}>Pay 1 USDC</button>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> **Note**: The wallet client must be extended with `publicActions` from viem to support transaction confirmation via `waitForTransactionReceipt`. If you're using the React hooks (`useX402Client`), this is done automatically.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Amount Handling
|
|
117
|
+
|
|
118
|
+
The `amount` parameter in `client.execute()` and `prepareSettlement()` **must be in atomic units** (the smallest unit of the token). This follows the same pattern as viem's `parseEther()` and ethers' `parseEther()`.
|
|
119
|
+
|
|
120
|
+
### Converting USD Amounts to Atomic Units
|
|
121
|
+
|
|
122
|
+
Use `parseDefaultAssetAmount()` from `@x402x/core` to convert USD amounts:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { parseDefaultAssetAmount, formatDefaultAssetAmount } from "@x402x/core";
|
|
126
|
+
|
|
127
|
+
// Convert USD to atomic units
|
|
128
|
+
const atomicAmount = parseDefaultAssetAmount("1", "base-sepolia"); // '1000000' (1 USDC)
|
|
129
|
+
const largeAmount = parseDefaultAssetAmount("100", "base-sepolia"); // '100000000' (100 USDC)
|
|
130
|
+
|
|
131
|
+
// Convert atomic units back to USD (for display)
|
|
132
|
+
const displayAmount = formatDefaultAssetAmount("1000000", "base-sepolia"); // '1'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Why Atomic Units?
|
|
136
|
+
|
|
137
|
+
- **Consistency**: Matches viem/ethers standard practice
|
|
138
|
+
- **Precision**: Avoids floating-point precision issues
|
|
139
|
+
- **Clarity**: No ambiguity about what unit is expected
|
|
140
|
+
- **Safety**: Prevents double conversion bugs
|
|
141
|
+
|
|
142
|
+
### Example
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { X402Client } from "@x402x/client";
|
|
146
|
+
import { parseDefaultAssetAmount } from "@x402x/core";
|
|
147
|
+
|
|
148
|
+
const client = new X402Client({ wallet, network: "base-sepolia" });
|
|
149
|
+
|
|
150
|
+
// ✅ Correct: Convert first, then pass atomic units
|
|
151
|
+
const atomicAmount = parseDefaultAssetAmount("5", "base-sepolia");
|
|
152
|
+
await client.execute({ amount: atomicAmount, payTo: "0x..." });
|
|
153
|
+
|
|
154
|
+
// ❌ Wrong: Don't pass USD amounts directly
|
|
155
|
+
await client.execute({ amount: "5", payTo: "0x..." }); // Will fail validation
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## API Reference
|
|
161
|
+
|
|
162
|
+
### High-Level API (Recommended)
|
|
163
|
+
|
|
164
|
+
#### X402Client
|
|
165
|
+
|
|
166
|
+
The main client class that handles the entire settlement flow.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
class X402Client {
|
|
170
|
+
constructor(config: X402ClientConfig);
|
|
171
|
+
execute(params: ExecuteParams): Promise<ExecuteResult>;
|
|
172
|
+
calculateFee(hook: Address, hookData?: Hex): Promise<FeeCalculationResult>;
|
|
173
|
+
waitForTransaction(txHash: Hex): Promise<TransactionReceipt>;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Example:**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { X402Client } from "@x402x/client";
|
|
181
|
+
|
|
182
|
+
// Uses default facilitator at https://facilitator.x402x.dev/
|
|
183
|
+
const client = new X402Client({
|
|
184
|
+
wallet: walletClient,
|
|
185
|
+
network: "base-sepolia",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Or specify custom facilitator
|
|
189
|
+
const client = new X402Client({
|
|
190
|
+
wallet: walletClient,
|
|
191
|
+
network: "base-sepolia",
|
|
192
|
+
facilitatorUrl: "https://custom-facilitator.example.com",
|
|
193
|
+
timeout: 30000, // optional
|
|
194
|
+
confirmationTimeout: 60000, // optional
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Convert USD amount to atomic units
|
|
198
|
+
import { parseDefaultAssetAmount } from "@x402x/core";
|
|
199
|
+
const atomicAmount = parseDefaultAssetAmount("1", "base-sepolia"); // '1000000'
|
|
200
|
+
|
|
201
|
+
const result = await client.execute({
|
|
202
|
+
hook: "0x...",
|
|
203
|
+
hookData: "0x...",
|
|
204
|
+
amount: atomicAmount, // Must be atomic units
|
|
205
|
+
payTo: "0x...",
|
|
206
|
+
facilitatorFee: "10000", // optional, will query if not provided (also atomic units)
|
|
207
|
+
customSalt: "0x...", // optional, will generate if not provided
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### React Hooks
|
|
212
|
+
|
|
213
|
+
##### useX402Client
|
|
214
|
+
|
|
215
|
+
Automatically creates an X402Client using wagmi's wallet connection.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { useX402Client } from '@x402x/client';
|
|
219
|
+
|
|
220
|
+
function MyComponent() {
|
|
221
|
+
// Uses default facilitator at https://facilitator.x402x.dev/
|
|
222
|
+
const client = useX402Client();
|
|
223
|
+
|
|
224
|
+
// Or specify custom facilitator
|
|
225
|
+
const client = useX402Client({
|
|
226
|
+
facilitatorUrl: 'https://custom-facilitator.example.com'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!client) {
|
|
230
|
+
return <div>Please connect your wallet</div>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Use client...
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
##### useExecute
|
|
238
|
+
|
|
239
|
+
Provides automatic state management for settlements.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { useExecute } from '@x402x/client';
|
|
243
|
+
import { parseDefaultAssetAmount } from '@x402x/core';
|
|
244
|
+
|
|
245
|
+
function PayButton() {
|
|
246
|
+
// Uses default facilitator at https://facilitator.x402x.dev/
|
|
247
|
+
const { execute, status, error, result } = useExecute();
|
|
248
|
+
|
|
249
|
+
// Or specify custom facilitator
|
|
250
|
+
const { execute, status, error, result } = useExecute({
|
|
251
|
+
facilitatorUrl: 'https://custom-facilitator.example.com'
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const handlePay = async () => {
|
|
255
|
+
// Convert USD amount to atomic units
|
|
256
|
+
const atomicAmount = parseDefaultAssetAmount('1', 'base-sepolia'); // '1000000'
|
|
257
|
+
|
|
258
|
+
await execute({
|
|
259
|
+
hook: '0x...',
|
|
260
|
+
amount: atomicAmount, // Must be atomic units
|
|
261
|
+
payTo: '0x...'
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<div>
|
|
267
|
+
<button onClick={handlePay} disabled={status !== 'idle'}>
|
|
268
|
+
{status === 'idle' ? 'Pay' : 'Processing...'}
|
|
269
|
+
</button>
|
|
270
|
+
{status === 'success' && <div>✅ TX: {result.txHash}</div>}
|
|
271
|
+
{status === 'error' && <div>❌ {error.message}</div>}
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Terminology
|
|
280
|
+
|
|
281
|
+
Understanding the x402 protocol terminology used in this SDK:
|
|
282
|
+
|
|
283
|
+
### verify
|
|
284
|
+
|
|
285
|
+
**Verify** (from x402 protocol) - Validate a payment payload without executing it on-chain. This is useful for pre-validation before actual settlement.
|
|
286
|
+
|
|
287
|
+
- In x402 protocol: `POST /verify` endpoint
|
|
288
|
+
- In @x402x/core: `verify()` function
|
|
289
|
+
- Use case: Check if payment is valid before committing resources
|
|
290
|
+
|
|
291
|
+
### settle
|
|
292
|
+
|
|
293
|
+
**Settle** (from x402 protocol) - Execute a payment on-chain by submitting it to the blockchain. This is the actual payment execution step.
|
|
294
|
+
|
|
295
|
+
- In x402 protocol: `POST /settle` endpoint
|
|
296
|
+
- In @x402x/core: `settle()` function
|
|
297
|
+
- In @x402x/client: `settle()` function (convenience wrapper)
|
|
298
|
+
- Use case: Submit signed payment for blockchain execution
|
|
299
|
+
|
|
300
|
+
### execute
|
|
301
|
+
|
|
302
|
+
**Execute** (high-level API) - Complete end-to-end payment flow including preparation, signing, settlement, and confirmation.
|
|
303
|
+
|
|
304
|
+
- In @x402x/client: `X402Client.execute()` method
|
|
305
|
+
- Flow: `prepare → sign → settle → wait for confirmation`
|
|
306
|
+
- Use case: One-line payment execution for most developers
|
|
307
|
+
|
|
308
|
+
### API Hierarchy
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
High-Level (Recommended for most developers):
|
|
312
|
+
└─ execute() - Complete flow
|
|
313
|
+
|
|
314
|
+
Low-Level (Advanced use cases):
|
|
315
|
+
├─ prepareSettlement() - Prepare data
|
|
316
|
+
├─ signAuthorization() - Sign with wallet
|
|
317
|
+
└─ settle() - Submit to facilitator
|
|
318
|
+
|
|
319
|
+
Core Protocol (x402 standard):
|
|
320
|
+
├─ verify() - Validate payment
|
|
321
|
+
└─ settle() - Execute payment
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### Low-Level API (Advanced)
|
|
327
|
+
|
|
328
|
+
For users who need full control over the settlement flow.
|
|
329
|
+
|
|
330
|
+
#### prepareSettlement
|
|
331
|
+
|
|
332
|
+
Prepares settlement data for signing.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import { prepareSettlement } from "@x402x/client";
|
|
336
|
+
import { parseDefaultAssetAmount } from "@x402x/core";
|
|
337
|
+
|
|
338
|
+
// Convert USD amount to atomic units
|
|
339
|
+
const atomicAmount = parseDefaultAssetAmount("1", "base-sepolia"); // '1000000'
|
|
340
|
+
|
|
341
|
+
const settlement = await prepareSettlement({
|
|
342
|
+
wallet: walletClient,
|
|
343
|
+
network: "base-sepolia",
|
|
344
|
+
hook: "0x...",
|
|
345
|
+
hookData: "0x...",
|
|
346
|
+
amount: atomicAmount, // Must be atomic units
|
|
347
|
+
payTo: "0x...",
|
|
348
|
+
facilitatorUrl: "https://facilitator.x402x.dev", // Optional: uses default if not provided
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### signAuthorization
|
|
353
|
+
|
|
354
|
+
Signs EIP-3009 authorization.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { signAuthorization } from "@x402x/client";
|
|
358
|
+
|
|
359
|
+
const signed = await signAuthorization(walletClient, settlement);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### settle
|
|
363
|
+
|
|
364
|
+
Submits signed authorization to facilitator.
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { settle } from "@x402x/client";
|
|
368
|
+
|
|
369
|
+
const result = await settle("https://facilitator.x402x.dev", signed);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Examples
|
|
375
|
+
|
|
376
|
+
### Example 1: Simple Payment
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { X402Client } from "@x402x/client";
|
|
380
|
+
import { TransferHook, parseDefaultAssetAmount } from "@x402x/core";
|
|
381
|
+
|
|
382
|
+
// Uses default facilitator at https://facilitator.x402x.dev/
|
|
383
|
+
const client = new X402Client({
|
|
384
|
+
wallet: walletClient,
|
|
385
|
+
network: "base-sepolia",
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Convert USD amount to atomic units
|
|
389
|
+
const atomicAmount = parseDefaultAssetAmount("1", "base-sepolia"); // '1000000'
|
|
390
|
+
|
|
391
|
+
const result = await client.execute({
|
|
392
|
+
hook: TransferHook.getAddress("base-sepolia"),
|
|
393
|
+
hookData: TransferHook.encode(), // Simple transfer mode
|
|
394
|
+
amount: atomicAmount, // Must be atomic units
|
|
395
|
+
payTo: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
console.log("Transaction:", result.txHash);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Example 2: Distributed Transfer (Payroll, Revenue Split)
|
|
402
|
+
|
|
403
|
+
TransferHook supports distributing funds to multiple recipients by percentage:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { X402Client } from "@x402x/client";
|
|
407
|
+
import { TransferHook, parseDefaultAssetAmount, type Split } from "@x402x/core";
|
|
408
|
+
|
|
409
|
+
const client = new X402Client({
|
|
410
|
+
wallet: walletClient,
|
|
411
|
+
network: "base-sepolia",
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Payroll example: Pay 3 employees with different shares
|
|
415
|
+
const payrollAmount = parseDefaultAssetAmount("10", "base-sepolia"); // '10000000' (10 USDC)
|
|
416
|
+
const result = await client.execute({
|
|
417
|
+
hook: TransferHook.getAddress("base-sepolia"),
|
|
418
|
+
hookData: TransferHook.encode([
|
|
419
|
+
{ recipient: "0xEmployee1...", bips: 3000 }, // 30%
|
|
420
|
+
{ recipient: "0xEmployee2...", bips: 4000 }, // 40%
|
|
421
|
+
{ recipient: "0xEmployee3...", bips: 3000 }, // 30%
|
|
422
|
+
]),
|
|
423
|
+
amount: payrollAmount, // Must be atomic units
|
|
424
|
+
payTo: "0xCompany...", // Receives remainder (0% in this case)
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Revenue split example: Platform takes 30%, creator gets 70%
|
|
428
|
+
const revenueAmount = parseDefaultAssetAmount("100", "base-sepolia"); // '100000000' (100 USDC)
|
|
429
|
+
const result2 = await client.execute({
|
|
430
|
+
hook: TransferHook.getAddress("base-sepolia"),
|
|
431
|
+
hookData: TransferHook.encode([
|
|
432
|
+
{ recipient: "0xPlatform...", bips: 3000 }, // 30%
|
|
433
|
+
]),
|
|
434
|
+
amount: revenueAmount, // Must be atomic units
|
|
435
|
+
payTo: "0xCreator...", // Gets remaining 70% automatically
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
console.log("Distributed transfer:", result.txHash);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Split Rules:**
|
|
442
|
+
|
|
443
|
+
- `bips` = basis points (1-10000, where 10000 = 100%)
|
|
444
|
+
- Total bips must be ≤ 10000
|
|
445
|
+
- If total < 10000, remainder goes to `recipient` parameter
|
|
446
|
+
- If total = 10000, `recipient` gets 0
|
|
447
|
+
|
|
448
|
+
### Example 3: NFT Minting (React)
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { useExecute } from '@x402x/client';
|
|
452
|
+
import { NFTMintHook, parseDefaultAssetAmount } from '@x402x/core';
|
|
453
|
+
|
|
454
|
+
function MintNFT() {
|
|
455
|
+
// Uses default facilitator
|
|
456
|
+
const { execute, status, error } = useExecute();
|
|
457
|
+
|
|
458
|
+
const handleMint = async () => {
|
|
459
|
+
// Convert USD amount to atomic units
|
|
460
|
+
const atomicAmount = parseDefaultAssetAmount('5', 'base-sepolia'); // '5000000'
|
|
461
|
+
|
|
462
|
+
const result = await execute({
|
|
463
|
+
hook: NFTMintHook.getAddress('base-sepolia'),
|
|
464
|
+
hookData: NFTMintHook.encode({
|
|
465
|
+
collection: '0x...',
|
|
466
|
+
tokenId: 1
|
|
467
|
+
}),
|
|
468
|
+
amount: atomicAmount, // Must be atomic units
|
|
469
|
+
payTo: '0x...'
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
alert(`NFT Minted! TX: ${result.txHash}`);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
return (
|
|
476
|
+
<button onClick={handleMint} disabled={status !== 'idle'}>
|
|
477
|
+
{status === 'idle' ? 'Mint NFT for 5 USDC' : 'Processing...'}
|
|
478
|
+
</button>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Example 4: Revenue Split (Low-Level API)
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
import { prepareSettlement, signAuthorization, settle } from "@x402x/client";
|
|
487
|
+
import { calculateFacilitatorFee, TransferHook, parseDefaultAssetAmount } from "@x402x/core";
|
|
488
|
+
|
|
489
|
+
// 1. Query minimum fee
|
|
490
|
+
const hookData = TransferHook.encode([
|
|
491
|
+
{ recipient: "0xAlice...", bips: 6000 }, // 60% to Alice
|
|
492
|
+
{ recipient: "0xBob...", bips: 4000 }, // 40% to Bob
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
const feeEstimate = await calculateFacilitatorFee(
|
|
496
|
+
"https://facilitator.x402x.dev",
|
|
497
|
+
"base-sepolia",
|
|
498
|
+
TransferHook.getAddress("base-sepolia"),
|
|
499
|
+
hookData,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// 2. Convert USD amount to atomic units
|
|
503
|
+
const atomicAmount = parseDefaultAssetAmount("10", "base-sepolia"); // '10000000'
|
|
504
|
+
|
|
505
|
+
// 3. Prepare settlement
|
|
506
|
+
const settlement = await prepareSettlement({
|
|
507
|
+
wallet: walletClient,
|
|
508
|
+
network: "base-sepolia",
|
|
509
|
+
hook: TransferHook.getAddress("base-sepolia"),
|
|
510
|
+
hookData,
|
|
511
|
+
amount: atomicAmount, // Must be atomic units
|
|
512
|
+
payTo: "0xCharity...", // Receives 0% (full split)
|
|
513
|
+
facilitatorFee: feeEstimate.facilitatorFee,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// 3. Sign authorization
|
|
517
|
+
const signed = await signAuthorization(walletClient, settlement);
|
|
518
|
+
|
|
519
|
+
// 4. Submit to facilitator
|
|
520
|
+
const result = await settle("https://facilitator.x402x.dev", signed);
|
|
521
|
+
|
|
522
|
+
console.log("Transaction:", result.transaction);
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Example 5: Vue 3 Integration
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
import { ref } from "vue";
|
|
529
|
+
import { X402Client } from "@x402x/client";
|
|
530
|
+
import { TransferHook, parseDefaultAssetAmount } from "@x402x/core";
|
|
531
|
+
|
|
532
|
+
export function usePayment() {
|
|
533
|
+
const status = ref("idle");
|
|
534
|
+
const error = ref(null);
|
|
535
|
+
|
|
536
|
+
const pay = async (walletClient, usdAmount, recipient, network = "base-sepolia") => {
|
|
537
|
+
status.value = "processing";
|
|
538
|
+
error.value = null;
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
const client = new X402Client({
|
|
542
|
+
wallet: walletClient,
|
|
543
|
+
network,
|
|
544
|
+
facilitatorUrl: import.meta.env.VITE_FACILITATOR_URL,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Convert USD amount to atomic units
|
|
548
|
+
const atomicAmount = parseDefaultAssetAmount(usdAmount, network);
|
|
549
|
+
|
|
550
|
+
const result = await client.execute({
|
|
551
|
+
hook: TransferHook.getAddress(network),
|
|
552
|
+
hookData: TransferHook.encode(),
|
|
553
|
+
amount: atomicAmount, // Must be atomic units
|
|
554
|
+
payTo: recipient,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
status.value = "success";
|
|
558
|
+
return result;
|
|
559
|
+
} catch (err) {
|
|
560
|
+
error.value = err;
|
|
561
|
+
status.value = "error";
|
|
562
|
+
throw err;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
return { status, error, pay };
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Error Handling
|
|
573
|
+
|
|
574
|
+
The SDK provides typed error classes for better error handling:
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
import {
|
|
578
|
+
X402ClientError,
|
|
579
|
+
NetworkError,
|
|
580
|
+
SigningError,
|
|
581
|
+
FacilitatorError,
|
|
582
|
+
TransactionError,
|
|
583
|
+
ValidationError,
|
|
584
|
+
} from "@x402x/client";
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
await client.execute(params);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (error instanceof ValidationError) {
|
|
590
|
+
console.error("Invalid parameters:", error.message);
|
|
591
|
+
} else if (error instanceof SigningError) {
|
|
592
|
+
if (error.code === "USER_REJECTED") {
|
|
593
|
+
console.log("User rejected signing");
|
|
594
|
+
}
|
|
595
|
+
} else if (error instanceof FacilitatorError) {
|
|
596
|
+
console.error("Facilitator error:", error.statusCode, error.response);
|
|
597
|
+
} else if (error instanceof TransactionError) {
|
|
598
|
+
console.error("Transaction failed:", error.txHash);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## TypeScript Support
|
|
606
|
+
|
|
607
|
+
Full TypeScript support with comprehensive type definitions:
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
import type {
|
|
611
|
+
X402ClientConfig,
|
|
612
|
+
ExecuteParams,
|
|
613
|
+
ExecuteResult,
|
|
614
|
+
SettlementData,
|
|
615
|
+
SignedAuthorization,
|
|
616
|
+
FeeCalculationResult,
|
|
617
|
+
ExecuteStatus,
|
|
618
|
+
} from "@x402x/client";
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Supported Networks
|
|
624
|
+
|
|
625
|
+
- Base Sepolia (testnet): `base-sepolia`
|
|
626
|
+
- Base (mainnet): `base`
|
|
627
|
+
- X-Layer (mainnet): `x-layer`
|
|
628
|
+
- X-Layer Testnet: `x-layer-testnet`
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Requirements
|
|
633
|
+
|
|
634
|
+
- Node.js 18+
|
|
635
|
+
- React 18+ (for hooks)
|
|
636
|
+
- wagmi 2+ (for wallet connection)
|
|
637
|
+
- viem 2+ (for Ethereum interactions)
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Migration from Manual Implementation
|
|
642
|
+
|
|
643
|
+
### Before
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
// 200+ lines of manual implementation
|
|
647
|
+
import { usePayment } from "./hooks/usePayment";
|
|
648
|
+
|
|
649
|
+
function Component() {
|
|
650
|
+
const { pay, status, error } = usePayment();
|
|
651
|
+
|
|
652
|
+
const handlePay = () => {
|
|
653
|
+
pay("/api/transfer", "base-sepolia", { amount: "1000000" });
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### After
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
// 10 lines with @x402x/client (no facilitatorUrl needed!)
|
|
662
|
+
import { useExecute } from "@x402x/client";
|
|
663
|
+
import { TransferHook, parseDefaultAssetAmount } from "@x402x/core";
|
|
664
|
+
|
|
665
|
+
function Component() {
|
|
666
|
+
// Uses default facilitator automatically
|
|
667
|
+
const { execute, status, error } = useExecute();
|
|
668
|
+
|
|
669
|
+
const handlePay = async () => {
|
|
670
|
+
// Convert USD amount to atomic units
|
|
671
|
+
const atomicAmount = parseDefaultAssetAmount("1", "base-sepolia"); // '1000000'
|
|
672
|
+
|
|
673
|
+
await execute({
|
|
674
|
+
hook: TransferHook.address,
|
|
675
|
+
amount: atomicAmount, // Must be atomic units
|
|
676
|
+
payTo: "0x...",
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Related Packages
|
|
685
|
+
|
|
686
|
+
- [@x402x/core](../core) - Core utilities and commitment calculation
|
|
687
|
+
- [@x402x/facilitator](../../facilitator) - Facilitator server implementation
|
|
688
|
+
- [x402](https://github.com/coinbase/x402) - Base x402 protocol
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## Contributing
|
|
693
|
+
|
|
694
|
+
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup and contribution guidelines.
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## License
|
|
699
|
+
|
|
700
|
+
Apache-2.0 - see [LICENSE](../../LICENSE) for details.
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## Support
|
|
705
|
+
|
|
706
|
+
- [Documentation](https://x402x.dev/)
|
|
707
|
+
- [GitHub Issues](https://github.com/nuwa-protocol/x402-exec/issues)
|
|
708
|
+
- [Discord Community](https://discord.gg/nuwa-protocol)
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
**Built with ❤️ by [Nuwa Protocol](https://github.com/nuwa-protocol)**
|