uvd-x402-sdk 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -1396
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +1 -1
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{index-fIhvHqCQ.d.mts → index-BYIugZlE.d.mts} +17 -0
- package/dist/{index-fIhvHqCQ.d.ts → index-BYIugZlE.d.ts} +17 -0
- package/dist/{index-DmJGKD9r.d.ts → index-Cwi_VM05.d.ts} +1 -1
- package/dist/{index-C6Vxnneo.d.mts → index-D3PO3jLW.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +4 -1
- package/dist/providers/algorand/index.d.ts +4 -1
- package/dist/providers/algorand/index.js +25 -15
- package/dist/providers/algorand/index.js.map +1 -1
- package/dist/providers/algorand/index.mjs +25 -15
- package/dist/providers/algorand/index.mjs.map +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/near/index.d.mts +40 -2
- package/dist/providers/near/index.d.ts +40 -2
- package/dist/providers/near/index.js +68 -1
- package/dist/providers/near/index.js.map +1 -1
- package/dist/providers/near/index.mjs +68 -1
- package/dist/providers/near/index.mjs.map +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/solana/index.js +3 -0
- package/dist/providers/solana/index.js.map +1 -1
- package/dist/providers/solana/index.mjs +3 -0
- package/dist/providers/solana/index.mjs.map +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/providers/algorand/index.ts +33 -20
- package/src/providers/near/index.ts +101 -1
- package/src/providers/solana/index.ts +9 -2
- package/src/types/index.ts +18 -0
package/README.md
CHANGED
|
@@ -1,51 +1,17 @@
|
|
|
1
1
|
# uvd-x402-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Gasless crypto payments across 16 blockchain networks using the x402 protocol.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Users sign a message or transaction, and the Ultravioleta facilitator handles on-chain settlement. No gas fees for users.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **Multi-Stablecoin**: USDC, EURC, AUSD, PYUSD, USDT
|
|
11
|
-
- **x402 v1 & v2**:
|
|
12
|
-
- **Gasless
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **Bazaar Discovery**: Discover and register x402-enabled resources
|
|
16
|
-
- **Escrow & Refunds**: Hold payments in escrow with refund and dispute support
|
|
17
|
-
- **Type-Safe**: Comprehensive TypeScript definitions
|
|
18
|
-
- **Framework Agnostic**: Works with any JavaScript framework
|
|
19
|
-
- **React Hooks**: First-class React integration
|
|
20
|
-
- **Wagmi/RainbowKit**: Dedicated adapter for wagmi-based apps
|
|
21
|
-
- **Modular**: Import only what you need
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
27
|
-
|
|
28
|
-
const client = new X402Client({ defaultChain: 'base' });
|
|
29
|
-
|
|
30
|
-
// Connect wallet
|
|
31
|
-
const address = await client.connect('base');
|
|
32
|
-
|
|
33
|
-
// Create payment
|
|
34
|
-
const result = await client.createPayment({
|
|
35
|
-
recipient: '0x...',
|
|
36
|
-
amount: '10.00',
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Use in your API request
|
|
40
|
-
await fetch('/api/purchase', {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: {
|
|
43
|
-
'Content-Type': 'application/json',
|
|
44
|
-
'X-PAYMENT': result.paymentHeader,
|
|
45
|
-
},
|
|
46
|
-
body: JSON.stringify({ item: 'premium-feature' }),
|
|
47
|
-
});
|
|
48
|
-
```
|
|
9
|
+
- **16 Networks**: EVM (10), Solana, Fogo, Stellar, NEAR, Algorand (2)
|
|
10
|
+
- **Multi-Stablecoin**: USDC, EURC, AUSD, PYUSD, USDT
|
|
11
|
+
- **x402 v1 & v2**: Both protocol versions with auto-detection
|
|
12
|
+
- **Gasless**: Facilitator pays all network fees
|
|
13
|
+
- **Type-Safe**: Full TypeScript support
|
|
14
|
+
- **React & Wagmi**: First-class integrations
|
|
49
15
|
|
|
50
16
|
## Installation
|
|
51
17
|
|
|
@@ -53,13 +19,13 @@ await fetch('/api/purchase', {
|
|
|
53
19
|
npm install uvd-x402-sdk
|
|
54
20
|
```
|
|
55
21
|
|
|
56
|
-
### Peer Dependencies
|
|
22
|
+
### Peer Dependencies
|
|
57
23
|
|
|
58
24
|
```bash
|
|
59
|
-
# EVM
|
|
25
|
+
# EVM (included by default)
|
|
60
26
|
npm install ethers@^6
|
|
61
27
|
|
|
62
|
-
# Solana
|
|
28
|
+
# Solana/Fogo
|
|
63
29
|
npm install @solana/web3.js @solana/spl-token
|
|
64
30
|
|
|
65
31
|
# Stellar
|
|
@@ -68,112 +34,120 @@ npm install @stellar/stellar-sdk @stellar/freighter-api
|
|
|
68
34
|
# NEAR
|
|
69
35
|
npm install @near-wallet-selector/core @near-wallet-selector/my-near-wallet
|
|
70
36
|
|
|
71
|
-
#
|
|
72
|
-
npm install
|
|
37
|
+
# Algorand
|
|
38
|
+
npm install algosdk lute-connect
|
|
73
39
|
```
|
|
74
40
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## WalletConnect Integration
|
|
78
|
-
|
|
79
|
-
The SDK works with any EVM wallet that injects into `window.ethereum`, including:
|
|
80
|
-
|
|
81
|
-
- **Browser extensions**: MetaMask, Rainbow, Rabby, Coinbase Wallet
|
|
82
|
-
- **Mobile wallets**: Rainbow, MetaMask Mobile, Trust Wallet (via WalletConnect)
|
|
83
|
-
|
|
84
|
-
### Using WalletConnect (Mobile Wallets)
|
|
41
|
+
## Quick Start
|
|
85
42
|
|
|
86
|
-
|
|
43
|
+
### EVM Chains
|
|
87
44
|
|
|
88
45
|
```typescript
|
|
89
|
-
import { EthereumProvider } from '@walletconnect/ethereum-provider';
|
|
90
46
|
import { X402Client } from 'uvd-x402-sdk';
|
|
91
47
|
|
|
92
|
-
// 1. Initialize WalletConnect
|
|
93
|
-
const walletConnectProvider = await EthereumProvider.init({
|
|
94
|
-
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // Get from cloud.walletconnect.com
|
|
95
|
-
chains: [8453], // Base mainnet
|
|
96
|
-
optionalChains: [1, 137, 42161, 10], // ETH, Polygon, Arbitrum, Optimism
|
|
97
|
-
showQrModal: true,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// 2. Connect wallet (shows QR code for mobile wallet to scan)
|
|
101
|
-
await walletConnectProvider.connect();
|
|
102
|
-
|
|
103
|
-
// 3. Use the SDK normally
|
|
104
48
|
const client = new X402Client({ defaultChain: 'base' });
|
|
105
49
|
const address = await client.connect('base');
|
|
106
50
|
|
|
107
|
-
// 4. Create payment
|
|
108
51
|
const result = await client.createPayment({
|
|
109
|
-
recipient: '
|
|
52
|
+
recipient: '0x...',
|
|
110
53
|
amount: '10.00',
|
|
111
54
|
});
|
|
112
|
-
```
|
|
113
55
|
|
|
114
|
-
|
|
56
|
+
await fetch('/api/purchase', {
|
|
57
|
+
headers: { 'X-PAYMENT': result.paymentHeader },
|
|
58
|
+
});
|
|
59
|
+
```
|
|
115
60
|
|
|
116
|
-
|
|
61
|
+
### Solana
|
|
117
62
|
|
|
118
63
|
```typescript
|
|
119
|
-
import {
|
|
120
|
-
|
|
121
|
-
const client = new X402Client({ defaultChain: 'base' });
|
|
64
|
+
import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
65
|
+
import { getChainByName } from 'uvd-x402-sdk';
|
|
122
66
|
|
|
123
|
-
|
|
124
|
-
const address = await
|
|
67
|
+
const svm = new SVMProvider();
|
|
68
|
+
const address = await svm.connect();
|
|
69
|
+
const chainConfig = getChainByName('solana')!;
|
|
125
70
|
|
|
126
|
-
const
|
|
127
|
-
recipient: '
|
|
71
|
+
const payload = await svm.signPayment({
|
|
72
|
+
recipient: '5Y32Dk6weq1LrMRdujpJyDbTN3SjwXGoQS9QN39WQ9Cq',
|
|
128
73
|
amount: '10.00',
|
|
129
|
-
});
|
|
74
|
+
}, chainConfig);
|
|
75
|
+
|
|
76
|
+
const header = svm.encodePaymentHeader(payload, chainConfig);
|
|
130
77
|
```
|
|
131
78
|
|
|
132
|
-
|
|
79
|
+
### Algorand
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { AlgorandProvider } from 'uvd-x402-sdk/algorand';
|
|
83
|
+
import { getChainByName } from 'uvd-x402-sdk';
|
|
84
|
+
|
|
85
|
+
const algorand = new AlgorandProvider();
|
|
86
|
+
const address = await algorand.connect(); // Lute or Pera wallet
|
|
87
|
+
const chainConfig = getChainByName('algorand')!;
|
|
88
|
+
|
|
89
|
+
const payload = await algorand.signPayment({
|
|
90
|
+
recipient: 'NCDSNUQ2QLXDMJXRALAW4CRUSSKG4IS37MVOFDQQPC45SE4EBZO42U6ZX4',
|
|
91
|
+
amount: '10.00',
|
|
92
|
+
}, chainConfig);
|
|
93
|
+
|
|
94
|
+
const header = algorand.encodePaymentHeader(payload, chainConfig);
|
|
95
|
+
```
|
|
133
96
|
|
|
134
|
-
|
|
97
|
+
Algorand uses atomic transaction groups:
|
|
98
|
+
- Transaction 0: Fee payment (unsigned, facilitator signs)
|
|
99
|
+
- Transaction 1: USDC ASA transfer (signed by user)
|
|
135
100
|
|
|
136
|
-
|
|
101
|
+
### Stellar
|
|
137
102
|
|
|
138
103
|
```typescript
|
|
139
|
-
import {
|
|
140
|
-
import {
|
|
104
|
+
import { StellarProvider } from 'uvd-x402-sdk/stellar';
|
|
105
|
+
import { getChainByName } from 'uvd-x402-sdk';
|
|
141
106
|
|
|
142
|
-
|
|
143
|
-
|
|
107
|
+
const stellar = new StellarProvider();
|
|
108
|
+
const address = await stellar.connect(); // Freighter wallet
|
|
109
|
+
const chainConfig = getChainByName('stellar')!;
|
|
144
110
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
chainName: 'base', // optional, defaults to 'base'
|
|
150
|
-
});
|
|
111
|
+
const payload = await stellar.signPayment({
|
|
112
|
+
recipient: 'GD3FWQ4QFSCO2F2KVXZPQWOC27CQHXHYCRCRRZBMWU3DNOZW2IIGOU54',
|
|
113
|
+
amount: '10.00',
|
|
114
|
+
}, chainConfig);
|
|
151
115
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
headers: { 'X-PAYMENT': paymentHeader },
|
|
155
|
-
method: 'POST',
|
|
156
|
-
});
|
|
157
|
-
};
|
|
116
|
+
const header = stellar.encodePaymentHeader(payload);
|
|
117
|
+
```
|
|
158
118
|
|
|
159
|
-
|
|
160
|
-
|
|
119
|
+
### NEAR
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { NEARProvider } from 'uvd-x402-sdk/near';
|
|
123
|
+
import { getChainByName } from 'uvd-x402-sdk';
|
|
124
|
+
|
|
125
|
+
const near = new NEARProvider();
|
|
126
|
+
const accountId = await near.connect(); // MyNearWallet
|
|
127
|
+
const chainConfig = getChainByName('near')!;
|
|
128
|
+
|
|
129
|
+
const payload = await near.signPayment({
|
|
130
|
+
recipient: 'merchant.near',
|
|
131
|
+
amount: '10.00',
|
|
132
|
+
}, chainConfig);
|
|
133
|
+
|
|
134
|
+
const header = near.encodePaymentHeader(payload);
|
|
161
135
|
```
|
|
162
136
|
|
|
163
|
-
|
|
137
|
+
## Wagmi/RainbowKit
|
|
164
138
|
|
|
165
139
|
```typescript
|
|
166
140
|
import { useWalletClient } from 'wagmi';
|
|
167
|
-
import {
|
|
141
|
+
import { createPaymentFromWalletClient } from 'uvd-x402-sdk/wagmi';
|
|
168
142
|
|
|
169
143
|
function PayButton() {
|
|
170
144
|
const { data: walletClient } = useWalletClient();
|
|
171
|
-
const { createPayment, isReady } = useX402Wagmi(walletClient);
|
|
172
145
|
|
|
173
146
|
const handlePay = async () => {
|
|
174
|
-
const paymentHeader = await
|
|
175
|
-
recipient: '
|
|
147
|
+
const paymentHeader = await createPaymentFromWalletClient(walletClient, {
|
|
148
|
+
recipient: '0x...',
|
|
176
149
|
amount: '10.00',
|
|
150
|
+
chainName: 'base',
|
|
177
151
|
});
|
|
178
152
|
|
|
179
153
|
await fetch('/api/purchase', {
|
|
@@ -181,1353 +155,201 @@ function PayButton() {
|
|
|
181
155
|
});
|
|
182
156
|
};
|
|
183
157
|
|
|
184
|
-
return
|
|
185
|
-
<button onClick={handlePay} disabled={!isReady}>
|
|
186
|
-
Pay $10 USDC
|
|
187
|
-
</button>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Full Example with RainbowKit
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
import { ConnectButton } from '@rainbow-me/rainbowkit';
|
|
196
|
-
import { useWalletClient, useAccount } from 'wagmi';
|
|
197
|
-
import { createPaymentFromWalletClient } from 'uvd-x402-sdk/wagmi';
|
|
198
|
-
|
|
199
|
-
function App() {
|
|
200
|
-
const { data: walletClient } = useWalletClient();
|
|
201
|
-
const { isConnected } = useAccount();
|
|
202
|
-
|
|
203
|
-
const handlePurchase = async () => {
|
|
204
|
-
if (!walletClient) return;
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const paymentHeader = await createPaymentFromWalletClient(walletClient, {
|
|
208
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
209
|
-
amount: '5.00',
|
|
210
|
-
chainName: 'base',
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const response = await fetch('/api/purchase', {
|
|
214
|
-
method: 'POST',
|
|
215
|
-
headers: {
|
|
216
|
-
'Content-Type': 'application/json',
|
|
217
|
-
'X-PAYMENT': paymentHeader,
|
|
218
|
-
},
|
|
219
|
-
body: JSON.stringify({ item: 'premium-feature' }),
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
if (response.ok) {
|
|
223
|
-
alert('Purchase successful!');
|
|
224
|
-
}
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error('Payment failed:', error);
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
return (
|
|
231
|
-
<div>
|
|
232
|
-
<ConnectButton />
|
|
233
|
-
{isConnected && (
|
|
234
|
-
<button onClick={handlePurchase}>
|
|
235
|
-
Buy Premium ($5 USDC)
|
|
236
|
-
</button>
|
|
237
|
-
)}
|
|
238
|
-
</div>
|
|
239
|
-
);
|
|
158
|
+
return <button onClick={handlePay}>Pay $10</button>;
|
|
240
159
|
}
|
|
241
160
|
```
|
|
242
161
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
## Network Examples
|
|
246
|
-
|
|
247
|
-
### EVM Chains (10 Networks)
|
|
248
|
-
|
|
249
|
-
All EVM chains use EIP-712 typed data signing with ERC-3009 TransferWithAuthorization.
|
|
250
|
-
|
|
251
|
-
#### Base (Recommended - Fastest & Cheapest)
|
|
162
|
+
## Multi-Stablecoin (EVM)
|
|
252
163
|
|
|
253
164
|
```typescript
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const client = new X402Client({ defaultChain: 'base' });
|
|
257
|
-
|
|
258
|
-
// Connect to Base
|
|
259
|
-
const address = await client.connect('base');
|
|
260
|
-
console.log('Connected:', address);
|
|
261
|
-
|
|
262
|
-
// Check balance
|
|
263
|
-
const balance = await client.getBalance();
|
|
264
|
-
console.log('USDC Balance:', balance);
|
|
265
|
-
|
|
266
|
-
// Create payment
|
|
165
|
+
// Pay with EURC instead of USDC
|
|
267
166
|
const result = await client.createPayment({
|
|
268
|
-
recipient: '
|
|
167
|
+
recipient: '0x...',
|
|
269
168
|
amount: '10.00',
|
|
169
|
+
tokenType: 'eurc', // 'usdc' | 'eurc' | 'ausd' | 'pyusd' | 'usdt'
|
|
270
170
|
});
|
|
271
171
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
#### Ethereum
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
279
|
-
|
|
280
|
-
const client = new X402Client({ defaultChain: 'ethereum' });
|
|
281
|
-
const address = await client.connect('ethereum');
|
|
172
|
+
// Check token availability
|
|
173
|
+
import { getSupportedTokens, isTokenSupported } from 'uvd-x402-sdk';
|
|
282
174
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
});
|
|
175
|
+
getSupportedTokens('ethereum'); // ['usdc', 'eurc', 'ausd', 'pyusd']
|
|
176
|
+
getSupportedTokens('base'); // ['usdc', 'eurc']
|
|
177
|
+
isTokenSupported('base', 'eurc'); // true
|
|
287
178
|
```
|
|
288
179
|
|
|
289
|
-
|
|
180
|
+
## AUSD on Solana (Token2022)
|
|
290
181
|
|
|
291
182
|
```typescript
|
|
292
|
-
import {
|
|
183
|
+
import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
184
|
+
import { getChainByName } from 'uvd-x402-sdk';
|
|
293
185
|
|
|
294
|
-
const
|
|
295
|
-
const
|
|
186
|
+
const svm = new SVMProvider();
|
|
187
|
+
const chainConfig = getChainByName('solana')!;
|
|
296
188
|
|
|
297
|
-
|
|
298
|
-
|
|
189
|
+
// AUSD uses Token2022 program
|
|
190
|
+
const payload = await svm.signPayment({
|
|
191
|
+
recipient: '5Y32Dk...',
|
|
299
192
|
amount: '10.00',
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
#### Arbitrum
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
193
|
+
token: 'ausd', // Token2022 AUSD
|
|
194
|
+
}, chainConfig);
|
|
307
195
|
|
|
308
|
-
const
|
|
309
|
-
const address = await client.connect('arbitrum');
|
|
310
|
-
|
|
311
|
-
const result = await client.createPayment({
|
|
312
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
313
|
-
amount: '10.00',
|
|
314
|
-
});
|
|
196
|
+
const header = svm.encodePaymentHeader(payload, chainConfig);
|
|
315
197
|
```
|
|
316
198
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
```typescript
|
|
320
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
321
|
-
|
|
322
|
-
const client = new X402Client({ defaultChain: 'optimism' });
|
|
323
|
-
const address = await client.connect('optimism');
|
|
199
|
+
## Supported Networks
|
|
324
200
|
|
|
325
|
-
|
|
326
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
327
|
-
amount: '10.00',
|
|
328
|
-
});
|
|
329
|
-
```
|
|
201
|
+
### EVM (10)
|
|
330
202
|
|
|
331
|
-
|
|
203
|
+
| Network | Chain ID | Tokens |
|
|
204
|
+
|---------|----------|--------|
|
|
205
|
+
| Base | 8453 | USDC, EURC |
|
|
206
|
+
| Ethereum | 1 | USDC, EURC, AUSD, PYUSD, USDT |
|
|
207
|
+
| Polygon | 137 | USDC, AUSD |
|
|
208
|
+
| Arbitrum | 42161 | USDC, AUSD, USDT |
|
|
209
|
+
| Optimism | 10 | USDC |
|
|
210
|
+
| Avalanche | 43114 | USDC, EURC, AUSD |
|
|
211
|
+
| Celo | 42220 | USDC |
|
|
212
|
+
| HyperEVM | 999 | USDC |
|
|
213
|
+
| Unichain | 130 | USDC |
|
|
214
|
+
| Monad | 143 | USDC, AUSD |
|
|
332
215
|
|
|
333
|
-
|
|
334
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
216
|
+
### SVM (2)
|
|
335
217
|
|
|
336
|
-
|
|
337
|
-
|
|
218
|
+
| Network | Tokens | Wallet |
|
|
219
|
+
|---------|--------|--------|
|
|
220
|
+
| Solana | USDC, AUSD | Phantom |
|
|
221
|
+
| Fogo | USDC | Phantom |
|
|
338
222
|
|
|
339
|
-
|
|
340
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
341
|
-
amount: '10.00',
|
|
342
|
-
});
|
|
343
|
-
```
|
|
223
|
+
### Algorand (2)
|
|
344
224
|
|
|
345
|
-
|
|
225
|
+
| Network | USDC ASA | Wallet |
|
|
226
|
+
|---------|----------|--------|
|
|
227
|
+
| Algorand | 31566704 | Lute, Pera |
|
|
228
|
+
| Algorand Testnet | 10458941 | Lute, Pera |
|
|
346
229
|
|
|
347
|
-
|
|
348
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
230
|
+
### Other (2)
|
|
349
231
|
|
|
350
|
-
|
|
351
|
-
|
|
232
|
+
| Network | Wallet |
|
|
233
|
+
|---------|--------|
|
|
234
|
+
| Stellar | Freighter |
|
|
235
|
+
| NEAR | MyNearWallet |
|
|
352
236
|
|
|
353
|
-
|
|
354
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
355
|
-
amount: '10.00',
|
|
356
|
-
});
|
|
357
|
-
```
|
|
237
|
+
## Facilitator Addresses
|
|
358
238
|
|
|
359
|
-
|
|
239
|
+
The SDK includes built-in facilitator addresses. You don't need to configure them.
|
|
360
240
|
|
|
361
241
|
```typescript
|
|
362
|
-
import {
|
|
242
|
+
import { FACILITATOR_ADDRESSES, getFacilitatorAddress } from 'uvd-x402-sdk';
|
|
363
243
|
|
|
364
|
-
|
|
365
|
-
|
|
244
|
+
// Built-in addresses
|
|
245
|
+
FACILITATOR_ADDRESSES.evm; // 0x103040545AC5031A11E8C03dd11324C7333a13C7
|
|
246
|
+
FACILITATOR_ADDRESSES.solana; // F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq
|
|
247
|
+
FACILITATOR_ADDRESSES.algorand; // KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I
|
|
248
|
+
FACILITATOR_ADDRESSES.stellar; // GCHPGXJT2WFFRFCA5TV4G4E3PMMXLNIDUH27PKDYA4QJ2XGYZWGFZNHB
|
|
249
|
+
FACILITATOR_ADDRESSES.near; // uvd-facilitator.near
|
|
366
250
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
251
|
+
// Or get by chain name
|
|
252
|
+
getFacilitatorAddress('algorand'); // KIMS5H6...
|
|
253
|
+
getFacilitatorAddress('base', 'evm'); // 0x1030...
|
|
371
254
|
```
|
|
372
255
|
|
|
373
|
-
|
|
256
|
+
## Backend
|
|
374
257
|
|
|
375
258
|
```typescript
|
|
376
|
-
import {
|
|
259
|
+
import {
|
|
260
|
+
FacilitatorClient,
|
|
261
|
+
create402Response,
|
|
262
|
+
extractPaymentFromHeaders,
|
|
263
|
+
buildPaymentRequirements,
|
|
264
|
+
} from 'uvd-x402-sdk/backend';
|
|
377
265
|
|
|
378
|
-
|
|
379
|
-
|
|
266
|
+
// Return 402 if no payment
|
|
267
|
+
app.post('/api/premium', async (req, res) => {
|
|
268
|
+
const payment = extractPaymentFromHeaders(req.headers);
|
|
380
269
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
270
|
+
if (!payment) {
|
|
271
|
+
const { status, headers, body } = create402Response({
|
|
272
|
+
amount: '1.00',
|
|
273
|
+
recipient: process.env.RECIPIENT,
|
|
274
|
+
resource: 'https://api.example.com/premium',
|
|
275
|
+
chainName: 'base',
|
|
276
|
+
});
|
|
277
|
+
return res.status(status).set(headers).json(body);
|
|
278
|
+
}
|
|
386
279
|
|
|
387
|
-
|
|
280
|
+
// Verify and settle
|
|
281
|
+
const client = new FacilitatorClient();
|
|
282
|
+
const requirements = buildPaymentRequirements({
|
|
283
|
+
amount: '1.00',
|
|
284
|
+
recipient: process.env.RECIPIENT,
|
|
285
|
+
});
|
|
388
286
|
|
|
389
|
-
|
|
390
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
287
|
+
const result = await client.verifyAndSettle(payment, requirements);
|
|
391
288
|
|
|
392
|
-
|
|
393
|
-
|
|
289
|
+
if (!result.verified) {
|
|
290
|
+
return res.status(402).json({ error: result.error });
|
|
291
|
+
}
|
|
394
292
|
|
|
395
|
-
|
|
396
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
397
|
-
amount: '10.00',
|
|
293
|
+
res.json({ data: 'premium content', txHash: result.transactionHash });
|
|
398
294
|
});
|
|
399
295
|
```
|
|
400
296
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
### SVM Chains (Solana Virtual Machine)
|
|
404
|
-
|
|
405
|
-
SVM chains use partially-signed transactions where the facilitator is the fee payer.
|
|
406
|
-
|
|
407
|
-
#### Solana
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
411
|
-
import { getChainByName } from 'uvd-x402-sdk';
|
|
297
|
+
## React
|
|
412
298
|
|
|
413
|
-
|
|
299
|
+
```tsx
|
|
300
|
+
import { X402Provider, useX402, usePayment } from 'uvd-x402-sdk/react';
|
|
414
301
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
302
|
+
function App() {
|
|
303
|
+
return (
|
|
304
|
+
<X402Provider config={{ defaultChain: 'base' }}>
|
|
305
|
+
<PaymentPage />
|
|
306
|
+
</X402Provider>
|
|
307
|
+
);
|
|
418
308
|
}
|
|
419
309
|
|
|
420
|
-
|
|
421
|
-
const address =
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// Get chain config
|
|
425
|
-
const chainConfig = getChainByName('solana')!;
|
|
426
|
-
|
|
427
|
-
// Get balance
|
|
428
|
-
const balance = await svm.getBalance(chainConfig);
|
|
429
|
-
console.log('USDC Balance:', balance);
|
|
430
|
-
|
|
431
|
-
// Create payment
|
|
432
|
-
const paymentPayload = await svm.signPayment(
|
|
433
|
-
{
|
|
434
|
-
recipient: '5Y32Dk6weq1LrMRdujpJyDbTN3SjwXGoQS9QN39WQ9Cq',
|
|
435
|
-
amount: '10.00',
|
|
436
|
-
facilitator: 'F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq',
|
|
437
|
-
},
|
|
438
|
-
chainConfig
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
// Encode as X-PAYMENT header
|
|
442
|
-
const header = svm.encodePaymentHeader(paymentPayload, chainConfig);
|
|
443
|
-
console.log('Payment header:', header);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
#### Fogo
|
|
447
|
-
|
|
448
|
-
Fogo is an SVM chain with ultra-fast ~400ms finality.
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
452
|
-
import { getChainByName } from 'uvd-x402-sdk';
|
|
453
|
-
|
|
454
|
-
const svm = new SVMProvider();
|
|
310
|
+
function PaymentPage() {
|
|
311
|
+
const { connect, isConnected, address } = useX402();
|
|
312
|
+
const { pay, isPaying } = usePayment();
|
|
455
313
|
|
|
456
|
-
|
|
457
|
-
|
|
314
|
+
if (!isConnected) {
|
|
315
|
+
return <button onClick={() => connect('base')}>Connect</button>;
|
|
316
|
+
}
|
|
458
317
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
// Create Fogo payment
|
|
467
|
-
const paymentPayload = await svm.signPayment(
|
|
468
|
-
{
|
|
469
|
-
recipient: '5Y32Dk6weq1LrMRdujpJyDbTN3SjwXGoQS9QN39WQ9Cq',
|
|
470
|
-
amount: '10.00',
|
|
471
|
-
facilitator: 'F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq',
|
|
472
|
-
},
|
|
473
|
-
chainConfig
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
// Encode with correct network name ('fogo')
|
|
477
|
-
const header = svm.encodePaymentHeader(paymentPayload, chainConfig);
|
|
478
|
-
console.log('Fogo payment header:', header);
|
|
318
|
+
return (
|
|
319
|
+
<button onClick={() => pay({ recipient: '0x...', amount: '10.00' })} disabled={isPaying}>
|
|
320
|
+
{isPaying ? 'Processing...' : 'Pay $10'}
|
|
321
|
+
</button>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
479
324
|
```
|
|
480
325
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
### Stellar
|
|
484
|
-
|
|
485
|
-
Stellar uses Soroban authorization entries for gasless transfers.
|
|
326
|
+
## Error Handling
|
|
486
327
|
|
|
487
328
|
```typescript
|
|
488
|
-
import {
|
|
489
|
-
import { getChainByName } from 'uvd-x402-sdk';
|
|
490
|
-
|
|
491
|
-
const stellar = new StellarProvider();
|
|
329
|
+
import { X402Error } from 'uvd-x402-sdk';
|
|
492
330
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
331
|
+
try {
|
|
332
|
+
await client.createPayment(paymentInfo);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error instanceof X402Error) {
|
|
335
|
+
switch (error.code) {
|
|
336
|
+
case 'WALLET_NOT_FOUND': // Install wallet
|
|
337
|
+
case 'WALLET_CONNECTION_REJECTED': // User rejected
|
|
338
|
+
case 'INSUFFICIENT_BALANCE': // Not enough USDC
|
|
339
|
+
case 'SIGNATURE_REJECTED': // User cancelled
|
|
340
|
+
case 'CHAIN_NOT_SUPPORTED': // Unsupported network
|
|
341
|
+
}
|
|
342
|
+
}
|
|
496
343
|
}
|
|
497
|
-
|
|
498
|
-
// Connect
|
|
499
|
-
const address = await stellar.connect();
|
|
500
|
-
console.log('Connected Stellar wallet:', address);
|
|
501
|
-
|
|
502
|
-
// Get chain config
|
|
503
|
-
const chainConfig = getChainByName('stellar')!;
|
|
504
|
-
|
|
505
|
-
// Get balance
|
|
506
|
-
const balance = await stellar.getBalance(chainConfig);
|
|
507
|
-
console.log('USDC Balance:', balance);
|
|
508
|
-
|
|
509
|
-
// Create payment
|
|
510
|
-
const paymentPayload = await stellar.signPayment(
|
|
511
|
-
{
|
|
512
|
-
recipient: 'GD3FWQ4QFSCO2F2KVXZPQWOC27CQHXHYCRCRRZBMWU3DNOZW2IIGOU54',
|
|
513
|
-
amount: '10.00',
|
|
514
|
-
},
|
|
515
|
-
chainConfig
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
// Encode as X-PAYMENT header
|
|
519
|
-
const header = stellar.encodePaymentHeader(paymentPayload);
|
|
520
|
-
console.log('Payment header:', header);
|
|
521
344
|
```
|
|
522
345
|
|
|
523
|
-
|
|
346
|
+
## Links
|
|
524
347
|
|
|
525
|
-
|
|
348
|
+
- [x402 Protocol](https://x402.org)
|
|
349
|
+
- [Ultravioleta DAO](https://ultravioletadao.xyz)
|
|
350
|
+
- [npm](https://www.npmjs.com/package/uvd-x402-sdk)
|
|
351
|
+
- [GitHub](https://github.com/UltravioletaDAO/uvd-x402-sdk-typescript)
|
|
526
352
|
|
|
527
|
-
|
|
353
|
+
## License
|
|
528
354
|
|
|
529
|
-
|
|
530
|
-
import { NEARProvider } from 'uvd-x402-sdk/near';
|
|
531
|
-
import { getChainByName } from 'uvd-x402-sdk';
|
|
532
|
-
|
|
533
|
-
const near = new NEARProvider();
|
|
534
|
-
|
|
535
|
-
// Check if NEAR wallet is available
|
|
536
|
-
if (!near.isAvailable()) {
|
|
537
|
-
throw new Error('Please install MyNearWallet or Meteor wallet');
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Connect
|
|
541
|
-
const accountId = await near.connect();
|
|
542
|
-
console.log('Connected NEAR account:', accountId);
|
|
543
|
-
|
|
544
|
-
// Get chain config
|
|
545
|
-
const chainConfig = getChainByName('near')!;
|
|
546
|
-
|
|
547
|
-
// Get balance
|
|
548
|
-
const balance = await near.getBalance(chainConfig);
|
|
549
|
-
console.log('USDC Balance:', balance);
|
|
550
|
-
|
|
551
|
-
// Create payment
|
|
552
|
-
const paymentPayload = await near.signPayment(
|
|
553
|
-
{
|
|
554
|
-
recipient: '0xultravioleta.near',
|
|
555
|
-
amount: '10.00',
|
|
556
|
-
},
|
|
557
|
-
chainConfig
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
// Encode as X-PAYMENT header
|
|
561
|
-
const header = near.encodePaymentHeader(paymentPayload);
|
|
562
|
-
console.log('Payment header:', header);
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
---
|
|
566
|
-
|
|
567
|
-
## x402 Protocol Versions
|
|
568
|
-
|
|
569
|
-
The SDK supports both x402 v1 and v2 protocols.
|
|
570
|
-
|
|
571
|
-
### v1 (Default)
|
|
572
|
-
|
|
573
|
-
```typescript
|
|
574
|
-
// v1 uses simple network names
|
|
575
|
-
{
|
|
576
|
-
"x402Version": 1,
|
|
577
|
-
"scheme": "exact",
|
|
578
|
-
"network": "base",
|
|
579
|
-
"payload": { ... }
|
|
580
|
-
}
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
### v2 (CAIP-2)
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
// v2 uses CAIP-2 chain identifiers
|
|
587
|
-
{
|
|
588
|
-
"x402Version": 2,
|
|
589
|
-
"scheme": "exact",
|
|
590
|
-
"network": "eip155:8453",
|
|
591
|
-
"payload": { ... },
|
|
592
|
-
"accepts": [
|
|
593
|
-
{ "network": "eip155:8453", "asset": "0x833...", "amount": "10000000" },
|
|
594
|
-
{ "network": "solana:5eykt...", "asset": "EPjF...", "amount": "10000000" }
|
|
595
|
-
]
|
|
596
|
-
}
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Version Detection and Conversion
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
import {
|
|
603
|
-
detectX402Version,
|
|
604
|
-
chainToCAIP2,
|
|
605
|
-
caip2ToChain,
|
|
606
|
-
convertX402Header,
|
|
607
|
-
} from 'uvd-x402-sdk';
|
|
608
|
-
|
|
609
|
-
// Detect version from response
|
|
610
|
-
const version = detectX402Version(response402);
|
|
611
|
-
// Returns: 1 or 2
|
|
612
|
-
|
|
613
|
-
// Convert between formats
|
|
614
|
-
const caip2 = chainToCAIP2('base');
|
|
615
|
-
// Returns: 'eip155:8453'
|
|
616
|
-
|
|
617
|
-
const chainName = caip2ToChain('eip155:8453');
|
|
618
|
-
// Returns: 'base'
|
|
619
|
-
|
|
620
|
-
// Convert headers between versions
|
|
621
|
-
const v2Header = convertX402Header(v1Header, 2);
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### Configure Version
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
const client = new X402Client({
|
|
628
|
-
defaultChain: 'base',
|
|
629
|
-
x402Version: 2, // Force v2 format
|
|
630
|
-
// or
|
|
631
|
-
x402Version: 'auto', // Auto-detect from 402 response (default)
|
|
632
|
-
});
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
---
|
|
636
|
-
|
|
637
|
-
## Multi-Payment Support
|
|
638
|
-
|
|
639
|
-
Accept payments on multiple networks simultaneously.
|
|
640
|
-
|
|
641
|
-
### Configuration
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
const client = new X402Client({
|
|
645
|
-
defaultChain: 'base',
|
|
646
|
-
multiPayment: {
|
|
647
|
-
networks: ['base', 'solana', 'stellar', 'near'],
|
|
648
|
-
defaultNetwork: 'base',
|
|
649
|
-
autoDetect: true, // Auto-select based on user's wallet
|
|
650
|
-
},
|
|
651
|
-
});
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
### Generate Payment Options
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
import { generatePaymentOptions, getEnabledChains } from 'uvd-x402-sdk';
|
|
658
|
-
|
|
659
|
-
// Get all enabled chain configs
|
|
660
|
-
const chains = getEnabledChains();
|
|
661
|
-
|
|
662
|
-
// Generate v2 payment options
|
|
663
|
-
const options = generatePaymentOptions(chains, '10.00');
|
|
664
|
-
|
|
665
|
-
// Result:
|
|
666
|
-
// [
|
|
667
|
-
// { network: 'eip155:8453', asset: '0x833...', amount: '10000000' },
|
|
668
|
-
// { network: 'solana:5eykt...', asset: 'EPjF...', amount: '10000000' },
|
|
669
|
-
// { network: 'stellar:pubnet', asset: 'CCW67...', amount: '100000000' },
|
|
670
|
-
// { network: 'near:mainnet', asset: '17208...', amount: '10000000' },
|
|
671
|
-
// ]
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
---
|
|
675
|
-
|
|
676
|
-
## Multi-Stablecoin Support (EVM)
|
|
677
|
-
|
|
678
|
-
EVM chains support multiple stablecoins beyond USDC. Token availability varies by chain.
|
|
679
|
-
|
|
680
|
-
### Supported Tokens
|
|
681
|
-
|
|
682
|
-
| Token | Description | Decimals | Chains |
|
|
683
|
-
|-------|-------------|----------|--------|
|
|
684
|
-
| USDC | USD Coin (Circle) | 6 | All EVM chains |
|
|
685
|
-
| EURC | Euro Coin (Circle) | 6 | Ethereum, Base, Avalanche |
|
|
686
|
-
| AUSD | Agora Dollar | 6 | Ethereum, Avalanche, Polygon, Arbitrum, Monad |
|
|
687
|
-
| PYUSD | PayPal USD | 6 | Ethereum |
|
|
688
|
-
| USDT | Tether USD (USDT0 Omnichain via LayerZero) | 6 | Ethereum, Arbitrum |
|
|
689
|
-
|
|
690
|
-
### Basic Usage
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
import { X402Client } from 'uvd-x402-sdk';
|
|
694
|
-
|
|
695
|
-
const client = new X402Client({ defaultChain: 'base' });
|
|
696
|
-
await client.connect('base');
|
|
697
|
-
|
|
698
|
-
// Pay with EURC instead of USDC
|
|
699
|
-
const result = await client.createPayment({
|
|
700
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
701
|
-
amount: '10.00',
|
|
702
|
-
tokenType: 'eurc', // 'usdc' | 'eurc' | 'ausd' | 'pyusd'
|
|
703
|
-
});
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
### Check Token Availability
|
|
707
|
-
|
|
708
|
-
```typescript
|
|
709
|
-
import {
|
|
710
|
-
getSupportedTokens,
|
|
711
|
-
isTokenSupported,
|
|
712
|
-
getTokenConfig,
|
|
713
|
-
getChainsByToken,
|
|
714
|
-
} from 'uvd-x402-sdk';
|
|
715
|
-
|
|
716
|
-
// Get all tokens supported on a chain
|
|
717
|
-
const tokens = getSupportedTokens('ethereum');
|
|
718
|
-
// Returns: ['usdc', 'eurc', 'ausd', 'pyusd']
|
|
719
|
-
|
|
720
|
-
const baseTokens = getSupportedTokens('base');
|
|
721
|
-
// Returns: ['usdc', 'eurc']
|
|
722
|
-
|
|
723
|
-
// Check if a specific token is supported
|
|
724
|
-
if (isTokenSupported('base', 'eurc')) {
|
|
725
|
-
console.log('EURC available on Base');
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Get token configuration (address, decimals, name)
|
|
729
|
-
const eurcConfig = getTokenConfig('ethereum', 'eurc');
|
|
730
|
-
// Returns: { address: '0x1aBa...', decimals: 6, name: 'EURC', version: '2' }
|
|
731
|
-
|
|
732
|
-
// Find all chains that support a token
|
|
733
|
-
const eurcChains = getChainsByToken('eurc');
|
|
734
|
-
// Returns: [baseConfig, ethereumConfig, avalancheConfig]
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Check Token Balance
|
|
738
|
-
|
|
739
|
-
```typescript
|
|
740
|
-
import { EVMProvider } from 'uvd-x402-sdk';
|
|
741
|
-
import { getChainByName } from 'uvd-x402-sdk';
|
|
742
|
-
|
|
743
|
-
const evm = new EVMProvider();
|
|
744
|
-
await evm.connect();
|
|
745
|
-
|
|
746
|
-
const chainConfig = getChainByName('ethereum')!;
|
|
747
|
-
|
|
748
|
-
// Check USDC balance (default)
|
|
749
|
-
const usdcBalance = await evm.getBalance(chainConfig);
|
|
750
|
-
|
|
751
|
-
// Check EURC balance
|
|
752
|
-
const eurcBalance = await evm.getBalance(chainConfig, 'eurc');
|
|
753
|
-
|
|
754
|
-
// Check PYUSD balance
|
|
755
|
-
const pyusdBalance = await evm.getBalance(chainConfig, 'pyusd');
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
### Wagmi/RainbowKit with Multi-Token
|
|
759
|
-
|
|
760
|
-
```typescript
|
|
761
|
-
import { useWalletClient } from 'wagmi';
|
|
762
|
-
import { createPaymentFromWalletClient } from 'uvd-x402-sdk/wagmi';
|
|
763
|
-
|
|
764
|
-
function PayWithEURC() {
|
|
765
|
-
const { data: walletClient } = useWalletClient();
|
|
766
|
-
|
|
767
|
-
const handlePay = async () => {
|
|
768
|
-
const paymentHeader = await createPaymentFromWalletClient(walletClient, {
|
|
769
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
770
|
-
amount: '10.00',
|
|
771
|
-
chainName: 'base',
|
|
772
|
-
tokenType: 'eurc', // Pay with EURC
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
await fetch('/api/purchase', {
|
|
776
|
-
headers: { 'X-PAYMENT': paymentHeader },
|
|
777
|
-
method: 'POST',
|
|
778
|
-
});
|
|
779
|
-
};
|
|
780
|
-
|
|
781
|
-
return <button onClick={handlePay}>Pay 10 EURC</button>;
|
|
782
|
-
}
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Critical Implementation Notes
|
|
786
|
-
|
|
787
|
-
#### EIP-712 Domain Names Vary by Chain
|
|
788
|
-
|
|
789
|
-
The same token may use **different EIP-712 domain names on different chains**. This affects signature verification.
|
|
790
|
-
|
|
791
|
-
| Token | Ethereum | Base | Avalanche |
|
|
792
|
-
|-------|----------|------|-----------|
|
|
793
|
-
| EURC | `"Euro Coin"` | `"EURC"` | `"Euro Coin"` |
|
|
794
|
-
| USDC | `"USD Coin"` | `"USD Coin"` | `"USD Coin"` |
|
|
795
|
-
| AUSD | `"AUSD"` | N/A | `"AUSD"` |
|
|
796
|
-
| PYUSD | `"PayPal USD"` | N/A | N/A |
|
|
797
|
-
|
|
798
|
-
**Important:** Always use `getTokenConfig()` to get the correct domain name for each chain. Never hardcode domain names.
|
|
799
|
-
|
|
800
|
-
```typescript
|
|
801
|
-
// CORRECT: Use getTokenConfig for each chain
|
|
802
|
-
const eurcBase = getTokenConfig('base', 'eurc');
|
|
803
|
-
// Returns: { name: 'EURC', version: '2', ... }
|
|
804
|
-
|
|
805
|
-
const eurcEthereum = getTokenConfig('ethereum', 'eurc');
|
|
806
|
-
// Returns: { name: 'Euro Coin', version: '2', ... }
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
#### PYUSD Signature Format (PayPal USD)
|
|
810
|
-
|
|
811
|
-
PYUSD uses the Paxos implementation which only supports the **v,r,s signature variant** of `transferWithAuthorization`. This is different from Circle's USDC/EURC which support both compact bytes and v,r,s variants.
|
|
812
|
-
|
|
813
|
-
**Backend implications:**
|
|
814
|
-
- The x402 facilitator (v1.9.0+) automatically handles this by detecting PYUSD and using `transferWithAuthorization_1(v,r,s)` instead of `transferWithAuthorization_0(bytes signature)`
|
|
815
|
-
- If using a custom facilitator, ensure it supports the v,r,s variant for PYUSD
|
|
816
|
-
|
|
817
|
-
#### Token Info Must Be Passed to Backend
|
|
818
|
-
|
|
819
|
-
When using non-USDC tokens, you **must** pass the token info in your payment payload so the backend can validate the correct EIP-712 domain. The SDK handles this automatically when you specify `tokenType`.
|
|
820
|
-
|
|
821
|
-
```typescript
|
|
822
|
-
// The SDK automatically includes token info when you specify tokenType
|
|
823
|
-
const paymentHeader = await client.createPayment({
|
|
824
|
-
recipient: '0x...',
|
|
825
|
-
amount: '10.00',
|
|
826
|
-
tokenType: 'eurc', // This ensures token info is included
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
// Backend receives token info in the payload:
|
|
830
|
-
// {
|
|
831
|
-
// "token": {
|
|
832
|
-
// "address": "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
|
|
833
|
-
// "symbol": "EURC",
|
|
834
|
-
// "decimals": 6,
|
|
835
|
-
// "eip712": { "name": "EURC", "version": "2" }
|
|
836
|
-
// }
|
|
837
|
-
// }
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
---
|
|
841
|
-
|
|
842
|
-
## React Integration
|
|
843
|
-
|
|
844
|
-
```tsx
|
|
845
|
-
import { X402Provider, useX402, usePayment, useBalance } from 'uvd-x402-sdk/react';
|
|
846
|
-
|
|
847
|
-
function App() {
|
|
848
|
-
return (
|
|
849
|
-
<X402Provider config={{ defaultChain: 'base' }}>
|
|
850
|
-
<PaymentPage />
|
|
851
|
-
</X402Provider>
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
function PaymentPage() {
|
|
856
|
-
const { connect, disconnect, isConnected, address, network } = useX402();
|
|
857
|
-
const { balance, isLoading: balanceLoading } = useBalance();
|
|
858
|
-
const { pay, isPaying, error } = usePayment();
|
|
859
|
-
|
|
860
|
-
const handlePurchase = async () => {
|
|
861
|
-
const result = await pay({
|
|
862
|
-
recipient: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
863
|
-
amount: '10.00',
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
await fetch('/api/purchase', {
|
|
867
|
-
headers: { 'X-PAYMENT': result.paymentHeader },
|
|
868
|
-
method: 'POST',
|
|
869
|
-
body: JSON.stringify({ item: 'premium' }),
|
|
870
|
-
});
|
|
871
|
-
};
|
|
872
|
-
|
|
873
|
-
if (!isConnected) {
|
|
874
|
-
return <button onClick={() => connect('base')}>Connect Wallet</button>;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
return (
|
|
878
|
-
<div>
|
|
879
|
-
<p>Connected: {address}</p>
|
|
880
|
-
<p>Network: {network}</p>
|
|
881
|
-
<p>Balance: {balanceLoading ? 'Loading...' : `${balance} USDC`}</p>
|
|
882
|
-
<button onClick={handlePurchase} disabled={isPaying}>
|
|
883
|
-
{isPaying ? 'Processing...' : 'Pay $10 USDC'}
|
|
884
|
-
</button>
|
|
885
|
-
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
886
|
-
</div>
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
---
|
|
892
|
-
|
|
893
|
-
## Supported Networks
|
|
894
|
-
|
|
895
|
-
### EVM Networks (10)
|
|
896
|
-
|
|
897
|
-
| Network | Chain ID | Tokens | Status |
|
|
898
|
-
|---------|----------|--------|--------|
|
|
899
|
-
| Ethereum | 1 | USDC, EURC, AUSD, PYUSD | Enabled |
|
|
900
|
-
| Base | 8453 | USDC, EURC | Enabled |
|
|
901
|
-
| Avalanche | 43114 | USDC, EURC, AUSD | Enabled |
|
|
902
|
-
| Arbitrum | 42161 | USDC, AUSD | Enabled |
|
|
903
|
-
| Polygon | 137 | USDC, AUSD | Enabled |
|
|
904
|
-
| Monad | 143 | USDC, AUSD | Enabled |
|
|
905
|
-
| Optimism | 10 | USDC | Enabled |
|
|
906
|
-
| Celo | 42220 | USDC | Enabled |
|
|
907
|
-
| HyperEVM | 999 | USDC | Enabled |
|
|
908
|
-
| Unichain | 130 | USDC | Enabled |
|
|
909
|
-
|
|
910
|
-
### SVM Networks (2)
|
|
911
|
-
|
|
912
|
-
| Network | USDC Mint | Decimals | Wallet | Status |
|
|
913
|
-
|---------|-----------|----------|--------|--------|
|
|
914
|
-
| Solana | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | 6 | Phantom | Enabled |
|
|
915
|
-
| Fogo | uSd2czE61Evaf76RNbq4KPpXnkiL3irdzgLFUMe3NoG | 6 | Phantom | Enabled |
|
|
916
|
-
|
|
917
|
-
### Other Networks (2)
|
|
918
|
-
|
|
919
|
-
| Network | USDC Address | Decimals | Wallet | Status |
|
|
920
|
-
|---------|--------------|----------|--------|--------|
|
|
921
|
-
| Stellar | CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75 | 7 | Freighter | Enabled |
|
|
922
|
-
| NEAR | 17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1 | 6 | MyNearWallet | Enabled |
|
|
923
|
-
|
|
924
|
-
---
|
|
925
|
-
|
|
926
|
-
## API Reference
|
|
927
|
-
|
|
928
|
-
### X402Client
|
|
929
|
-
|
|
930
|
-
```typescript
|
|
931
|
-
const client = new X402Client(config?: X402ClientConfig);
|
|
932
|
-
|
|
933
|
-
interface X402ClientConfig {
|
|
934
|
-
facilitatorUrl?: string; // Default: 'https://facilitator.ultravioletadao.xyz'
|
|
935
|
-
defaultChain?: string; // Default: 'base'
|
|
936
|
-
autoConnect?: boolean; // Default: false
|
|
937
|
-
debug?: boolean; // Default: false
|
|
938
|
-
x402Version?: 1 | 2 | 'auto'; // Default: 'auto'
|
|
939
|
-
customChains?: Record<string, Partial<ChainConfig>>;
|
|
940
|
-
rpcOverrides?: Record<string, string>;
|
|
941
|
-
multiPayment?: MultiPaymentConfig;
|
|
942
|
-
}
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
#### Methods
|
|
946
|
-
|
|
947
|
-
| Method | Description |
|
|
948
|
-
|--------|-------------|
|
|
949
|
-
| `connect(chainName?)` | Connect wallet to specified chain |
|
|
950
|
-
| `disconnect()` | Disconnect current wallet |
|
|
951
|
-
| `switchChain(chainName)` | Switch to different EVM chain |
|
|
952
|
-
| `createPayment(paymentInfo)` | Create payment authorization (supports `tokenType` in paymentInfo) |
|
|
953
|
-
| `getBalance(tokenType?)` | Get token balance on current chain (defaults to USDC) |
|
|
954
|
-
| `getState()` | Get current wallet state |
|
|
955
|
-
| `isConnected()` | Check if wallet is connected |
|
|
956
|
-
| `on(event, handler)` | Subscribe to events |
|
|
957
|
-
|
|
958
|
-
### Chain Utilities
|
|
959
|
-
|
|
960
|
-
```typescript
|
|
961
|
-
import {
|
|
962
|
-
SUPPORTED_CHAINS,
|
|
963
|
-
getChainByName,
|
|
964
|
-
getChainById,
|
|
965
|
-
getEnabledChains,
|
|
966
|
-
getChainsByNetworkType,
|
|
967
|
-
getSVMChains,
|
|
968
|
-
isSVMChain,
|
|
969
|
-
getExplorerTxUrl,
|
|
970
|
-
getExplorerAddressUrl,
|
|
971
|
-
// Multi-token utilities
|
|
972
|
-
getTokenConfig,
|
|
973
|
-
getSupportedTokens,
|
|
974
|
-
isTokenSupported,
|
|
975
|
-
getChainsByToken,
|
|
976
|
-
} from 'uvd-x402-sdk';
|
|
977
|
-
```
|
|
978
|
-
|
|
979
|
-
### Token Utilities
|
|
980
|
-
|
|
981
|
-
| Function | Description |
|
|
982
|
-
|----------|-------------|
|
|
983
|
-
| `getTokenConfig(chain, tokenType)` | Get token config (address, decimals, name, version) |
|
|
984
|
-
| `getSupportedTokens(chain)` | Get array of supported token types for a chain |
|
|
985
|
-
| `isTokenSupported(chain, tokenType)` | Check if token is available on chain |
|
|
986
|
-
| `getChainsByToken(tokenType)` | Get all chains that support a specific token |
|
|
987
|
-
|
|
988
|
-
### x402 Utilities
|
|
989
|
-
|
|
990
|
-
```typescript
|
|
991
|
-
import {
|
|
992
|
-
detectX402Version,
|
|
993
|
-
chainToCAIP2,
|
|
994
|
-
caip2ToChain,
|
|
995
|
-
createX402V1Header,
|
|
996
|
-
createX402V2Header,
|
|
997
|
-
encodeX402Header,
|
|
998
|
-
decodeX402Header,
|
|
999
|
-
convertX402Header,
|
|
1000
|
-
generatePaymentOptions,
|
|
1001
|
-
} from 'uvd-x402-sdk';
|
|
1002
|
-
```
|
|
1003
|
-
|
|
1004
|
-
### Wagmi Adapter
|
|
1005
|
-
|
|
1006
|
-
```typescript
|
|
1007
|
-
import {
|
|
1008
|
-
createPaymentFromWalletClient,
|
|
1009
|
-
createPaymentWithResult,
|
|
1010
|
-
useX402Wagmi,
|
|
1011
|
-
} from 'uvd-x402-sdk/wagmi';
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
| Function | Description |
|
|
1015
|
-
|----------|-------------|
|
|
1016
|
-
| `createPaymentFromWalletClient(walletClient, options)` | Create payment header using wagmi's WalletClient |
|
|
1017
|
-
| `createPaymentWithResult(walletClient, options)` | Same as above but returns full PaymentResult |
|
|
1018
|
-
| `useX402Wagmi(walletClient)` | Helper hook returning `{ createPayment, isReady }` |
|
|
1019
|
-
|
|
1020
|
-
#### Options
|
|
1021
|
-
|
|
1022
|
-
```typescript
|
|
1023
|
-
interface WagmiPaymentOptions {
|
|
1024
|
-
recipient: string; // Recipient address
|
|
1025
|
-
amount: string; // Amount in token (e.g., "10.00")
|
|
1026
|
-
chainName?: string; // Chain name (default: 'base')
|
|
1027
|
-
tokenType?: TokenType; // Token type (default: 'usdc')
|
|
1028
|
-
validitySeconds?: number; // Signature validity window (default: 300)
|
|
1029
|
-
}
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
---
|
|
1033
|
-
|
|
1034
|
-
## Error Handling
|
|
1035
|
-
|
|
1036
|
-
```typescript
|
|
1037
|
-
import { X402Error } from 'uvd-x402-sdk';
|
|
1038
|
-
|
|
1039
|
-
try {
|
|
1040
|
-
await client.createPayment(paymentInfo);
|
|
1041
|
-
} catch (error) {
|
|
1042
|
-
if (error instanceof X402Error) {
|
|
1043
|
-
switch (error.code) {
|
|
1044
|
-
case 'WALLET_NOT_FOUND':
|
|
1045
|
-
alert('Please install a wallet');
|
|
1046
|
-
break;
|
|
1047
|
-
case 'WALLET_CONNECTION_REJECTED':
|
|
1048
|
-
alert('Connection cancelled');
|
|
1049
|
-
break;
|
|
1050
|
-
case 'INSUFFICIENT_BALANCE':
|
|
1051
|
-
alert('Not enough USDC');
|
|
1052
|
-
break;
|
|
1053
|
-
case 'SIGNATURE_REJECTED':
|
|
1054
|
-
alert('Payment cancelled');
|
|
1055
|
-
break;
|
|
1056
|
-
case 'CHAIN_NOT_SUPPORTED':
|
|
1057
|
-
alert('Network not supported');
|
|
1058
|
-
break;
|
|
1059
|
-
default:
|
|
1060
|
-
alert(error.message);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
```
|
|
1065
|
-
|
|
1066
|
-
### Error Codes
|
|
1067
|
-
|
|
1068
|
-
| Code | Description |
|
|
1069
|
-
|------|-------------|
|
|
1070
|
-
| `WALLET_NOT_FOUND` | No compatible wallet detected |
|
|
1071
|
-
| `WALLET_NOT_CONNECTED` | Wallet not connected |
|
|
1072
|
-
| `WALLET_CONNECTION_REJECTED` | User rejected connection |
|
|
1073
|
-
| `CHAIN_NOT_SUPPORTED` | Chain not supported |
|
|
1074
|
-
| `CHAIN_SWITCH_REJECTED` | User rejected chain switch |
|
|
1075
|
-
| `INSUFFICIENT_BALANCE` | Not enough USDC |
|
|
1076
|
-
| `SIGNATURE_REJECTED` | User rejected signature |
|
|
1077
|
-
| `PAYMENT_FAILED` | Payment processing failed |
|
|
1078
|
-
| `NETWORK_ERROR` | Network request failed |
|
|
1079
|
-
| `INVALID_CONFIG` | Invalid configuration |
|
|
1080
|
-
|
|
1081
|
-
---
|
|
1082
|
-
|
|
1083
|
-
## Backend Utilities
|
|
1084
|
-
|
|
1085
|
-
The SDK includes comprehensive server-side utilities for handling x402 payments.
|
|
1086
|
-
|
|
1087
|
-
### Payment Flow Diagram
|
|
1088
|
-
|
|
1089
|
-
```
|
|
1090
|
-
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ ┌──────────────┐
|
|
1091
|
-
│ Client │ │ Your API │ │ Facilitator │ │ Blockchain │
|
|
1092
|
-
└──────┬──────┘ └──────┬──────┘ └────────┬────────┘ └──────┬───────┘
|
|
1093
|
-
│ │ │ │
|
|
1094
|
-
│ 1. Request resource (no payment) │ │
|
|
1095
|
-
│ ────────────────> │ │ │
|
|
1096
|
-
│ │ │ │
|
|
1097
|
-
│ 2. 402 Payment Required │ │
|
|
1098
|
-
│ <──────────────── │ │ │
|
|
1099
|
-
│ │ │ │
|
|
1100
|
-
│ 3. User signs payment │ │
|
|
1101
|
-
│ (wallet popup) │ │ │
|
|
1102
|
-
│ │ │ │
|
|
1103
|
-
│ 4. Request with X-PAYMENT header │ │
|
|
1104
|
-
│ ────────────────> │ │ │
|
|
1105
|
-
│ │ │ │
|
|
1106
|
-
│ │ 5. Verify payment │ │
|
|
1107
|
-
│ │ ────────────────────> │
|
|
1108
|
-
│ │ │ │
|
|
1109
|
-
│ │ 6. Payment valid │ │
|
|
1110
|
-
│ │ <────────────────── │ │
|
|
1111
|
-
│ │ │ │
|
|
1112
|
-
│ 7. Provide resource │ │
|
|
1113
|
-
│ <──────────────── │ │ │
|
|
1114
|
-
│ │ │ │
|
|
1115
|
-
│ │ 8. Settle payment │ │
|
|
1116
|
-
│ │ ────────────────────> │
|
|
1117
|
-
│ │ │ │
|
|
1118
|
-
│ │ │ 9. Submit tx │
|
|
1119
|
-
│ │ │ ───────────────────>│
|
|
1120
|
-
│ │ │ │
|
|
1121
|
-
│ │ │ 10. Confirmed │
|
|
1122
|
-
│ │ │ <───────────────────│
|
|
1123
|
-
│ │ │ │
|
|
1124
|
-
│ │ 11. Settlement done │ │
|
|
1125
|
-
│ │ <────────────────── │ │
|
|
1126
|
-
└ └ └ └
|
|
1127
|
-
```
|
|
1128
|
-
|
|
1129
|
-
### Basic Backend Setup
|
|
1130
|
-
|
|
1131
|
-
```typescript
|
|
1132
|
-
import {
|
|
1133
|
-
parsePaymentHeader,
|
|
1134
|
-
extractPaymentFromHeaders,
|
|
1135
|
-
buildPaymentRequirements,
|
|
1136
|
-
FacilitatorClient,
|
|
1137
|
-
create402Response,
|
|
1138
|
-
X402_CORS_HEADERS,
|
|
1139
|
-
} from 'uvd-x402-sdk/backend';
|
|
1140
|
-
|
|
1141
|
-
// Configure CORS
|
|
1142
|
-
app.use((req, res, next) => {
|
|
1143
|
-
Object.entries(X402_CORS_HEADERS).forEach(([key, value]) => {
|
|
1144
|
-
res.setHeader(key, value);
|
|
1145
|
-
});
|
|
1146
|
-
next();
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
// Protected endpoint
|
|
1150
|
-
app.post('/api/premium', async (req, res) => {
|
|
1151
|
-
// Extract payment from headers
|
|
1152
|
-
const payment = extractPaymentFromHeaders(req.headers);
|
|
1153
|
-
|
|
1154
|
-
if (!payment) {
|
|
1155
|
-
// Return 402 Payment Required
|
|
1156
|
-
const { status, headers, body } = create402Response({
|
|
1157
|
-
amount: '1.00',
|
|
1158
|
-
recipient: process.env.PAYMENT_RECIPIENT,
|
|
1159
|
-
resource: 'https://api.example.com/premium',
|
|
1160
|
-
chainName: 'base',
|
|
1161
|
-
});
|
|
1162
|
-
return res.status(status).set(headers).json(body);
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// Verify payment with facilitator
|
|
1166
|
-
const client = new FacilitatorClient();
|
|
1167
|
-
const requirements = buildPaymentRequirements({
|
|
1168
|
-
amount: '1.00',
|
|
1169
|
-
recipient: process.env.PAYMENT_RECIPIENT,
|
|
1170
|
-
resource: 'https://api.example.com/premium',
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
const verifyResult = await client.verify(payment, requirements);
|
|
1174
|
-
|
|
1175
|
-
if (!verifyResult.isValid) {
|
|
1176
|
-
return res.status(402).json({
|
|
1177
|
-
error: 'Payment verification failed',
|
|
1178
|
-
reason: verifyResult.invalidReason,
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Provide the resource
|
|
1183
|
-
res.json({ data: 'premium content' });
|
|
1184
|
-
|
|
1185
|
-
// Settle payment after response
|
|
1186
|
-
await client.settle(payment, requirements);
|
|
1187
|
-
});
|
|
1188
|
-
```
|
|
1189
|
-
|
|
1190
|
-
### Using Payment Middleware
|
|
1191
|
-
|
|
1192
|
-
```typescript
|
|
1193
|
-
import { createPaymentMiddleware } from 'uvd-x402-sdk/backend';
|
|
1194
|
-
|
|
1195
|
-
const paymentMiddleware = createPaymentMiddleware(
|
|
1196
|
-
(req) => ({
|
|
1197
|
-
amount: '1.00',
|
|
1198
|
-
recipient: process.env.PAYMENT_RECIPIENT,
|
|
1199
|
-
resource: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
|
|
1200
|
-
})
|
|
1201
|
-
);
|
|
1202
|
-
|
|
1203
|
-
// Apply to specific routes
|
|
1204
|
-
app.get('/premium/*', paymentMiddleware, (req, res) => {
|
|
1205
|
-
res.json({ premium: 'data' });
|
|
1206
|
-
});
|
|
1207
|
-
```
|
|
1208
|
-
|
|
1209
|
-
### FacilitatorClient API
|
|
1210
|
-
|
|
1211
|
-
```typescript
|
|
1212
|
-
const client = new FacilitatorClient({
|
|
1213
|
-
baseUrl: 'https://facilitator.ultravioletadao.xyz', // default
|
|
1214
|
-
timeout: 30000, // default: 30 seconds
|
|
1215
|
-
});
|
|
1216
|
-
|
|
1217
|
-
// Verify a payment
|
|
1218
|
-
const verifyResult = await client.verify(payment, requirements);
|
|
1219
|
-
// Returns: { isValid: boolean, invalidReason?: string, payer?: string }
|
|
1220
|
-
|
|
1221
|
-
// Settle a payment (execute on-chain)
|
|
1222
|
-
const settleResult = await client.settle(payment, requirements);
|
|
1223
|
-
// Returns: { success: boolean, transactionHash?: string, error?: string }
|
|
1224
|
-
|
|
1225
|
-
// Combined verify + settle
|
|
1226
|
-
const result = await client.verifyAndSettle(payment, requirements);
|
|
1227
|
-
// Returns: { verified: boolean, settled: boolean, transactionHash?: string, error?: string }
|
|
1228
|
-
|
|
1229
|
-
// Health check
|
|
1230
|
-
const isHealthy = await client.healthCheck();
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
---
|
|
1234
|
-
|
|
1235
|
-
## Bazaar Discovery API
|
|
1236
|
-
|
|
1237
|
-
Discover and register x402-enabled resources across the ecosystem.
|
|
1238
|
-
|
|
1239
|
-
```typescript
|
|
1240
|
-
import { BazaarClient } from 'uvd-x402-sdk/backend';
|
|
1241
|
-
|
|
1242
|
-
// Discover resources (no auth required)
|
|
1243
|
-
const bazaar = new BazaarClient();
|
|
1244
|
-
|
|
1245
|
-
const results = await bazaar.discover({
|
|
1246
|
-
category: 'ai', // 'api' | 'data' | 'ai' | 'media' | 'compute' | 'storage'
|
|
1247
|
-
network: 'base', // Filter by network
|
|
1248
|
-
token: 'USDC', // Filter by token
|
|
1249
|
-
maxPrice: '0.10', // Max price in dollars
|
|
1250
|
-
query: 'image', // Search query
|
|
1251
|
-
sortBy: 'price', // 'price' | 'createdAt' | 'name'
|
|
1252
|
-
sortOrder: 'asc',
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
for (const resource of results.resources) {
|
|
1256
|
-
console.log(`${resource.name}: ${resource.url} - $${resource.pricePerRequest}`);
|
|
1257
|
-
}
|
|
1258
|
-
```
|
|
1259
|
-
|
|
1260
|
-
### Register a Resource
|
|
1261
|
-
|
|
1262
|
-
```typescript
|
|
1263
|
-
// Registration requires API key
|
|
1264
|
-
const bazaar = new BazaarClient({
|
|
1265
|
-
apiKey: process.env.BAZAAR_API_KEY,
|
|
1266
|
-
});
|
|
1267
|
-
|
|
1268
|
-
const resource = await bazaar.register({
|
|
1269
|
-
url: 'https://api.example.com/v1/generate',
|
|
1270
|
-
name: 'AI Image Generator',
|
|
1271
|
-
description: 'Generate high-quality images with AI',
|
|
1272
|
-
category: 'ai',
|
|
1273
|
-
networks: ['base', 'ethereum', 'polygon'],
|
|
1274
|
-
tokens: ['USDC', 'EURC'],
|
|
1275
|
-
price: '0.05',
|
|
1276
|
-
payTo: '0xD3868E1eD738CED6945A574a7c769433BeD5d474',
|
|
1277
|
-
tags: ['ai', 'image', 'generator'],
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
console.log('Registered:', resource.id);
|
|
1281
|
-
|
|
1282
|
-
// Update resource
|
|
1283
|
-
await bazaar.update(resource.id, { price: '0.03' });
|
|
1284
|
-
|
|
1285
|
-
// Deactivate (soft delete)
|
|
1286
|
-
await bazaar.deactivate(resource.id);
|
|
1287
|
-
|
|
1288
|
-
// Reactivate
|
|
1289
|
-
await bazaar.reactivate(resource.id);
|
|
1290
|
-
|
|
1291
|
-
// Delete permanently
|
|
1292
|
-
await bazaar.delete(resource.id);
|
|
1293
|
-
```
|
|
1294
|
-
|
|
1295
|
-
### List Your Resources
|
|
1296
|
-
|
|
1297
|
-
```typescript
|
|
1298
|
-
const myResources = await bazaar.listMyResources({
|
|
1299
|
-
includeInactive: true,
|
|
1300
|
-
});
|
|
1301
|
-
```
|
|
1302
|
-
|
|
1303
|
-
---
|
|
1304
|
-
|
|
1305
|
-
## Escrow & Refunds
|
|
1306
|
-
|
|
1307
|
-
For services that may require refunds, use the escrow system.
|
|
1308
|
-
|
|
1309
|
-
### Escrow Flow Diagram
|
|
1310
|
-
|
|
1311
|
-
```
|
|
1312
|
-
┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
1313
|
-
│ Payer │ │ Your API │ │ Escrow Service │ │ Blockchain │
|
|
1314
|
-
└──────┬──────┘ └──────┬──────┘ └────────┬─────────┘ └──────┬───────┘
|
|
1315
|
-
│ │ │ │
|
|
1316
|
-
│ 1. Pay with X-PAYMENT │ │
|
|
1317
|
-
│ ────────────────> │ │ │
|
|
1318
|
-
│ │ │ │
|
|
1319
|
-
│ │ 2. Create escrow │ │
|
|
1320
|
-
│ │ ────────────────────> │
|
|
1321
|
-
│ │ │ │
|
|
1322
|
-
│ │ │ 3. Hold funds │
|
|
1323
|
-
│ │ │ ─────────────────────>
|
|
1324
|
-
│ │ │ │
|
|
1325
|
-
│ │ 4. Escrow created │ │
|
|
1326
|
-
│ │ <────────────────── │ │
|
|
1327
|
-
│ │ │ │
|
|
1328
|
-
│ 5. Provide service │ │
|
|
1329
|
-
│ <──────────────── │ │ │
|
|
1330
|
-
│ │ │ │
|
|
1331
|
-
│ ┌───────────────────────────────────────────────────────────┐
|
|
1332
|
-
│ │ IF SERVICE DELIVERED │
|
|
1333
|
-
│ └───────────────────────────────────────────────────────────┘
|
|
1334
|
-
│ │ 6a. Release escrow │ │
|
|
1335
|
-
│ │ ────────────────────> │
|
|
1336
|
-
│ │ │ │
|
|
1337
|
-
│ │ │ 7a. Transfer to │
|
|
1338
|
-
│ │ │ recipient │
|
|
1339
|
-
│ │ │ ─────────────────────>
|
|
1340
|
-
│ │ │ │
|
|
1341
|
-
│ ┌───────────────────────────────────────────────────────────┐
|
|
1342
|
-
│ │ IF SERVICE FAILED │
|
|
1343
|
-
│ └───────────────────────────────────────────────────────────┘
|
|
1344
|
-
│ 6b. Request refund│ │ │
|
|
1345
|
-
│ ────────────────> │ │ │
|
|
1346
|
-
│ │ 7b. Process refund │ │
|
|
1347
|
-
│ │ ────────────────────> │
|
|
1348
|
-
│ │ │ │
|
|
1349
|
-
│ │ │ 8b. Return to payer │
|
|
1350
|
-
│ │ │ ─────────────────────>
|
|
1351
|
-
└ └ └ └
|
|
1352
|
-
```
|
|
1353
|
-
|
|
1354
|
-
### Basic Escrow Usage
|
|
1355
|
-
|
|
1356
|
-
```typescript
|
|
1357
|
-
import {
|
|
1358
|
-
EscrowClient,
|
|
1359
|
-
canReleaseEscrow,
|
|
1360
|
-
canRefundEscrow,
|
|
1361
|
-
isEscrowExpired,
|
|
1362
|
-
} from 'uvd-x402-sdk/backend';
|
|
1363
|
-
|
|
1364
|
-
const escrow = new EscrowClient({
|
|
1365
|
-
apiKey: process.env.ESCROW_API_KEY,
|
|
1366
|
-
});
|
|
1367
|
-
|
|
1368
|
-
// Create escrow payment
|
|
1369
|
-
const escrowPayment = await escrow.createEscrow({
|
|
1370
|
-
paymentHeader: req.headers['x-payment'],
|
|
1371
|
-
requirements: paymentRequirements,
|
|
1372
|
-
escrowDuration: 86400, // 24 hours
|
|
1373
|
-
releaseConditions: {
|
|
1374
|
-
minHoldTime: 3600, // Minimum 1 hour before release
|
|
1375
|
-
},
|
|
1376
|
-
});
|
|
1377
|
-
|
|
1378
|
-
console.log('Escrow ID:', escrowPayment.id);
|
|
1379
|
-
console.log('Status:', escrowPayment.status); // 'held'
|
|
1380
|
-
|
|
1381
|
-
// Check if we can release
|
|
1382
|
-
if (canReleaseEscrow(escrowPayment)) {
|
|
1383
|
-
// After service is provided, release funds to recipient
|
|
1384
|
-
const released = await escrow.release(escrowPayment.id);
|
|
1385
|
-
console.log('Released, tx:', released.transactionHash);
|
|
1386
|
-
}
|
|
1387
|
-
```
|
|
1388
|
-
|
|
1389
|
-
### Handling Refunds
|
|
1390
|
-
|
|
1391
|
-
```typescript
|
|
1392
|
-
// Payer requests refund
|
|
1393
|
-
const refundRequest = await escrow.requestRefund({
|
|
1394
|
-
escrowId: escrowPayment.id,
|
|
1395
|
-
reason: 'Service not delivered within expected timeframe',
|
|
1396
|
-
evidence: 'Order #12345 shows pending status after 48 hours',
|
|
1397
|
-
});
|
|
1398
|
-
|
|
1399
|
-
// Recipient can approve or reject
|
|
1400
|
-
await escrow.approveRefund(refundRequest.id, refundRequest.amountRequested);
|
|
1401
|
-
// or
|
|
1402
|
-
await escrow.rejectRefund(refundRequest.id, 'Service was delivered, see tracking #XYZ');
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
### Dispute Resolution
|
|
1406
|
-
|
|
1407
|
-
```typescript
|
|
1408
|
-
// If parties disagree, open a dispute
|
|
1409
|
-
const dispute = await escrow.openDispute(
|
|
1410
|
-
escrowPayment.id,
|
|
1411
|
-
'Service quality does not match description',
|
|
1412
|
-
'Screenshots showing issues with delivered product'
|
|
1413
|
-
);
|
|
1414
|
-
|
|
1415
|
-
// Submit additional evidence
|
|
1416
|
-
await escrow.submitEvidence(dispute.id, 'Additional documentation...');
|
|
1417
|
-
|
|
1418
|
-
// Check dispute status
|
|
1419
|
-
const updatedDispute = await escrow.getDispute(dispute.id);
|
|
1420
|
-
console.log('Outcome:', updatedDispute.outcome);
|
|
1421
|
-
// 'pending' | 'payer_wins' | 'recipient_wins' | 'split'
|
|
1422
|
-
```
|
|
1423
|
-
|
|
1424
|
-
### Helper Functions
|
|
1425
|
-
|
|
1426
|
-
```typescript
|
|
1427
|
-
import {
|
|
1428
|
-
canReleaseEscrow,
|
|
1429
|
-
canRefundEscrow,
|
|
1430
|
-
isEscrowExpired,
|
|
1431
|
-
escrowTimeRemaining,
|
|
1432
|
-
} from 'uvd-x402-sdk/backend';
|
|
1433
|
-
|
|
1434
|
-
// Check if escrow can be released
|
|
1435
|
-
if (canReleaseEscrow(escrowPayment)) {
|
|
1436
|
-
await escrow.release(escrowPayment.id);
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
// Check if escrow can be refunded
|
|
1440
|
-
if (canRefundEscrow(escrowPayment)) {
|
|
1441
|
-
await escrow.requestRefund({ escrowId: escrowPayment.id, reason: '...' });
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
// Check expiration
|
|
1445
|
-
if (isEscrowExpired(escrowPayment)) {
|
|
1446
|
-
console.log('Escrow has expired');
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// Time remaining
|
|
1450
|
-
const msRemaining = escrowTimeRemaining(escrowPayment);
|
|
1451
|
-
console.log(`Expires in ${msRemaining / 1000 / 60} minutes`);
|
|
1452
|
-
```
|
|
1453
|
-
|
|
1454
|
-
---
|
|
1455
|
-
|
|
1456
|
-
## Troubleshooting
|
|
1457
|
-
|
|
1458
|
-
### Common Issues
|
|
1459
|
-
|
|
1460
|
-
#### "No Ethereum wallet found"
|
|
1461
|
-
|
|
1462
|
-
Install MetaMask or another EVM wallet. For mobile, use WalletConnect.
|
|
1463
|
-
|
|
1464
|
-
#### "Phantom wallet not installed"
|
|
1465
|
-
|
|
1466
|
-
Install Phantom from [phantom.app](https://phantom.app) for Solana/Fogo support.
|
|
1467
|
-
|
|
1468
|
-
#### "Freighter wallet not installed"
|
|
1469
|
-
|
|
1470
|
-
Install Freighter from [freighter.app](https://www.freighter.app) for Stellar support.
|
|
1471
|
-
|
|
1472
|
-
#### "No NEAR wallet found"
|
|
1473
|
-
|
|
1474
|
-
Install MyNearWallet or Meteor wallet for NEAR support.
|
|
1475
|
-
|
|
1476
|
-
#### "Chain not supported"
|
|
1477
|
-
|
|
1478
|
-
Check if the chain is enabled in `SUPPORTED_CHAINS`.
|
|
1479
|
-
|
|
1480
|
-
#### "Signature rejected by user"
|
|
1481
|
-
|
|
1482
|
-
User clicked "Reject" in their wallet. This is not an error - just user cancellation.
|
|
1483
|
-
|
|
1484
|
-
#### Wrong network in X-PAYMENT header
|
|
1485
|
-
|
|
1486
|
-
For SVM chains, always pass `chainConfig` to `encodePaymentHeader()`:
|
|
1487
|
-
|
|
1488
|
-
```typescript
|
|
1489
|
-
// WRONG - will use 'solana' for Fogo
|
|
1490
|
-
const header = svm.encodePaymentHeader(payload);
|
|
1491
|
-
|
|
1492
|
-
// CORRECT - uses 'fogo' for Fogo
|
|
1493
|
-
const fogoConfig = getChainByName('fogo')!;
|
|
1494
|
-
const header = svm.encodePaymentHeader(payload, fogoConfig);
|
|
1495
|
-
```
|
|
1496
|
-
|
|
1497
|
-
### Debug Mode
|
|
1498
|
-
|
|
1499
|
-
Enable debug logging:
|
|
1500
|
-
|
|
1501
|
-
```typescript
|
|
1502
|
-
const client = new X402Client({
|
|
1503
|
-
debug: true,
|
|
1504
|
-
defaultChain: 'base',
|
|
1505
|
-
});
|
|
1506
|
-
```
|
|
1507
|
-
|
|
1508
|
-
---
|
|
1509
|
-
|
|
1510
|
-
## Security
|
|
1511
|
-
|
|
1512
|
-
- Users NEVER pay gas or submit transactions directly
|
|
1513
|
-
- EVM: Users sign EIP-712 structured messages only
|
|
1514
|
-
- Solana/Fogo: Users sign partial transactions (USDC transfer instruction only)
|
|
1515
|
-
- Stellar: Users sign Soroban authorization entries only
|
|
1516
|
-
- NEAR: Users sign NEP-366 meta-transactions only
|
|
1517
|
-
- The facilitator submits and pays for all transactions
|
|
1518
|
-
|
|
1519
|
-
---
|
|
1520
|
-
|
|
1521
|
-
## License
|
|
1522
|
-
|
|
1523
|
-
MIT License - see [LICENSE](LICENSE) for details.
|
|
1524
|
-
|
|
1525
|
-
---
|
|
1526
|
-
|
|
1527
|
-
## Links
|
|
1528
|
-
|
|
1529
|
-
- [x402 Protocol](https://x402.org)
|
|
1530
|
-
- [Ultravioleta DAO](https://ultravioletadao.xyz)
|
|
1531
|
-
- [402milly](https://402milly.xyz)
|
|
1532
|
-
- [GitHub](https://github.com/UltravioletaDAO/uvd-x402-sdk-typescript)
|
|
1533
|
-
- [npm](https://www.npmjs.com/package/uvd-x402-sdk)
|
|
355
|
+
MIT
|