uvd-x402-sdk 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +782 -0
- package/dist/index-BrBqP1I8.d.ts +199 -0
- package/dist/index-D6Sr4ARD.d.mts +429 -0
- package/dist/index-D6Sr4ARD.d.ts +429 -0
- package/dist/index-DJ4Cvrev.d.mts +199 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1178 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1146 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/evm/index.d.mts +84 -0
- package/dist/providers/evm/index.d.ts +84 -0
- package/dist/providers/evm/index.js +740 -0
- package/dist/providers/evm/index.js.map +1 -0
- package/dist/providers/evm/index.mjs +735 -0
- package/dist/providers/evm/index.mjs.map +1 -0
- package/dist/providers/near/index.d.mts +99 -0
- package/dist/providers/near/index.d.ts +99 -0
- package/dist/providers/near/index.js +483 -0
- package/dist/providers/near/index.js.map +1 -0
- package/dist/providers/near/index.mjs +478 -0
- package/dist/providers/near/index.mjs.map +1 -0
- package/dist/providers/solana/index.d.mts +115 -0
- package/dist/providers/solana/index.d.ts +115 -0
- package/dist/providers/solana/index.js +771 -0
- package/dist/providers/solana/index.js.map +1 -0
- package/dist/providers/solana/index.mjs +765 -0
- package/dist/providers/solana/index.mjs.map +1 -0
- package/dist/providers/stellar/index.d.mts +67 -0
- package/dist/providers/stellar/index.d.ts +67 -0
- package/dist/providers/stellar/index.js +306 -0
- package/dist/providers/stellar/index.js.map +1 -0
- package/dist/providers/stellar/index.mjs +301 -0
- package/dist/providers/stellar/index.mjs.map +1 -0
- package/dist/react/index.d.mts +73 -0
- package/dist/react/index.d.ts +73 -0
- package/dist/react/index.js +1218 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1211 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +103 -0
- package/dist/utils/index.d.ts +103 -0
- package/dist/utils/index.js +575 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +562 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +149 -0
- package/src/chains/index.ts +539 -0
- package/src/client/X402Client.ts +663 -0
- package/src/client/index.ts +1 -0
- package/src/index.ts +166 -0
- package/src/providers/evm/index.ts +394 -0
- package/src/providers/near/index.ts +664 -0
- package/src/providers/solana/index.ts +489 -0
- package/src/providers/stellar/index.ts +376 -0
- package/src/react/index.tsx +417 -0
- package/src/types/index.ts +561 -0
- package/src/utils/index.ts +20 -0
- package/src/utils/x402.ts +295 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uvd-x402-sdk - React Hooks
|
|
3
|
+
*
|
|
4
|
+
* Provides React hooks for easy integration with x402 payments.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { X402Provider, useX402, useBalance, usePayment } from 'uvd-x402-sdk/react';
|
|
9
|
+
*
|
|
10
|
+
* function App() {
|
|
11
|
+
* return (
|
|
12
|
+
* <X402Provider config={{ defaultChain: 'base' }}>
|
|
13
|
+
* <PaymentButton amount="10.00" recipient="0x..." />
|
|
14
|
+
* </X402Provider>
|
|
15
|
+
* );
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* function PaymentButton({ amount, recipient }) {
|
|
19
|
+
* const { connect, isConnected, address } = useX402();
|
|
20
|
+
* const { balance, isLoading: balanceLoading } = useBalance();
|
|
21
|
+
* const { pay, isPaying } = usePayment();
|
|
22
|
+
*
|
|
23
|
+
* if (!isConnected) {
|
|
24
|
+
* return <button onClick={() => connect('base')}>Connect Wallet</button>;
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <button
|
|
29
|
+
* onClick={() => pay({ amount, recipient })}
|
|
30
|
+
* disabled={isPaying}
|
|
31
|
+
* >
|
|
32
|
+
* Pay ${amount} USDC
|
|
33
|
+
* </button>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { createContext, useContext, useCallback, useState, useEffect, useMemo, type ReactNode } from 'react';
|
|
40
|
+
import { X402Client } from '../client';
|
|
41
|
+
import type {
|
|
42
|
+
X402ClientConfig,
|
|
43
|
+
WalletState,
|
|
44
|
+
PaymentInfo,
|
|
45
|
+
PaymentResult,
|
|
46
|
+
ChainConfig,
|
|
47
|
+
NetworkBalance,
|
|
48
|
+
} from '../types';
|
|
49
|
+
import { getEnabledChains, getChainByName } from '../chains';
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// CONTEXT
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
interface X402ContextValue {
|
|
56
|
+
client: X402Client;
|
|
57
|
+
state: WalletState;
|
|
58
|
+
connect: (chainName?: string) => Promise<string>;
|
|
59
|
+
disconnect: () => Promise<void>;
|
|
60
|
+
switchChain: (chainName: string) => Promise<void>;
|
|
61
|
+
getBalance: () => Promise<string>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const X402Context = createContext<X402ContextValue | null>(null);
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// PROVIDER
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
interface X402ProviderProps {
|
|
71
|
+
children: ReactNode;
|
|
72
|
+
config?: X402ClientConfig;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* X402Provider - Context provider for x402 SDK
|
|
77
|
+
*
|
|
78
|
+
* Wrap your app with this provider to use the x402 hooks.
|
|
79
|
+
*/
|
|
80
|
+
export function X402Provider({ children, config }: X402ProviderProps) {
|
|
81
|
+
const [client] = useState(() => new X402Client(config));
|
|
82
|
+
const [state, setState] = useState<WalletState>(() => client.getState());
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
// Subscribe to events
|
|
86
|
+
const unsubConnect = client.on('connect', (newState) => {
|
|
87
|
+
setState(newState);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const unsubDisconnect = client.on('disconnect', () => {
|
|
91
|
+
setState(client.getState());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const unsubChainChanged = client.on('chainChanged', () => {
|
|
95
|
+
setState(client.getState());
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const unsubAccountChanged = client.on('accountChanged', () => {
|
|
99
|
+
setState(client.getState());
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
unsubConnect();
|
|
104
|
+
unsubDisconnect();
|
|
105
|
+
unsubChainChanged();
|
|
106
|
+
unsubAccountChanged();
|
|
107
|
+
};
|
|
108
|
+
}, [client]);
|
|
109
|
+
|
|
110
|
+
const connect = useCallback(
|
|
111
|
+
async (chainName?: string) => {
|
|
112
|
+
const address = await client.connect(chainName);
|
|
113
|
+
setState(client.getState());
|
|
114
|
+
return address;
|
|
115
|
+
},
|
|
116
|
+
[client]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const disconnect = useCallback(async () => {
|
|
120
|
+
await client.disconnect();
|
|
121
|
+
setState(client.getState());
|
|
122
|
+
}, [client]);
|
|
123
|
+
|
|
124
|
+
const switchChain = useCallback(
|
|
125
|
+
async (chainName: string) => {
|
|
126
|
+
await client.switchChain(chainName);
|
|
127
|
+
setState(client.getState());
|
|
128
|
+
},
|
|
129
|
+
[client]
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const getBalance = useCallback(() => client.getBalance(), [client]);
|
|
133
|
+
|
|
134
|
+
const value = useMemo(
|
|
135
|
+
() => ({
|
|
136
|
+
client,
|
|
137
|
+
state,
|
|
138
|
+
connect,
|
|
139
|
+
disconnect,
|
|
140
|
+
switchChain,
|
|
141
|
+
getBalance,
|
|
142
|
+
}),
|
|
143
|
+
[client, state, connect, disconnect, switchChain, getBalance]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return <X402Context.Provider value={value}>{children}</X402Context.Provider>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// HOOKS
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* useX402 - Main hook for x402 functionality
|
|
155
|
+
*
|
|
156
|
+
* Returns wallet state and connection methods.
|
|
157
|
+
*/
|
|
158
|
+
export function useX402() {
|
|
159
|
+
const context = useContext(X402Context);
|
|
160
|
+
if (!context) {
|
|
161
|
+
throw new Error('useX402 must be used within an X402Provider');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { client, state, connect, disconnect, switchChain, getBalance } = context;
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
// State
|
|
168
|
+
isConnected: state.connected,
|
|
169
|
+
address: state.address,
|
|
170
|
+
chainId: state.chainId,
|
|
171
|
+
network: state.network,
|
|
172
|
+
networkType: state.networkType,
|
|
173
|
+
|
|
174
|
+
// Methods
|
|
175
|
+
connect,
|
|
176
|
+
disconnect,
|
|
177
|
+
switchChain,
|
|
178
|
+
getBalance,
|
|
179
|
+
|
|
180
|
+
// Client access for advanced usage
|
|
181
|
+
client,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* useBalance - Hook for USDC balance management
|
|
187
|
+
*/
|
|
188
|
+
export function useBalance() {
|
|
189
|
+
const { client, isConnected, network } = useX402();
|
|
190
|
+
const [balance, setBalance] = useState<string | null>(null);
|
|
191
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
192
|
+
const [error, setError] = useState<string | null>(null);
|
|
193
|
+
|
|
194
|
+
const fetchBalance = useCallback(async () => {
|
|
195
|
+
if (!isConnected) {
|
|
196
|
+
setBalance(null);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setIsLoading(true);
|
|
201
|
+
setError(null);
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const bal = await client.getBalance();
|
|
205
|
+
setBalance(bal);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch balance');
|
|
208
|
+
setBalance(null);
|
|
209
|
+
} finally {
|
|
210
|
+
setIsLoading(false);
|
|
211
|
+
}
|
|
212
|
+
}, [client, isConnected]);
|
|
213
|
+
|
|
214
|
+
// Auto-fetch on connection/network change
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
fetchBalance();
|
|
217
|
+
}, [fetchBalance, network]);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
balance,
|
|
221
|
+
isLoading,
|
|
222
|
+
error,
|
|
223
|
+
refetch: fetchBalance,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* usePayment - Hook for creating payments
|
|
229
|
+
*/
|
|
230
|
+
export function usePayment() {
|
|
231
|
+
const { client, isConnected } = useX402();
|
|
232
|
+
const [isPaying, setIsPaying] = useState(false);
|
|
233
|
+
const [error, setError] = useState<string | null>(null);
|
|
234
|
+
const [lastResult, setLastResult] = useState<PaymentResult | null>(null);
|
|
235
|
+
|
|
236
|
+
const pay = useCallback(
|
|
237
|
+
async (paymentInfo: PaymentInfo): Promise<PaymentResult> => {
|
|
238
|
+
if (!isConnected) {
|
|
239
|
+
throw new Error('Wallet not connected');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
setIsPaying(true);
|
|
243
|
+
setError(null);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const result = await client.createPayment(paymentInfo);
|
|
247
|
+
setLastResult(result);
|
|
248
|
+
return result;
|
|
249
|
+
} catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : 'Payment failed';
|
|
251
|
+
setError(message);
|
|
252
|
+
throw err;
|
|
253
|
+
} finally {
|
|
254
|
+
setIsPaying(false);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
[client, isConnected]
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const reset = useCallback(() => {
|
|
261
|
+
setError(null);
|
|
262
|
+
setLastResult(null);
|
|
263
|
+
}, []);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
pay,
|
|
267
|
+
isPaying,
|
|
268
|
+
error,
|
|
269
|
+
lastResult,
|
|
270
|
+
reset,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* useChains - Hook for chain information
|
|
276
|
+
*/
|
|
277
|
+
export function useChains() {
|
|
278
|
+
const { network: currentNetwork } = useX402();
|
|
279
|
+
|
|
280
|
+
const chains = useMemo(() => getEnabledChains(), []);
|
|
281
|
+
|
|
282
|
+
const currentChain = useMemo(
|
|
283
|
+
() => (currentNetwork ? getChainByName(currentNetwork) : null),
|
|
284
|
+
[currentNetwork]
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const evmChains = useMemo(
|
|
288
|
+
() => chains.filter((c) => c.networkType === 'evm'),
|
|
289
|
+
[chains]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const nonEvmChains = useMemo(
|
|
293
|
+
() => chains.filter((c) => c.networkType !== 'evm'),
|
|
294
|
+
[chains]
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
chains,
|
|
299
|
+
currentChain,
|
|
300
|
+
evmChains,
|
|
301
|
+
nonEvmChains,
|
|
302
|
+
getChain: getChainByName,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* useNetworkBalances - Hook for fetching balances across all networks
|
|
308
|
+
*/
|
|
309
|
+
export function useNetworkBalances() {
|
|
310
|
+
const { address, isConnected, networkType } = useX402();
|
|
311
|
+
const [balances, setBalances] = useState<Map<string, NetworkBalance>>(new Map());
|
|
312
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
313
|
+
|
|
314
|
+
const fetchAllBalances = useCallback(async () => {
|
|
315
|
+
if (!isConnected || !address) {
|
|
316
|
+
setBalances(new Map());
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
setIsLoading(true);
|
|
321
|
+
const chains = getEnabledChains();
|
|
322
|
+
|
|
323
|
+
// Filter to compatible chains
|
|
324
|
+
const compatibleChains = chains.filter((chain) => {
|
|
325
|
+
// EVM address starts with 0x
|
|
326
|
+
if (address.startsWith('0x')) {
|
|
327
|
+
return chain.networkType === 'evm';
|
|
328
|
+
}
|
|
329
|
+
// Otherwise match by network type
|
|
330
|
+
return chain.networkType === networkType;
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const newBalances = new Map<string, NetworkBalance>();
|
|
334
|
+
|
|
335
|
+
// Initialize loading states
|
|
336
|
+
compatibleChains.forEach((chain) => {
|
|
337
|
+
newBalances.set(chain.name, {
|
|
338
|
+
chainName: chain.name,
|
|
339
|
+
displayName: chain.displayName,
|
|
340
|
+
balance: null,
|
|
341
|
+
isLoading: true,
|
|
342
|
+
error: null,
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
setBalances(new Map(newBalances));
|
|
346
|
+
|
|
347
|
+
// Fetch in parallel
|
|
348
|
+
await Promise.allSettled(
|
|
349
|
+
compatibleChains.map(async (chain) => {
|
|
350
|
+
try {
|
|
351
|
+
const provider = new (await import('ethers')).JsonRpcProvider(chain.rpcUrl);
|
|
352
|
+
const usdcAbi = ['function balanceOf(address) view returns (uint256)'];
|
|
353
|
+
const contract = new (await import('ethers')).Contract(
|
|
354
|
+
chain.usdc.address,
|
|
355
|
+
usdcAbi,
|
|
356
|
+
provider
|
|
357
|
+
);
|
|
358
|
+
const balance = await contract.balanceOf(address);
|
|
359
|
+
const formatted = parseFloat(
|
|
360
|
+
(await import('ethers')).formatUnits(balance, chain.usdc.decimals)
|
|
361
|
+
).toFixed(2);
|
|
362
|
+
|
|
363
|
+
newBalances.set(chain.name, {
|
|
364
|
+
chainName: chain.name,
|
|
365
|
+
displayName: chain.displayName,
|
|
366
|
+
balance: formatted,
|
|
367
|
+
isLoading: false,
|
|
368
|
+
error: null,
|
|
369
|
+
});
|
|
370
|
+
} catch (err) {
|
|
371
|
+
newBalances.set(chain.name, {
|
|
372
|
+
chainName: chain.name,
|
|
373
|
+
displayName: chain.displayName,
|
|
374
|
+
balance: null,
|
|
375
|
+
isLoading: false,
|
|
376
|
+
error: err instanceof Error ? err.message : 'Failed',
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
setBalances(new Map(newBalances));
|
|
380
|
+
})
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
setIsLoading(false);
|
|
384
|
+
}, [address, isConnected, networkType]);
|
|
385
|
+
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
fetchAllBalances();
|
|
388
|
+
}, [fetchAllBalances]);
|
|
389
|
+
|
|
390
|
+
// Find network with highest balance
|
|
391
|
+
const highestBalanceNetwork = useMemo(() => {
|
|
392
|
+
let maxBalance = 0;
|
|
393
|
+
let maxChain: string | null = null;
|
|
394
|
+
|
|
395
|
+
balances.forEach((nb, chainName) => {
|
|
396
|
+
if (nb.balance !== null && !nb.error) {
|
|
397
|
+
const bal = parseFloat(nb.balance);
|
|
398
|
+
if (bal > maxBalance) {
|
|
399
|
+
maxBalance = bal;
|
|
400
|
+
maxChain = chainName;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return maxChain;
|
|
406
|
+
}, [balances]);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
balances,
|
|
410
|
+
isLoading,
|
|
411
|
+
refetch: fetchAllBalances,
|
|
412
|
+
highestBalanceNetwork,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Re-export types for convenience
|
|
417
|
+
export type { WalletState, PaymentInfo, PaymentResult, ChainConfig, NetworkBalance };
|