@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.
- package/.turbo/turbo-build.log +0 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/dist/config/index.d.mts +74 -0
- package/dist/config/index.d.ts +74 -0
- package/dist/config/index.js +242 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +224 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/engine-5ndtBaCr.d.ts +1039 -0
- package/dist/engine-CrlhH0nw.d.mts +1039 -0
- package/dist/hooks/index.d.mts +56 -0
- package/dist/hooks/index.d.ts +56 -0
- package/dist/hooks/index.js +1360 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +1356 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +356 -0
- package/dist/index.d.ts +356 -0
- package/dist/index.js +5068 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4949 -0
- package/dist/index.mjs.map +1 -0
- package/dist/price-CgqXPnT3.d.ts +13 -0
- package/dist/price-ClbLHHjv.d.mts +13 -0
- package/dist/providers/index.d.mts +121 -0
- package/dist/providers/index.d.ts +121 -0
- package/dist/providers/index.js +1642 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/index.mjs +1600 -0
- package/dist/providers/index.mjs.map +1 -0
- package/dist/react-native.d.mts +120 -0
- package/dist/react-native.d.ts +120 -0
- package/dist/react-native.js +1792 -0
- package/dist/react-native.js.map +1 -0
- package/dist/react-native.mjs +1755 -0
- package/dist/react-native.mjs.map +1 -0
- package/dist/services/index.d.mts +85 -0
- package/dist/services/index.d.ts +85 -0
- package/dist/services/index.js +1466 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/index.mjs +1458 -0
- package/dist/services/index.mjs.map +1 -0
- package/dist/types/index.d.mts +759 -0
- package/dist/types/index.d.ts +759 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +3 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +36 -0
- package/dist/utils/index.d.ts +36 -0
- package/dist/utils/index.js +164 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +142 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +101 -0
- package/src/components/OneConnectButton.tsx +143 -0
- package/src/components/OneNFTGallery.tsx +324 -0
- package/src/components/OneOfframpWidget.tsx +660 -0
- package/src/components/OneOnrampWidget.tsx +596 -0
- package/src/components/OnePayWidget.tsx +160 -0
- package/src/components/OneReceiveWidget.tsx +272 -0
- package/src/components/OneSendWidget.tsx +248 -0
- package/src/components/OneSwapWidget.tsx +715 -0
- package/src/components/OneTransactionButton.tsx +150 -0
- package/src/components/OneWalletBalance.tsx +354 -0
- package/src/components/index.ts +24 -0
- package/src/config/index.ts +299 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTokenPrice.ts +162 -0
- package/src/hooks/useWalletBalance.ts +98 -0
- package/src/index.ts +193 -0
- package/src/providers/OneProvider.tsx +452 -0
- package/src/providers/ThirdwebProvider.tsx +203 -0
- package/src/providers/index.ts +26 -0
- package/src/react-native.ts +378 -0
- package/src/services/engine.ts +1854 -0
- package/src/services/index.ts +30 -0
- package/src/services/price.ts +164 -0
- package/src/services/supabase.ts +180 -0
- package/src/types/index.ts +887 -0
- package/src/utils/index.ts +200 -0
- package/tsconfig.json +22 -0
- 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
|
+
}
|