@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,596 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect, useCallback } from 'react';
4
+ import { useActiveAccount } from 'thirdweb/react';
5
+ import { getEngineUrl } from '../config';
6
+
7
+ // ===== Types =====
8
+
9
+ export interface OneOnrampWidgetProps {
10
+ // Configuration
11
+ defaultFiat?: string;
12
+ defaultCrypto?: string;
13
+ defaultAmount?: number;
14
+ defaultNetwork?: string;
15
+
16
+ // Currency options
17
+ supportedFiats?: string[];
18
+ supportedCryptos?: string[];
19
+ supportedNetworks?: string[];
20
+
21
+ // User info (pre-fill)
22
+ email?: string;
23
+ country?: string;
24
+
25
+ // Appearance
26
+ theme?: 'light' | 'dark';
27
+ accentColor?: string;
28
+ className?: string;
29
+ style?: React.CSSProperties;
30
+
31
+ // Display mode
32
+ mode?: 'form' | 'embed' | 'popup';
33
+ embedHeight?: number;
34
+
35
+ // Callbacks
36
+ onSuccess?: (transaction: OnrampTransaction) => void;
37
+ onError?: (error: Error) => void;
38
+ onClose?: () => void;
39
+ onQuoteUpdate?: (quote: OnrampQuote) => void;
40
+ }
41
+
42
+ export interface OnrampQuote {
43
+ fiatCurrency: string;
44
+ fiatAmount: number;
45
+ cryptoCurrency: string;
46
+ cryptoAmount: number;
47
+ network: string;
48
+ rate: number;
49
+ fees: {
50
+ network: number;
51
+ provider: number;
52
+ total: number;
53
+ };
54
+ estimatedTime: string;
55
+ }
56
+
57
+ export interface OnrampTransaction {
58
+ id: string;
59
+ status: 'pending' | 'processing' | 'completed' | 'failed';
60
+ fiatCurrency: string;
61
+ fiatAmount: number;
62
+ cryptoCurrency: string;
63
+ cryptoAmount?: number;
64
+ walletAddress: string;
65
+ txHash?: string;
66
+ provider: string;
67
+ }
68
+
69
+ // ===== Default Values =====
70
+
71
+ const DEFAULT_FIATS = ['USD', 'EUR', 'GBP', 'CNY', 'JPY', 'KRW', 'CAD', 'AUD'];
72
+ const DEFAULT_CRYPTOS = ['USDT', 'USDC', 'ETH', 'BTC', 'MATIC', 'BNB', 'AVAX', 'SOL'];
73
+ const DEFAULT_NETWORKS = ['ethereum', 'polygon', 'arbitrum', 'optimism', 'base', 'bsc', 'avalanche'];
74
+
75
+ const NETWORK_DISPLAY_NAMES: Record<string, string> = {
76
+ ethereum: 'Ethereum',
77
+ polygon: 'Polygon',
78
+ arbitrum: 'Arbitrum',
79
+ optimism: 'Optimism',
80
+ base: 'Base',
81
+ bsc: 'BNB Chain',
82
+ avalanche: 'Avalanche',
83
+ solana: 'Solana',
84
+ };
85
+
86
+ // Approximate rates for quote estimation
87
+ const CRYPTO_RATES: Record<string, number> = {
88
+ ETH: 3500,
89
+ BTC: 95000,
90
+ USDT: 1,
91
+ USDC: 1,
92
+ MATIC: 0.85,
93
+ BNB: 650,
94
+ AVAX: 42,
95
+ SOL: 200,
96
+ DAI: 1,
97
+ ARB: 1.2,
98
+ OP: 2.5,
99
+ };
100
+
101
+ // ===== Styles =====
102
+
103
+ const containerStyle: React.CSSProperties = {
104
+ display: 'flex',
105
+ flexDirection: 'column',
106
+ gap: '16px',
107
+ padding: '24px',
108
+ borderRadius: '16px',
109
+ border: '1px solid',
110
+ maxWidth: '420px',
111
+ };
112
+
113
+ const headerStyle: React.CSSProperties = {
114
+ display: 'flex',
115
+ justifyContent: 'space-between',
116
+ alignItems: 'center',
117
+ marginBottom: '8px',
118
+ };
119
+
120
+ const selectStyle: React.CSSProperties = {
121
+ padding: '12px 16px',
122
+ borderRadius: '12px',
123
+ fontSize: '16px',
124
+ outline: 'none',
125
+ cursor: 'pointer',
126
+ appearance: 'none',
127
+ backgroundRepeat: 'no-repeat',
128
+ backgroundPosition: 'right 12px center',
129
+ paddingRight: '40px',
130
+ };
131
+
132
+ const inputStyle: React.CSSProperties = {
133
+ padding: '12px 16px',
134
+ borderRadius: '12px',
135
+ fontSize: '18px',
136
+ fontWeight: 500,
137
+ outline: 'none',
138
+ width: '100%',
139
+ boxSizing: 'border-box',
140
+ };
141
+
142
+ const buttonStyle: React.CSSProperties = {
143
+ width: '100%',
144
+ padding: '16px',
145
+ border: 'none',
146
+ borderRadius: '12px',
147
+ fontSize: '16px',
148
+ fontWeight: 600,
149
+ cursor: 'pointer',
150
+ transition: 'background-color 0.2s',
151
+ };
152
+
153
+ const rowStyle: React.CSSProperties = {
154
+ display: 'flex',
155
+ gap: '12px',
156
+ alignItems: 'center',
157
+ };
158
+
159
+ const quoteBoxStyle: React.CSSProperties = {
160
+ padding: '16px',
161
+ borderRadius: '12px',
162
+ fontSize: '14px',
163
+ };
164
+
165
+ // ===== Component =====
166
+
167
+ export function OneOnrampWidget({
168
+ defaultFiat = 'USD',
169
+ defaultCrypto = 'USDT',
170
+ defaultAmount = 100,
171
+ defaultNetwork = 'base',
172
+ supportedFiats = DEFAULT_FIATS,
173
+ supportedCryptos = DEFAULT_CRYPTOS,
174
+ supportedNetworks = DEFAULT_NETWORKS,
175
+ email,
176
+ country,
177
+ theme = 'dark',
178
+ accentColor = '#10b981',
179
+ className,
180
+ style,
181
+ mode = 'form',
182
+ embedHeight = 600,
183
+ onSuccess,
184
+ onError,
185
+ onClose,
186
+ onQuoteUpdate,
187
+ }: OneOnrampWidgetProps) {
188
+ const account = useActiveAccount();
189
+ const walletAddress = account?.address;
190
+
191
+ // Form state
192
+ const [fiatCurrency, setFiatCurrency] = useState(defaultFiat);
193
+ const [fiatAmount, setFiatAmount] = useState(defaultAmount.toString());
194
+ const [cryptoCurrency, setCryptoCurrency] = useState(defaultCrypto);
195
+ const [network, setNetwork] = useState(defaultNetwork);
196
+ const [isLoading, setIsLoading] = useState(false);
197
+ const [error, setError] = useState<string | null>(null);
198
+ const [quote, setQuote] = useState<OnrampQuote | null>(null);
199
+ const [widgetUrl, setWidgetUrl] = useState<string | null>(null);
200
+
201
+ const isDark = theme === 'dark';
202
+
203
+ // Calculate estimated quote
204
+ const calculateQuote = useCallback((): OnrampQuote | null => {
205
+ const amount = parseFloat(fiatAmount);
206
+ if (isNaN(amount) || amount <= 0) return null;
207
+
208
+ const cryptoRate = CRYPTO_RATES[cryptoCurrency.toUpperCase()] || 1;
209
+ const providerFee = amount * 0.035; // ~3.5% provider fee
210
+ const networkFee = cryptoCurrency === 'ETH' ? 5 : 1;
211
+ const totalFee = providerFee + networkFee;
212
+ const netAmount = amount - totalFee;
213
+ const cryptoAmount = netAmount / cryptoRate;
214
+
215
+ return {
216
+ fiatCurrency,
217
+ fiatAmount: amount,
218
+ cryptoCurrency,
219
+ cryptoAmount: parseFloat(cryptoAmount.toFixed(8)),
220
+ network,
221
+ rate: cryptoRate,
222
+ fees: {
223
+ network: networkFee,
224
+ provider: providerFee,
225
+ total: totalFee,
226
+ },
227
+ estimatedTime: '5-15 minutes',
228
+ };
229
+ }, [fiatAmount, fiatCurrency, cryptoCurrency, network]);
230
+
231
+ // Update quote when inputs change
232
+ useEffect(() => {
233
+ const newQuote = calculateQuote();
234
+ setQuote(newQuote);
235
+ if (newQuote) {
236
+ onQuoteUpdate?.(newQuote);
237
+ }
238
+ }, [calculateQuote, onQuoteUpdate]);
239
+
240
+ // Build Onramper widget URL
241
+ const buildWidgetUrl = useCallback(() => {
242
+ if (!walletAddress) return null;
243
+
244
+ const baseUrl = 'https://buy.onramper.com';
245
+ const params = new URLSearchParams();
246
+
247
+ // API key from engine or env
248
+ const apiKey = process.env.NEXT_PUBLIC_ONRAMPER_API_KEY || '';
249
+ if (apiKey) {
250
+ params.append('apiKey', apiKey);
251
+ }
252
+
253
+ // Wallet addresses (same address for all EVM chains - Smart Account)
254
+ params.append('wallets', `ETH:${walletAddress},MATIC:${walletAddress},BNB:${walletAddress},AVAX:${walletAddress}`);
255
+ params.append('networkWallets',
256
+ `ethereum:${walletAddress},polygon:${walletAddress},arbitrum:${walletAddress},` +
257
+ `optimism:${walletAddress},base:${walletAddress},bsc:${walletAddress},avalanche:${walletAddress}`
258
+ );
259
+
260
+ // Default selections
261
+ params.append('defaultCrypto', cryptoCurrency);
262
+ params.append('defaultFiat', fiatCurrency);
263
+ params.append('defaultAmount', fiatAmount);
264
+ params.append('mode', 'buy');
265
+
266
+ // Network filter
267
+ if (network) {
268
+ params.append('onlyCryptoNetworks', network);
269
+ }
270
+
271
+ // User info
272
+ if (email) params.append('email', email);
273
+ if (country) params.append('country', country);
274
+
275
+ // UI customization
276
+ params.append('color', accentColor.replace('#', ''));
277
+ params.append('darkMode', isDark ? 'true' : 'false');
278
+ params.append('hideTopBar', 'false');
279
+ params.append('isInAppBrowser', 'true');
280
+
281
+ // Tracking
282
+ params.append('partnerContext', `onesdk_${Date.now()}`);
283
+ params.append('popularCryptos', 'USDT,USDC,ETH,BTC');
284
+
285
+ return `${baseUrl}?${params.toString()}`;
286
+ }, [walletAddress, cryptoCurrency, fiatCurrency, fiatAmount, network, email, country, accentColor, isDark]);
287
+
288
+ // Handle buy button click
289
+ const handleBuy = async () => {
290
+ if (!walletAddress) {
291
+ setError('Please connect your wallet first');
292
+ return;
293
+ }
294
+
295
+ const amount = parseFloat(fiatAmount);
296
+ if (isNaN(amount) || amount < 10) {
297
+ setError('Minimum amount is $10');
298
+ return;
299
+ }
300
+
301
+ setIsLoading(true);
302
+ setError(null);
303
+
304
+ try {
305
+ // Try to create session via Engine API first
306
+ const engineUrl = getEngineUrl();
307
+ const response = await fetch(`${engineUrl}/v1/fiat/onramp`, {
308
+ method: 'POST',
309
+ headers: { 'Content-Type': 'application/json' },
310
+ body: JSON.stringify({
311
+ walletAddress,
312
+ fiatCurrency,
313
+ fiatAmount: amount,
314
+ cryptoCurrency,
315
+ network,
316
+ email,
317
+ }),
318
+ });
319
+
320
+ if (response.ok) {
321
+ const data = await response.json();
322
+ if (data.success && data.data?.widgetUrl) {
323
+ setWidgetUrl(data.data.widgetUrl);
324
+ if (mode === 'popup') {
325
+ window.open(data.data.widgetUrl, '_blank', 'width=450,height=700');
326
+ }
327
+ return;
328
+ }
329
+ }
330
+
331
+ // Fallback to direct Onramper URL
332
+ const directUrl = buildWidgetUrl();
333
+ if (directUrl) {
334
+ setWidgetUrl(directUrl);
335
+ if (mode === 'popup') {
336
+ window.open(directUrl, '_blank', 'width=450,height=700');
337
+ }
338
+ } else {
339
+ throw new Error('Failed to generate widget URL');
340
+ }
341
+ } catch (err) {
342
+ const error = err instanceof Error ? err : new Error('Failed to start purchase');
343
+ setError(error.message);
344
+ onError?.(error);
345
+ } finally {
346
+ setIsLoading(false);
347
+ }
348
+ };
349
+
350
+ // Handle closing embed
351
+ const handleCloseEmbed = () => {
352
+ setWidgetUrl(null);
353
+ onClose?.();
354
+ };
355
+
356
+ // Theme colors
357
+ const bgColor = isDark ? '#1f2937' : '#ffffff';
358
+ const borderColor = isDark ? '#374151' : '#e5e7eb';
359
+ const textColor = isDark ? '#ffffff' : '#111827';
360
+ const mutedColor = isDark ? '#9ca3af' : '#6b7280';
361
+ const inputBg = isDark ? '#374151' : '#f3f4f6';
362
+ const quoteBg = isDark ? '#374151' : '#f3f4f6';
363
+
364
+ // Embed mode - show iframe
365
+ if (mode === 'embed' && widgetUrl) {
366
+ return (
367
+ <div className={className} style={{ ...style }}>
368
+ <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '8px' }}>
369
+ <button
370
+ onClick={handleCloseEmbed}
371
+ style={{
372
+ background: 'none',
373
+ border: 'none',
374
+ color: mutedColor,
375
+ cursor: 'pointer',
376
+ fontSize: '14px',
377
+ }}
378
+ >
379
+ Close
380
+ </button>
381
+ </div>
382
+ <iframe
383
+ src={widgetUrl}
384
+ width="100%"
385
+ height={embedHeight}
386
+ style={{ border: 'none', borderRadius: '16px' }}
387
+ allow="payment; clipboard-read; clipboard-write"
388
+ />
389
+ </div>
390
+ );
391
+ }
392
+
393
+ // Form mode
394
+ return (
395
+ <div
396
+ className={className}
397
+ style={{
398
+ ...containerStyle,
399
+ backgroundColor: bgColor,
400
+ borderColor,
401
+ ...style,
402
+ }}
403
+ >
404
+ {/* Header */}
405
+ <div style={headerStyle}>
406
+ <h3 style={{ color: textColor, margin: 0, fontSize: '18px', fontWeight: 600 }}>
407
+ Buy Crypto
408
+ </h3>
409
+ <span style={{ color: mutedColor, fontSize: '12px' }}>
410
+ Powered by Onramper
411
+ </span>
412
+ </div>
413
+
414
+ {/* Fiat Input */}
415
+ <div>
416
+ <label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
417
+ You Pay
418
+ </label>
419
+ <div style={rowStyle}>
420
+ <input
421
+ type="number"
422
+ value={fiatAmount}
423
+ onChange={(e) => setFiatAmount(e.target.value)}
424
+ placeholder="0"
425
+ min="10"
426
+ style={{
427
+ ...inputStyle,
428
+ flex: 1,
429
+ backgroundColor: inputBg,
430
+ border: `1px solid ${borderColor}`,
431
+ color: textColor,
432
+ }}
433
+ />
434
+ <select
435
+ value={fiatCurrency}
436
+ onChange={(e) => setFiatCurrency(e.target.value)}
437
+ style={{
438
+ ...selectStyle,
439
+ backgroundColor: inputBg,
440
+ border: `1px solid ${borderColor}`,
441
+ color: textColor,
442
+ minWidth: '100px',
443
+ 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")`,
444
+ backgroundSize: '16px',
445
+ }}
446
+ >
447
+ {supportedFiats.map((fiat) => (
448
+ <option key={fiat} value={fiat}>{fiat}</option>
449
+ ))}
450
+ </select>
451
+ </div>
452
+ </div>
453
+
454
+ {/* Crypto Output */}
455
+ <div>
456
+ <label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
457
+ You Receive
458
+ </label>
459
+ <div style={rowStyle}>
460
+ <div
461
+ style={{
462
+ ...inputStyle,
463
+ flex: 1,
464
+ backgroundColor: inputBg,
465
+ border: `1px solid ${borderColor}`,
466
+ color: textColor,
467
+ display: 'flex',
468
+ alignItems: 'center',
469
+ }}
470
+ >
471
+ <span style={{ color: quote ? textColor : mutedColor }}>
472
+ {quote ? `~${quote.cryptoAmount.toFixed(6)}` : '0.00'}
473
+ </span>
474
+ </div>
475
+ <select
476
+ value={cryptoCurrency}
477
+ onChange={(e) => setCryptoCurrency(e.target.value)}
478
+ style={{
479
+ ...selectStyle,
480
+ backgroundColor: inputBg,
481
+ border: `1px solid ${borderColor}`,
482
+ color: textColor,
483
+ minWidth: '100px',
484
+ 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")`,
485
+ backgroundSize: '16px',
486
+ }}
487
+ >
488
+ {supportedCryptos.map((crypto) => (
489
+ <option key={crypto} value={crypto}>{crypto}</option>
490
+ ))}
491
+ </select>
492
+ </div>
493
+ </div>
494
+
495
+ {/* Network Selection */}
496
+ <div>
497
+ <label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
498
+ Network
499
+ </label>
500
+ <select
501
+ value={network}
502
+ onChange={(e) => setNetwork(e.target.value)}
503
+ style={{
504
+ ...selectStyle,
505
+ width: '100%',
506
+ backgroundColor: inputBg,
507
+ border: `1px solid ${borderColor}`,
508
+ color: textColor,
509
+ 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")`,
510
+ backgroundSize: '16px',
511
+ }}
512
+ >
513
+ {supportedNetworks.map((net) => (
514
+ <option key={net} value={net}>
515
+ {NETWORK_DISPLAY_NAMES[net] || net}
516
+ </option>
517
+ ))}
518
+ </select>
519
+ </div>
520
+
521
+ {/* Quote Summary */}
522
+ {quote && (
523
+ <div style={{ ...quoteBoxStyle, backgroundColor: quoteBg }}>
524
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
525
+ <span style={{ color: mutedColor }}>Rate</span>
526
+ <span style={{ color: textColor }}>
527
+ 1 {cryptoCurrency} = ${quote.rate.toLocaleString()}
528
+ </span>
529
+ </div>
530
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
531
+ <span style={{ color: mutedColor }}>Fees</span>
532
+ <span style={{ color: textColor }}>
533
+ ~${quote.fees.total.toFixed(2)}
534
+ </span>
535
+ </div>
536
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
537
+ <span style={{ color: mutedColor }}>Est. Time</span>
538
+ <span style={{ color: textColor }}>{quote.estimatedTime}</span>
539
+ </div>
540
+ </div>
541
+ )}
542
+
543
+ {/* Error Message */}
544
+ {error && (
545
+ <div style={{
546
+ color: '#ef4444',
547
+ fontSize: '14px',
548
+ padding: '12px',
549
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
550
+ borderRadius: '8px',
551
+ }}>
552
+ {error}
553
+ </div>
554
+ )}
555
+
556
+ {/* Buy Button */}
557
+ <button
558
+ onClick={handleBuy}
559
+ disabled={isLoading || !walletAddress}
560
+ style={{
561
+ ...buttonStyle,
562
+ backgroundColor: isLoading || !walletAddress ? '#6b7280' : accentColor,
563
+ color: '#ffffff',
564
+ cursor: isLoading || !walletAddress ? 'not-allowed' : 'pointer',
565
+ }}
566
+ >
567
+ {isLoading ? 'Loading...' : !walletAddress ? 'Connect Wallet' : `Buy ${cryptoCurrency}`}
568
+ </button>
569
+
570
+ {/* Wallet Address Display */}
571
+ {walletAddress && (
572
+ <div style={{ textAlign: 'center', color: mutedColor, fontSize: '12px' }}>
573
+ Receiving to: {walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
574
+ </div>
575
+ )}
576
+ </div>
577
+ );
578
+ }
579
+
580
+ // ===== Presets =====
581
+
582
+ export function OneBuyUSDTWidget(props: Omit<OneOnrampWidgetProps, 'defaultCrypto'>) {
583
+ return <OneOnrampWidget {...props} defaultCrypto="USDT" />;
584
+ }
585
+
586
+ export function OneBuyUSDCWidget(props: Omit<OneOnrampWidgetProps, 'defaultCrypto'>) {
587
+ return <OneOnrampWidget {...props} defaultCrypto="USDC" />;
588
+ }
589
+
590
+ export function OneBuyETHWidget(props: Omit<OneOnrampWidgetProps, 'defaultCrypto'>) {
591
+ return <OneOnrampWidget {...props} defaultCrypto="ETH" />;
592
+ }
593
+
594
+ export function OneBuyBTCWidget(props: Omit<OneOnrampWidgetProps, 'defaultCrypto'>) {
595
+ return <OneOnrampWidget {...props} defaultCrypto="BTC" />;
596
+ }