@one_deploy/sdk 1.0.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.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +0 -0
  2. package/.turbo/turbo-type-check.log +0 -0
  3. package/dist/config/index.d.mts +74 -0
  4. package/dist/config/index.d.ts +74 -0
  5. package/dist/config/index.js +242 -0
  6. package/dist/config/index.js.map +1 -0
  7. package/dist/config/index.mjs +224 -0
  8. package/dist/config/index.mjs.map +1 -0
  9. package/dist/engine-5ndtBaCr.d.ts +1039 -0
  10. package/dist/engine-CrlhH0nw.d.mts +1039 -0
  11. package/dist/hooks/index.d.mts +56 -0
  12. package/dist/hooks/index.d.ts +56 -0
  13. package/dist/hooks/index.js +1360 -0
  14. package/dist/hooks/index.js.map +1 -0
  15. package/dist/hooks/index.mjs +1356 -0
  16. package/dist/hooks/index.mjs.map +1 -0
  17. package/dist/index.d.mts +356 -0
  18. package/dist/index.d.ts +356 -0
  19. package/dist/index.js +5068 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +4949 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/price-CgqXPnT3.d.ts +13 -0
  24. package/dist/price-ClbLHHjv.d.mts +13 -0
  25. package/dist/providers/index.d.mts +121 -0
  26. package/dist/providers/index.d.ts +121 -0
  27. package/dist/providers/index.js +1642 -0
  28. package/dist/providers/index.js.map +1 -0
  29. package/dist/providers/index.mjs +1600 -0
  30. package/dist/providers/index.mjs.map +1 -0
  31. package/dist/react-native.d.mts +120 -0
  32. package/dist/react-native.d.ts +120 -0
  33. package/dist/react-native.js +1792 -0
  34. package/dist/react-native.js.map +1 -0
  35. package/dist/react-native.mjs +1755 -0
  36. package/dist/react-native.mjs.map +1 -0
  37. package/dist/services/index.d.mts +85 -0
  38. package/dist/services/index.d.ts +85 -0
  39. package/dist/services/index.js +1466 -0
  40. package/dist/services/index.js.map +1 -0
  41. package/dist/services/index.mjs +1458 -0
  42. package/dist/services/index.mjs.map +1 -0
  43. package/dist/types/index.d.mts +759 -0
  44. package/dist/types/index.d.ts +759 -0
  45. package/dist/types/index.js +4 -0
  46. package/dist/types/index.js.map +1 -0
  47. package/dist/types/index.mjs +3 -0
  48. package/dist/types/index.mjs.map +1 -0
  49. package/dist/utils/index.d.mts +36 -0
  50. package/dist/utils/index.d.ts +36 -0
  51. package/dist/utils/index.js +164 -0
  52. package/dist/utils/index.js.map +1 -0
  53. package/dist/utils/index.mjs +142 -0
  54. package/dist/utils/index.mjs.map +1 -0
  55. package/package.json +101 -0
  56. package/src/components/OneConnectButton.tsx +143 -0
  57. package/src/components/OneNFTGallery.tsx +324 -0
  58. package/src/components/OneOfframpWidget.tsx +660 -0
  59. package/src/components/OneOnrampWidget.tsx +596 -0
  60. package/src/components/OnePayWidget.tsx +160 -0
  61. package/src/components/OneReceiveWidget.tsx +272 -0
  62. package/src/components/OneSendWidget.tsx +248 -0
  63. package/src/components/OneSwapWidget.tsx +715 -0
  64. package/src/components/OneTransactionButton.tsx +150 -0
  65. package/src/components/OneWalletBalance.tsx +354 -0
  66. package/src/components/index.ts +24 -0
  67. package/src/config/index.ts +299 -0
  68. package/src/hooks/index.ts +2 -0
  69. package/src/hooks/useTokenPrice.ts +162 -0
  70. package/src/hooks/useWalletBalance.ts +98 -0
  71. package/src/index.ts +193 -0
  72. package/src/providers/OneProvider.tsx +452 -0
  73. package/src/providers/ThirdwebProvider.tsx +203 -0
  74. package/src/providers/index.ts +26 -0
  75. package/src/react-native.ts +378 -0
  76. package/src/services/engine.ts +1854 -0
  77. package/src/services/index.ts +30 -0
  78. package/src/services/price.ts +164 -0
  79. package/src/services/supabase.ts +180 -0
  80. package/src/types/index.ts +887 -0
  81. package/src/utils/index.ts +200 -0
  82. package/tsconfig.json +22 -0
  83. package/tsup.config.ts +25 -0
@@ -0,0 +1,715 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useCallback, useEffect, useMemo } from 'react';
4
+ import { useActiveAccount, useSendTransaction } from 'thirdweb/react';
5
+ import { prepareTransaction } from 'thirdweb';
6
+ import type { Chain } from 'thirdweb/chains';
7
+ import { base, ethereum, polygon, arbitrum, optimism, bsc, avalanche } from 'thirdweb/chains';
8
+ import { useThirdwebClient } from '../providers/ThirdwebProvider';
9
+ import { getEngineUrl } from '../config';
10
+
11
+ // ===== Types =====
12
+
13
+ export interface SwapToken {
14
+ address: string;
15
+ symbol: string;
16
+ name: string;
17
+ decimals: number;
18
+ logoURI?: string;
19
+ chainId: number;
20
+ }
21
+
22
+ export interface SwapRoute {
23
+ provider: string;
24
+ fromChain: number;
25
+ toChain: number;
26
+ fromToken: SwapToken;
27
+ toToken: SwapToken;
28
+ fromAmount: string;
29
+ toAmount: string;
30
+ estimatedGas: string;
31
+ priceImpact: string;
32
+ executionTime: string;
33
+ steps: SwapStep[];
34
+ }
35
+
36
+ export interface SwapStep {
37
+ type: 'swap' | 'bridge' | 'approve';
38
+ protocol: string;
39
+ fromToken: string;
40
+ toToken: string;
41
+ fromChain: number;
42
+ toChain: number;
43
+ }
44
+
45
+ export interface OneSwapWidgetProps {
46
+ // Default values
47
+ defaultFromToken?: SwapToken;
48
+ defaultToToken?: SwapToken;
49
+ defaultFromChain?: Chain;
50
+ defaultToChain?: Chain;
51
+
52
+ // Supported chains for cross-chain
53
+ supportedChains?: Chain[];
54
+
55
+ // Supported tokens per chain
56
+ tokens?: SwapToken[];
57
+
58
+ // Mode
59
+ mode?: 'same-chain' | 'cross-chain' | 'auto';
60
+
61
+ // Appearance
62
+ theme?: 'light' | 'dark';
63
+ accentColor?: string;
64
+ className?: string;
65
+ style?: React.CSSProperties;
66
+
67
+ // API endpoints
68
+ quoteEndpoint?: string;
69
+ executeEndpoint?: string;
70
+
71
+ // Callbacks
72
+ onSwapSuccess?: (txHash: string, route?: SwapRoute) => void;
73
+ onSwapError?: (error: Error) => void;
74
+ onQuoteReceived?: (route: SwapRoute) => void;
75
+ onChainChange?: (fromChain: number, toChain: number) => void;
76
+ }
77
+
78
+ // ===== Chain Configuration =====
79
+
80
+ interface ChainConfig {
81
+ chain: Chain;
82
+ name: string;
83
+ shortName: string;
84
+ nativeToken: string;
85
+ }
86
+
87
+ const CHAIN_CONFIGS: ChainConfig[] = [
88
+ { chain: ethereum, name: 'Ethereum', shortName: 'ETH', nativeToken: 'ETH' },
89
+ { chain: base, name: 'Base', shortName: 'BASE', nativeToken: 'ETH' },
90
+ { chain: arbitrum, name: 'Arbitrum', shortName: 'ARB', nativeToken: 'ETH' },
91
+ { chain: optimism, name: 'Optimism', shortName: 'OP', nativeToken: 'ETH' },
92
+ { chain: polygon, name: 'Polygon', shortName: 'MATIC', nativeToken: 'POL' },
93
+ { chain: bsc, name: 'BNB Chain', shortName: 'BSC', nativeToken: 'BNB' },
94
+ { chain: avalanche, name: 'Avalanche', shortName: 'AVAX', nativeToken: 'AVAX' },
95
+ ];
96
+
97
+ // ===== Default Tokens Per Chain =====
98
+
99
+ const TOKENS_BY_CHAIN: Record<number, SwapToken[]> = {
100
+ // Ethereum
101
+ 1: [
102
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'ETH', name: 'Ethereum', decimals: 18, chainId: 1 },
103
+ { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 1 },
104
+ { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', symbol: 'USDT', name: 'Tether', decimals: 6, chainId: 1 },
105
+ { address: '0x6B175474E89094C44Da98b954EescdeCB5', symbol: 'DAI', name: 'Dai', decimals: 18, chainId: 1 },
106
+ { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 1 },
107
+ ],
108
+ // Base
109
+ 8453: [
110
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'ETH', name: 'Ethereum', decimals: 18, chainId: 8453 },
111
+ { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 8453 },
112
+ { address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', symbol: 'DAI', name: 'Dai', decimals: 18, chainId: 8453 },
113
+ { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 8453 },
114
+ ],
115
+ // Arbitrum
116
+ 42161: [
117
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'ETH', name: 'Ethereum', decimals: 18, chainId: 42161 },
118
+ { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 42161 },
119
+ { address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', symbol: 'USDT', name: 'Tether', decimals: 6, chainId: 42161 },
120
+ { address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 42161 },
121
+ ],
122
+ // Optimism
123
+ 10: [
124
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'ETH', name: 'Ethereum', decimals: 18, chainId: 10 },
125
+ { address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 10 },
126
+ { address: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', symbol: 'USDT', name: 'Tether', decimals: 6, chainId: 10 },
127
+ { address: '0x4200000000000000000000000000000000000006', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 10 },
128
+ ],
129
+ // Polygon
130
+ 137: [
131
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'POL', name: 'Polygon', decimals: 18, chainId: 137 },
132
+ { address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 137 },
133
+ { address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', symbol: 'USDT', name: 'Tether', decimals: 6, chainId: 137 },
134
+ { address: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 137 },
135
+ ],
136
+ // BSC
137
+ 56: [
138
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'BNB', name: 'BNB', decimals: 18, chainId: 56 },
139
+ { address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', symbol: 'USDC', name: 'USD Coin', decimals: 18, chainId: 56 },
140
+ { address: '0x55d398326f99059fF775485246999027B3197955', symbol: 'USDT', name: 'Tether', decimals: 18, chainId: 56 },
141
+ { address: '0x2170Ed0880ac9A755fd29B2688956BD959F933F8', symbol: 'ETH', name: 'Ethereum', decimals: 18, chainId: 56 },
142
+ ],
143
+ // Avalanche
144
+ 43114: [
145
+ { address: '0x0000000000000000000000000000000000000000', symbol: 'AVAX', name: 'Avalanche', decimals: 18, chainId: 43114 },
146
+ { address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', symbol: 'USDC', name: 'USD Coin', decimals: 6, chainId: 43114 },
147
+ { address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', symbol: 'USDT', name: 'Tether', decimals: 6, chainId: 43114 },
148
+ { address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', symbol: 'WETH', name: 'Wrapped Ether', decimals: 18, chainId: 43114 },
149
+ ],
150
+ };
151
+
152
+ // ===== Styles =====
153
+
154
+ const containerStyle: React.CSSProperties = {
155
+ display: 'flex',
156
+ flexDirection: 'column',
157
+ gap: '16px',
158
+ padding: '24px',
159
+ borderRadius: '16px',
160
+ border: '1px solid',
161
+ maxWidth: '420px',
162
+ };
163
+
164
+ const chainSelectorStyle: React.CSSProperties = {
165
+ display: 'flex',
166
+ gap: '8px',
167
+ alignItems: 'center',
168
+ };
169
+
170
+ const tokenInputStyle: React.CSSProperties = {
171
+ display: 'flex',
172
+ flexDirection: 'column',
173
+ gap: '8px',
174
+ padding: '16px',
175
+ borderRadius: '12px',
176
+ };
177
+
178
+ const inputStyle: React.CSSProperties = {
179
+ width: '100%',
180
+ padding: '8px 0',
181
+ backgroundColor: 'transparent',
182
+ border: 'none',
183
+ fontSize: '24px',
184
+ fontWeight: 600,
185
+ outline: 'none',
186
+ };
187
+
188
+ const selectStyle: React.CSSProperties = {
189
+ padding: '8px 12px',
190
+ border: 'none',
191
+ borderRadius: '8px',
192
+ fontSize: '14px',
193
+ fontWeight: 500,
194
+ cursor: 'pointer',
195
+ appearance: 'none',
196
+ backgroundRepeat: 'no-repeat',
197
+ backgroundPosition: 'right 8px center',
198
+ paddingRight: '28px',
199
+ };
200
+
201
+ const buttonStyle: React.CSSProperties = {
202
+ width: '100%',
203
+ padding: '16px',
204
+ border: 'none',
205
+ borderRadius: '12px',
206
+ fontSize: '16px',
207
+ fontWeight: 600,
208
+ cursor: 'pointer',
209
+ transition: 'background-color 0.2s',
210
+ };
211
+
212
+ const routeBoxStyle: React.CSSProperties = {
213
+ padding: '12px',
214
+ borderRadius: '8px',
215
+ fontSize: '13px',
216
+ };
217
+
218
+ const labelStyle: React.CSSProperties = {
219
+ fontSize: '12px',
220
+ marginBottom: '4px',
221
+ };
222
+
223
+ // ===== Component =====
224
+
225
+ export function OneSwapWidget({
226
+ defaultFromToken,
227
+ defaultToToken,
228
+ defaultFromChain = base,
229
+ defaultToChain,
230
+ supportedChains,
231
+ tokens,
232
+ mode = 'auto',
233
+ theme = 'dark',
234
+ accentColor = '#10b981',
235
+ className,
236
+ style,
237
+ quoteEndpoint,
238
+ executeEndpoint,
239
+ onSwapSuccess,
240
+ onSwapError,
241
+ onQuoteReceived,
242
+ onChainChange,
243
+ }: OneSwapWidgetProps) {
244
+ const client = useThirdwebClient();
245
+ const account = useActiveAccount();
246
+ const { mutate: sendTransaction, isPending } = useSendTransaction();
247
+
248
+ // Available chains
249
+ const availableChains = useMemo(() => {
250
+ if (supportedChains) {
251
+ return CHAIN_CONFIGS.filter(c => supportedChains.some(sc => sc.id === c.chain.id));
252
+ }
253
+ return CHAIN_CONFIGS;
254
+ }, [supportedChains]);
255
+
256
+ // State
257
+ const [fromChain, setFromChain] = useState<ChainConfig>(
258
+ availableChains.find(c => c.chain.id === defaultFromChain.id) || availableChains[0]
259
+ );
260
+ const [toChain, setToChain] = useState<ChainConfig>(
261
+ defaultToChain
262
+ ? availableChains.find(c => c.chain.id === defaultToChain.id) || availableChains[0]
263
+ : fromChain
264
+ );
265
+
266
+ // Get tokens for selected chains
267
+ const fromTokens = useMemo(() => {
268
+ if (tokens) {
269
+ return tokens.filter(t => t.chainId === fromChain.chain.id);
270
+ }
271
+ return TOKENS_BY_CHAIN[fromChain.chain.id] || [];
272
+ }, [tokens, fromChain]);
273
+
274
+ const toTokens = useMemo(() => {
275
+ if (tokens) {
276
+ return tokens.filter(t => t.chainId === toChain.chain.id);
277
+ }
278
+ return TOKENS_BY_CHAIN[toChain.chain.id] || [];
279
+ }, [tokens, toChain]);
280
+
281
+ const [fromToken, setFromToken] = useState<SwapToken>(defaultFromToken || fromTokens[0]);
282
+ const [toToken, setToToken] = useState<SwapToken>(defaultToToken || toTokens[1] || toTokens[0]);
283
+ const [fromAmount, setFromAmount] = useState('');
284
+ const [toAmount, setToAmount] = useState('');
285
+ const [route, setRoute] = useState<SwapRoute | null>(null);
286
+ const [isLoadingQuote, setIsLoadingQuote] = useState(false);
287
+ const [error, setError] = useState<string | null>(null);
288
+
289
+ const isDark = theme === 'dark';
290
+ const isCrossChain = fromChain.chain.id !== toChain.chain.id;
291
+
292
+ // Update tokens when chain changes
293
+ useEffect(() => {
294
+ const newFromTokens = TOKENS_BY_CHAIN[fromChain.chain.id] || [];
295
+ if (newFromTokens.length > 0 && !newFromTokens.some(t => t.address === fromToken?.address)) {
296
+ setFromToken(newFromTokens[0]);
297
+ }
298
+ }, [fromChain, fromToken?.address]);
299
+
300
+ useEffect(() => {
301
+ const newToTokens = TOKENS_BY_CHAIN[toChain.chain.id] || [];
302
+ if (newToTokens.length > 0 && !newToTokens.some(t => t.address === toToken?.address)) {
303
+ // Try to find same symbol token on destination chain, otherwise use first
304
+ const sameSymbol = newToTokens.find(t => t.symbol === fromToken?.symbol);
305
+ setToToken(sameSymbol || newToTokens[0]);
306
+ }
307
+ }, [toChain, toToken?.address, fromToken?.symbol]);
308
+
309
+ // Notify chain change
310
+ useEffect(() => {
311
+ onChainChange?.(fromChain.chain.id, toChain.chain.id);
312
+ }, [fromChain, toChain, onChainChange]);
313
+
314
+ // Fetch quote
315
+ const fetchQuote = useCallback(async () => {
316
+ if (!fromAmount || parseFloat(fromAmount) <= 0 || !fromToken || !toToken) {
317
+ setToAmount('');
318
+ setRoute(null);
319
+ return;
320
+ }
321
+
322
+ setIsLoadingQuote(true);
323
+ setError(null);
324
+
325
+ try {
326
+ const engineUrl = getEngineUrl();
327
+ const endpoint = quoteEndpoint || `${engineUrl}/v1/swap/quote`;
328
+
329
+ const response = await fetch(endpoint, {
330
+ method: 'POST',
331
+ headers: { 'Content-Type': 'application/json' },
332
+ body: JSON.stringify({
333
+ fromChainId: fromChain.chain.id,
334
+ toChainId: toChain.chain.id,
335
+ fromToken: fromToken.address,
336
+ toToken: toToken.address,
337
+ fromAmount: fromAmount,
338
+ fromDecimals: fromToken.decimals,
339
+ walletAddress: account?.address,
340
+ slippage: 0.5, // 0.5% default slippage
341
+ }),
342
+ });
343
+
344
+ const data = await response.json();
345
+
346
+ if (data.success && data.data) {
347
+ const routeData: SwapRoute = {
348
+ provider: data.data.provider || 'aggregator',
349
+ fromChain: fromChain.chain.id,
350
+ toChain: toChain.chain.id,
351
+ fromToken,
352
+ toToken,
353
+ fromAmount,
354
+ toAmount: data.data.toAmount || '0',
355
+ estimatedGas: data.data.estimatedGas || '0',
356
+ priceImpact: data.data.priceImpact || '0',
357
+ executionTime: isCrossChain ? '2-5 min' : '~30 sec',
358
+ steps: data.data.steps || [],
359
+ };
360
+
361
+ setRoute(routeData);
362
+ setToAmount(routeData.toAmount);
363
+ onQuoteReceived?.(routeData);
364
+ } else {
365
+ // Fallback: simple estimate
366
+ const estimatedOutput = estimateSwapOutput(fromAmount, fromToken, toToken);
367
+ setToAmount(estimatedOutput.toFixed(6));
368
+ setError(data.error?.message || null);
369
+ }
370
+ } catch (err) {
371
+ // Fallback estimate on error
372
+ const estimatedOutput = estimateSwapOutput(fromAmount, fromToken, toToken);
373
+ setToAmount(estimatedOutput.toFixed(6));
374
+ console.error('Quote fetch error:', err);
375
+ } finally {
376
+ setIsLoadingQuote(false);
377
+ }
378
+ }, [fromAmount, fromToken, toToken, fromChain, toChain, account?.address, quoteEndpoint, isCrossChain, onQuoteReceived]);
379
+
380
+ // Simple estimate fallback
381
+ const estimateSwapOutput = (amount: string, from: SwapToken, to: SwapToken): number => {
382
+ const prices: Record<string, number> = {
383
+ ETH: 3500, WETH: 3500, BNB: 650, AVAX: 42, POL: 0.85,
384
+ USDC: 1, USDT: 1, DAI: 1,
385
+ };
386
+ const fromPrice = prices[from.symbol] || 1;
387
+ const toPrice = prices[to.symbol] || 1;
388
+ return (parseFloat(amount) * fromPrice) / toPrice;
389
+ };
390
+
391
+ useEffect(() => {
392
+ const debounce = setTimeout(fetchQuote, 500);
393
+ return () => clearTimeout(debounce);
394
+ }, [fetchQuote]);
395
+
396
+ // Swap tokens/chains
397
+ const handleSwapDirection = () => {
398
+ const tempChain = fromChain;
399
+ const tempToken = fromToken;
400
+ setFromChain(toChain);
401
+ setToChain(tempChain);
402
+ setFromToken(toToken);
403
+ setToToken(tempToken);
404
+ setFromAmount(toAmount);
405
+ setToAmount(fromAmount);
406
+ };
407
+
408
+ // Execute swap
409
+ const handleSwap = async () => {
410
+ if (!account || !fromToken || !toToken) return;
411
+
412
+ setError(null);
413
+
414
+ try {
415
+ const engineUrl = getEngineUrl();
416
+ const endpoint = executeEndpoint || `${engineUrl}/v1/swap/execute`;
417
+
418
+ const response = await fetch(endpoint, {
419
+ method: 'POST',
420
+ headers: { 'Content-Type': 'application/json' },
421
+ body: JSON.stringify({
422
+ fromChainId: fromChain.chain.id,
423
+ toChainId: toChain.chain.id,
424
+ fromToken: fromToken.address,
425
+ toToken: toToken.address,
426
+ fromAmount,
427
+ walletAddress: account.address,
428
+ slippage: 0.5,
429
+ }),
430
+ });
431
+
432
+ const data = await response.json();
433
+
434
+ if (data.success && data.data?.transaction) {
435
+ const tx = prepareTransaction({
436
+ to: data.data.transaction.to,
437
+ data: data.data.transaction.data,
438
+ value: BigInt(data.data.transaction.value || 0),
439
+ chain: fromChain.chain,
440
+ client,
441
+ });
442
+
443
+ sendTransaction(tx, {
444
+ onSuccess: (result) => {
445
+ onSwapSuccess?.(result.transactionHash, route || undefined);
446
+ setFromAmount('');
447
+ setToAmount('');
448
+ setRoute(null);
449
+ },
450
+ onError: (err) => {
451
+ setError(err.message);
452
+ onSwapError?.(err);
453
+ },
454
+ });
455
+ } else {
456
+ throw new Error(data.error?.message || 'Swap execution failed');
457
+ }
458
+ } catch (err) {
459
+ const error = err instanceof Error ? err : new Error('Swap failed');
460
+ setError(error.message);
461
+ onSwapError?.(error);
462
+ }
463
+ };
464
+
465
+ const canSwap = account && fromAmount && parseFloat(fromAmount) > 0 && !isPending && !isLoadingQuote;
466
+
467
+ // Theme colors
468
+ const bgColor = isDark ? '#1f2937' : '#ffffff';
469
+ const borderColor = isDark ? '#374151' : '#e5e7eb';
470
+ const textColor = isDark ? '#ffffff' : '#111827';
471
+ const mutedColor = isDark ? '#9ca3af' : '#6b7280';
472
+ const inputBg = isDark ? '#374151' : '#f3f4f6';
473
+
474
+ return (
475
+ <div
476
+ className={className}
477
+ style={{
478
+ ...containerStyle,
479
+ backgroundColor: bgColor,
480
+ borderColor,
481
+ ...style,
482
+ }}
483
+ >
484
+ {/* Header */}
485
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
486
+ <h3 style={{ color: textColor, margin: 0, fontSize: '18px', fontWeight: 600 }}>
487
+ Swap
488
+ </h3>
489
+ {isCrossChain && (
490
+ <span style={{
491
+ fontSize: '11px',
492
+ padding: '4px 8px',
493
+ backgroundColor: accentColor,
494
+ color: '#fff',
495
+ borderRadius: '4px',
496
+ }}>
497
+ Cross-Chain
498
+ </span>
499
+ )}
500
+ </div>
501
+
502
+ {/* From Section */}
503
+ <div style={{ ...tokenInputStyle, backgroundColor: inputBg }}>
504
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
505
+ <span style={{ ...labelStyle, color: mutedColor }}>From</span>
506
+ {mode !== 'same-chain' && (
507
+ <div style={chainSelectorStyle}>
508
+ <select
509
+ value={fromChain.chain.id}
510
+ onChange={(e) => {
511
+ const chain = availableChains.find(c => c.chain.id === parseInt(e.target.value));
512
+ if (chain) setFromChain(chain);
513
+ }}
514
+ style={{
515
+ ...selectStyle,
516
+ backgroundColor: isDark ? '#4b5563' : '#e5e7eb',
517
+ color: textColor,
518
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='${isDark ? '%239ca3af' : '%236b7280'}'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
519
+ backgroundSize: '12px',
520
+ fontSize: '12px',
521
+ padding: '6px 24px 6px 8px',
522
+ }}
523
+ >
524
+ {availableChains.map(c => (
525
+ <option key={c.chain.id} value={c.chain.id}>{c.shortName}</option>
526
+ ))}
527
+ </select>
528
+ </div>
529
+ )}
530
+ </div>
531
+
532
+ <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
533
+ <input
534
+ type="number"
535
+ value={fromAmount}
536
+ onChange={(e) => setFromAmount(e.target.value)}
537
+ placeholder="0.0"
538
+ style={{ ...inputStyle, flex: 1, color: textColor }}
539
+ />
540
+ <select
541
+ value={fromToken?.address || ''}
542
+ onChange={(e) => {
543
+ const token = fromTokens.find(t => t.address === e.target.value);
544
+ if (token) setFromToken(token);
545
+ }}
546
+ style={{
547
+ ...selectStyle,
548
+ backgroundColor: isDark ? '#4b5563' : '#e5e7eb',
549
+ color: textColor,
550
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='${isDark ? '%239ca3af' : '%236b7280'}'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
551
+ backgroundSize: '12px',
552
+ }}
553
+ >
554
+ {fromTokens.map(token => (
555
+ <option key={token.address} value={token.address}>{token.symbol}</option>
556
+ ))}
557
+ </select>
558
+ </div>
559
+ </div>
560
+
561
+ {/* Swap Direction Button */}
562
+ <div style={{ display: 'flex', justifyContent: 'center', margin: '-8px 0' }}>
563
+ <button
564
+ onClick={handleSwapDirection}
565
+ style={{
566
+ width: '36px',
567
+ height: '36px',
568
+ borderRadius: '50%',
569
+ backgroundColor: inputBg,
570
+ border: `2px solid ${bgColor}`,
571
+ cursor: 'pointer',
572
+ display: 'flex',
573
+ alignItems: 'center',
574
+ justifyContent: 'center',
575
+ fontSize: '16px',
576
+ color: mutedColor,
577
+ zIndex: 1,
578
+ }}
579
+ >
580
+
581
+ </button>
582
+ </div>
583
+
584
+ {/* To Section */}
585
+ <div style={{ ...tokenInputStyle, backgroundColor: inputBg }}>
586
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
587
+ <span style={{ ...labelStyle, color: mutedColor }}>To</span>
588
+ {mode !== 'same-chain' && (
589
+ <div style={chainSelectorStyle}>
590
+ <select
591
+ value={toChain.chain.id}
592
+ onChange={(e) => {
593
+ const chain = availableChains.find(c => c.chain.id === parseInt(e.target.value));
594
+ if (chain) setToChain(chain);
595
+ }}
596
+ style={{
597
+ ...selectStyle,
598
+ backgroundColor: isDark ? '#4b5563' : '#e5e7eb',
599
+ color: textColor,
600
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='${isDark ? '%239ca3af' : '%236b7280'}'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
601
+ backgroundSize: '12px',
602
+ fontSize: '12px',
603
+ padding: '6px 24px 6px 8px',
604
+ }}
605
+ >
606
+ {availableChains.map(c => (
607
+ <option key={c.chain.id} value={c.chain.id}>{c.shortName}</option>
608
+ ))}
609
+ </select>
610
+ </div>
611
+ )}
612
+ </div>
613
+
614
+ <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
615
+ <input
616
+ type="number"
617
+ value={toAmount}
618
+ placeholder="0.0"
619
+ readOnly
620
+ style={{ ...inputStyle, flex: 1, color: mutedColor }}
621
+ />
622
+ <select
623
+ value={toToken?.address || ''}
624
+ onChange={(e) => {
625
+ const token = toTokens.find(t => t.address === e.target.value);
626
+ if (token) setToToken(token);
627
+ }}
628
+ style={{
629
+ ...selectStyle,
630
+ backgroundColor: isDark ? '#4b5563' : '#e5e7eb',
631
+ color: textColor,
632
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='${isDark ? '%239ca3af' : '%236b7280'}'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
633
+ backgroundSize: '12px',
634
+ }}
635
+ >
636
+ {toTokens.map(token => (
637
+ <option key={token.address} value={token.address}>{token.symbol}</option>
638
+ ))}
639
+ </select>
640
+ </div>
641
+ </div>
642
+
643
+ {/* Route Info */}
644
+ {(route || (fromAmount && toAmount)) && (
645
+ <div style={{ ...routeBoxStyle, backgroundColor: inputBg }}>
646
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '6px' }}>
647
+ <span style={{ color: mutedColor }}>Rate</span>
648
+ <span style={{ color: textColor }}>
649
+ 1 {fromToken?.symbol} = {toAmount && fromAmount ? (parseFloat(toAmount) / parseFloat(fromAmount)).toFixed(6) : '0'} {toToken?.symbol}
650
+ </span>
651
+ </div>
652
+ {route?.priceImpact && parseFloat(route.priceImpact) > 0 && (
653
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '6px' }}>
654
+ <span style={{ color: mutedColor }}>Price Impact</span>
655
+ <span style={{ color: parseFloat(route.priceImpact) > 3 ? '#ef4444' : textColor }}>
656
+ {route.priceImpact}%
657
+ </span>
658
+ </div>
659
+ )}
660
+ {isCrossChain && (
661
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
662
+ <span style={{ color: mutedColor }}>Est. Time</span>
663
+ <span style={{ color: textColor }}>{route?.executionTime || '2-5 min'}</span>
664
+ </div>
665
+ )}
666
+ </div>
667
+ )}
668
+
669
+ {/* Error */}
670
+ {error && (
671
+ <div style={{
672
+ color: '#ef4444',
673
+ fontSize: '14px',
674
+ padding: '10px 12px',
675
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
676
+ borderRadius: '8px',
677
+ }}>
678
+ {error}
679
+ </div>
680
+ )}
681
+
682
+ {/* Swap Button */}
683
+ <button
684
+ onClick={handleSwap}
685
+ disabled={!canSwap}
686
+ style={{
687
+ ...buttonStyle,
688
+ backgroundColor: canSwap ? accentColor : '#6b7280',
689
+ color: '#ffffff',
690
+ cursor: canSwap ? 'pointer' : 'not-allowed',
691
+ }}
692
+ >
693
+ {isPending
694
+ ? 'Swapping...'
695
+ : isLoadingQuote
696
+ ? 'Getting Quote...'
697
+ : !account
698
+ ? 'Connect Wallet'
699
+ : isCrossChain
700
+ ? `Bridge & Swap`
701
+ : 'Swap'}
702
+ </button>
703
+ </div>
704
+ );
705
+ }
706
+
707
+ // ===== Presets =====
708
+
709
+ export function OneSameChainSwap(props: Omit<OneSwapWidgetProps, 'mode'>) {
710
+ return <OneSwapWidget {...props} mode="same-chain" />;
711
+ }
712
+
713
+ export function OneCrossChainSwap(props: Omit<OneSwapWidgetProps, 'mode'>) {
714
+ return <OneSwapWidget {...props} mode="cross-chain" />;
715
+ }