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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +782 -0
  3. package/dist/index-BrBqP1I8.d.ts +199 -0
  4. package/dist/index-D6Sr4ARD.d.mts +429 -0
  5. package/dist/index-D6Sr4ARD.d.ts +429 -0
  6. package/dist/index-DJ4Cvrev.d.mts +199 -0
  7. package/dist/index.d.mts +3 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +1178 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/index.mjs +1146 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/providers/evm/index.d.mts +84 -0
  14. package/dist/providers/evm/index.d.ts +84 -0
  15. package/dist/providers/evm/index.js +740 -0
  16. package/dist/providers/evm/index.js.map +1 -0
  17. package/dist/providers/evm/index.mjs +735 -0
  18. package/dist/providers/evm/index.mjs.map +1 -0
  19. package/dist/providers/near/index.d.mts +99 -0
  20. package/dist/providers/near/index.d.ts +99 -0
  21. package/dist/providers/near/index.js +483 -0
  22. package/dist/providers/near/index.js.map +1 -0
  23. package/dist/providers/near/index.mjs +478 -0
  24. package/dist/providers/near/index.mjs.map +1 -0
  25. package/dist/providers/solana/index.d.mts +115 -0
  26. package/dist/providers/solana/index.d.ts +115 -0
  27. package/dist/providers/solana/index.js +771 -0
  28. package/dist/providers/solana/index.js.map +1 -0
  29. package/dist/providers/solana/index.mjs +765 -0
  30. package/dist/providers/solana/index.mjs.map +1 -0
  31. package/dist/providers/stellar/index.d.mts +67 -0
  32. package/dist/providers/stellar/index.d.ts +67 -0
  33. package/dist/providers/stellar/index.js +306 -0
  34. package/dist/providers/stellar/index.js.map +1 -0
  35. package/dist/providers/stellar/index.mjs +301 -0
  36. package/dist/providers/stellar/index.mjs.map +1 -0
  37. package/dist/react/index.d.mts +73 -0
  38. package/dist/react/index.d.ts +73 -0
  39. package/dist/react/index.js +1218 -0
  40. package/dist/react/index.js.map +1 -0
  41. package/dist/react/index.mjs +1211 -0
  42. package/dist/react/index.mjs.map +1 -0
  43. package/dist/utils/index.d.mts +103 -0
  44. package/dist/utils/index.d.ts +103 -0
  45. package/dist/utils/index.js +575 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/utils/index.mjs +562 -0
  48. package/dist/utils/index.mjs.map +1 -0
  49. package/package.json +149 -0
  50. package/src/chains/index.ts +539 -0
  51. package/src/client/X402Client.ts +663 -0
  52. package/src/client/index.ts +1 -0
  53. package/src/index.ts +166 -0
  54. package/src/providers/evm/index.ts +394 -0
  55. package/src/providers/near/index.ts +664 -0
  56. package/src/providers/solana/index.ts +489 -0
  57. package/src/providers/stellar/index.ts +376 -0
  58. package/src/react/index.tsx +417 -0
  59. package/src/types/index.ts +561 -0
  60. package/src/utils/index.ts +20 -0
  61. 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 };