@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,660 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useActiveAccount, useWalletBalance } from 'thirdweb/react';
|
|
5
|
+
import { base } from 'thirdweb/chains';
|
|
6
|
+
import { useThirdwebClient } from '../providers/ThirdwebProvider';
|
|
7
|
+
import { getEngineUrl } from '../config';
|
|
8
|
+
|
|
9
|
+
// ===== Types =====
|
|
10
|
+
|
|
11
|
+
export interface OneOfframpWidgetProps {
|
|
12
|
+
// Configuration
|
|
13
|
+
defaultFiat?: string;
|
|
14
|
+
defaultCrypto?: string;
|
|
15
|
+
defaultAmount?: string;
|
|
16
|
+
defaultNetwork?: string;
|
|
17
|
+
|
|
18
|
+
// Currency options
|
|
19
|
+
supportedFiats?: string[];
|
|
20
|
+
supportedCryptos?: string[];
|
|
21
|
+
supportedNetworks?: string[];
|
|
22
|
+
|
|
23
|
+
// User info (pre-fill)
|
|
24
|
+
email?: string;
|
|
25
|
+
country?: string;
|
|
26
|
+
|
|
27
|
+
// Appearance
|
|
28
|
+
theme?: 'light' | 'dark';
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: React.CSSProperties;
|
|
32
|
+
|
|
33
|
+
// Display mode
|
|
34
|
+
mode?: 'form' | 'embed' | 'popup';
|
|
35
|
+
embedHeight?: number;
|
|
36
|
+
|
|
37
|
+
// Callbacks
|
|
38
|
+
onSuccess?: (transaction: OfframpTransaction) => void;
|
|
39
|
+
onError?: (error: Error) => void;
|
|
40
|
+
onClose?: () => void;
|
|
41
|
+
onQuoteUpdate?: (quote: OfframpQuote) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface OfframpQuote {
|
|
45
|
+
cryptoCurrency: string;
|
|
46
|
+
cryptoAmount: number;
|
|
47
|
+
fiatCurrency: string;
|
|
48
|
+
fiatAmount: number;
|
|
49
|
+
network: string;
|
|
50
|
+
rate: number;
|
|
51
|
+
fees: {
|
|
52
|
+
network: number;
|
|
53
|
+
provider: number;
|
|
54
|
+
total: number;
|
|
55
|
+
};
|
|
56
|
+
estimatedTime: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface OfframpTransaction {
|
|
60
|
+
id: string;
|
|
61
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
62
|
+
cryptoCurrency: string;
|
|
63
|
+
cryptoAmount: number;
|
|
64
|
+
fiatCurrency: string;
|
|
65
|
+
fiatAmount?: number;
|
|
66
|
+
walletAddress: string;
|
|
67
|
+
txHash?: string;
|
|
68
|
+
provider: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ===== Default Values =====
|
|
72
|
+
|
|
73
|
+
const DEFAULT_FIATS = ['USD', 'EUR', 'GBP', 'CNY', 'JPY', 'KRW', 'CAD', 'AUD'];
|
|
74
|
+
const DEFAULT_CRYPTOS = ['USDT', 'USDC', 'ETH', 'BTC', 'MATIC', 'BNB'];
|
|
75
|
+
const DEFAULT_NETWORKS = ['ethereum', 'polygon', 'arbitrum', 'optimism', 'base', 'bsc', 'avalanche'];
|
|
76
|
+
|
|
77
|
+
const NETWORK_DISPLAY_NAMES: Record<string, string> = {
|
|
78
|
+
ethereum: 'Ethereum',
|
|
79
|
+
polygon: 'Polygon',
|
|
80
|
+
arbitrum: 'Arbitrum',
|
|
81
|
+
optimism: 'Optimism',
|
|
82
|
+
base: 'Base',
|
|
83
|
+
bsc: 'BNB Chain',
|
|
84
|
+
avalanche: 'Avalanche',
|
|
85
|
+
solana: 'Solana',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Approximate rates for quote estimation
|
|
89
|
+
const CRYPTO_RATES: Record<string, number> = {
|
|
90
|
+
ETH: 3500,
|
|
91
|
+
BTC: 95000,
|
|
92
|
+
USDT: 1,
|
|
93
|
+
USDC: 1,
|
|
94
|
+
MATIC: 0.85,
|
|
95
|
+
BNB: 650,
|
|
96
|
+
AVAX: 42,
|
|
97
|
+
SOL: 200,
|
|
98
|
+
DAI: 1,
|
|
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
|
+
const balanceStyle: React.CSSProperties = {
|
|
166
|
+
display: 'flex',
|
|
167
|
+
justifyContent: 'space-between',
|
|
168
|
+
fontSize: '12px',
|
|
169
|
+
marginTop: '4px',
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// ===== Component =====
|
|
173
|
+
|
|
174
|
+
export function OneOfframpWidget({
|
|
175
|
+
defaultFiat = 'USD',
|
|
176
|
+
defaultCrypto = 'USDT',
|
|
177
|
+
defaultAmount = '',
|
|
178
|
+
defaultNetwork = 'base',
|
|
179
|
+
supportedFiats = DEFAULT_FIATS,
|
|
180
|
+
supportedCryptos = DEFAULT_CRYPTOS,
|
|
181
|
+
supportedNetworks = DEFAULT_NETWORKS,
|
|
182
|
+
email,
|
|
183
|
+
country,
|
|
184
|
+
theme = 'dark',
|
|
185
|
+
accentColor = '#ef4444', // Red for sell
|
|
186
|
+
className,
|
|
187
|
+
style,
|
|
188
|
+
mode = 'form',
|
|
189
|
+
embedHeight = 600,
|
|
190
|
+
onSuccess,
|
|
191
|
+
onError,
|
|
192
|
+
onClose,
|
|
193
|
+
onQuoteUpdate,
|
|
194
|
+
}: OneOfframpWidgetProps) {
|
|
195
|
+
const client = useThirdwebClient();
|
|
196
|
+
const account = useActiveAccount();
|
|
197
|
+
const walletAddress = account?.address;
|
|
198
|
+
|
|
199
|
+
// Get balance for selected crypto
|
|
200
|
+
const { data: balanceData } = useWalletBalance({
|
|
201
|
+
client,
|
|
202
|
+
chain: base,
|
|
203
|
+
address: walletAddress,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Form state
|
|
207
|
+
const [cryptoCurrency, setCryptoCurrency] = useState(defaultCrypto);
|
|
208
|
+
const [cryptoAmount, setCryptoAmount] = useState(defaultAmount);
|
|
209
|
+
const [fiatCurrency, setFiatCurrency] = useState(defaultFiat);
|
|
210
|
+
const [network, setNetwork] = useState(defaultNetwork);
|
|
211
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
212
|
+
const [error, setError] = useState<string | null>(null);
|
|
213
|
+
const [quote, setQuote] = useState<OfframpQuote | null>(null);
|
|
214
|
+
const [widgetUrl, setWidgetUrl] = useState<string | null>(null);
|
|
215
|
+
|
|
216
|
+
const isDark = theme === 'dark';
|
|
217
|
+
|
|
218
|
+
// Calculate estimated quote
|
|
219
|
+
const calculateQuote = useCallback((): OfframpQuote | null => {
|
|
220
|
+
const amount = parseFloat(cryptoAmount);
|
|
221
|
+
if (isNaN(amount) || amount <= 0) return null;
|
|
222
|
+
|
|
223
|
+
const cryptoRate = CRYPTO_RATES[cryptoCurrency.toUpperCase()] || 1;
|
|
224
|
+
const grossAmount = amount * cryptoRate;
|
|
225
|
+
const providerFee = grossAmount * 0.025; // ~2.5% provider fee for offramp
|
|
226
|
+
const networkFee = cryptoCurrency === 'ETH' ? 5 : 1;
|
|
227
|
+
const totalFee = providerFee + networkFee;
|
|
228
|
+
const netAmount = grossAmount - totalFee;
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
cryptoCurrency,
|
|
232
|
+
cryptoAmount: amount,
|
|
233
|
+
fiatCurrency,
|
|
234
|
+
fiatAmount: parseFloat(netAmount.toFixed(2)),
|
|
235
|
+
network,
|
|
236
|
+
rate: cryptoRate,
|
|
237
|
+
fees: {
|
|
238
|
+
network: networkFee,
|
|
239
|
+
provider: providerFee,
|
|
240
|
+
total: totalFee,
|
|
241
|
+
},
|
|
242
|
+
estimatedTime: '1-3 business days',
|
|
243
|
+
};
|
|
244
|
+
}, [cryptoAmount, cryptoCurrency, fiatCurrency, network]);
|
|
245
|
+
|
|
246
|
+
// Update quote when inputs change
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
const newQuote = calculateQuote();
|
|
249
|
+
setQuote(newQuote);
|
|
250
|
+
if (newQuote) {
|
|
251
|
+
onQuoteUpdate?.(newQuote);
|
|
252
|
+
}
|
|
253
|
+
}, [calculateQuote, onQuoteUpdate]);
|
|
254
|
+
|
|
255
|
+
// Build Onramper widget URL (sell mode)
|
|
256
|
+
const buildWidgetUrl = useCallback(() => {
|
|
257
|
+
if (!walletAddress) return null;
|
|
258
|
+
|
|
259
|
+
const baseUrl = 'https://buy.onramper.com';
|
|
260
|
+
const params = new URLSearchParams();
|
|
261
|
+
|
|
262
|
+
// API key from engine or env
|
|
263
|
+
const apiKey = process.env.NEXT_PUBLIC_ONRAMPER_API_KEY || '';
|
|
264
|
+
if (apiKey) {
|
|
265
|
+
params.append('apiKey', apiKey);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Wallet addresses
|
|
269
|
+
params.append('wallets', `ETH:${walletAddress},MATIC:${walletAddress},BNB:${walletAddress}`);
|
|
270
|
+
params.append('networkWallets',
|
|
271
|
+
`ethereum:${walletAddress},polygon:${walletAddress},arbitrum:${walletAddress},` +
|
|
272
|
+
`optimism:${walletAddress},base:${walletAddress},bsc:${walletAddress}`
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// SELL MODE
|
|
276
|
+
params.append('mode', 'sell');
|
|
277
|
+
params.append('defaultCrypto', cryptoCurrency);
|
|
278
|
+
params.append('defaultFiat', fiatCurrency);
|
|
279
|
+
if (cryptoAmount) {
|
|
280
|
+
params.append('sellDefaultAmount', cryptoAmount);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Network filter
|
|
284
|
+
if (network) {
|
|
285
|
+
params.append('onlyCryptoNetworks', network);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// User info
|
|
289
|
+
if (email) params.append('email', email);
|
|
290
|
+
if (country) params.append('country', country);
|
|
291
|
+
|
|
292
|
+
// UI customization
|
|
293
|
+
params.append('color', accentColor.replace('#', ''));
|
|
294
|
+
params.append('darkMode', isDark ? 'true' : 'false');
|
|
295
|
+
params.append('hideTopBar', 'false');
|
|
296
|
+
params.append('isInAppBrowser', 'true');
|
|
297
|
+
|
|
298
|
+
// Tracking
|
|
299
|
+
params.append('partnerContext', `onesdk_sell_${Date.now()}`);
|
|
300
|
+
|
|
301
|
+
return `${baseUrl}?${params.toString()}`;
|
|
302
|
+
}, [walletAddress, cryptoCurrency, fiatCurrency, cryptoAmount, network, email, country, accentColor, isDark]);
|
|
303
|
+
|
|
304
|
+
// Handle sell button click
|
|
305
|
+
const handleSell = async () => {
|
|
306
|
+
if (!walletAddress) {
|
|
307
|
+
setError('Please connect your wallet first');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const amount = parseFloat(cryptoAmount);
|
|
312
|
+
if (isNaN(amount) || amount <= 0) {
|
|
313
|
+
setError('Please enter a valid amount');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check balance
|
|
318
|
+
if (balanceData && cryptoCurrency === 'ETH') {
|
|
319
|
+
const balance = parseFloat(balanceData.displayValue);
|
|
320
|
+
if (amount > balance) {
|
|
321
|
+
setError(`Insufficient balance. You have ${balance.toFixed(4)} ${cryptoCurrency}`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
setIsLoading(true);
|
|
327
|
+
setError(null);
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
// Try Engine API first
|
|
331
|
+
const engineUrl = getEngineUrl();
|
|
332
|
+
const response = await fetch(`${engineUrl}/v1/fiat/offramp`, {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: { 'Content-Type': 'application/json' },
|
|
335
|
+
body: JSON.stringify({
|
|
336
|
+
walletAddress,
|
|
337
|
+
cryptoCurrency,
|
|
338
|
+
cryptoAmount: amount,
|
|
339
|
+
fiatCurrency,
|
|
340
|
+
network,
|
|
341
|
+
email,
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (response.ok) {
|
|
346
|
+
const data = await response.json();
|
|
347
|
+
if (data.success && data.data?.widgetUrl) {
|
|
348
|
+
setWidgetUrl(data.data.widgetUrl);
|
|
349
|
+
if (mode === 'popup') {
|
|
350
|
+
window.open(data.data.widgetUrl, '_blank', 'width=450,height=700');
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Fallback to direct URL
|
|
357
|
+
const directUrl = buildWidgetUrl();
|
|
358
|
+
if (directUrl) {
|
|
359
|
+
setWidgetUrl(directUrl);
|
|
360
|
+
if (mode === 'popup') {
|
|
361
|
+
window.open(directUrl, '_blank', 'width=450,height=700');
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
throw new Error('Failed to generate widget URL');
|
|
365
|
+
}
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const error = err instanceof Error ? err : new Error('Failed to start sale');
|
|
368
|
+
setError(error.message);
|
|
369
|
+
onError?.(error);
|
|
370
|
+
} finally {
|
|
371
|
+
setIsLoading(false);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Use max balance
|
|
376
|
+
const handleUseMax = () => {
|
|
377
|
+
if (balanceData && cryptoCurrency === 'ETH') {
|
|
378
|
+
const balance = parseFloat(balanceData.displayValue);
|
|
379
|
+
// Leave some for gas
|
|
380
|
+
const maxAmount = Math.max(0, balance - 0.005);
|
|
381
|
+
setCryptoAmount(maxAmount.toFixed(6));
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Handle closing embed
|
|
386
|
+
const handleCloseEmbed = () => {
|
|
387
|
+
setWidgetUrl(null);
|
|
388
|
+
onClose?.();
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Theme colors
|
|
392
|
+
const bgColor = isDark ? '#1f2937' : '#ffffff';
|
|
393
|
+
const borderColor = isDark ? '#374151' : '#e5e7eb';
|
|
394
|
+
const textColor = isDark ? '#ffffff' : '#111827';
|
|
395
|
+
const mutedColor = isDark ? '#9ca3af' : '#6b7280';
|
|
396
|
+
const inputBg = isDark ? '#374151' : '#f3f4f6';
|
|
397
|
+
const quoteBg = isDark ? '#374151' : '#f3f4f6';
|
|
398
|
+
|
|
399
|
+
// Embed mode - show iframe
|
|
400
|
+
if (mode === 'embed' && widgetUrl) {
|
|
401
|
+
return (
|
|
402
|
+
<div className={className} style={{ ...style }}>
|
|
403
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '8px' }}>
|
|
404
|
+
<button
|
|
405
|
+
onClick={handleCloseEmbed}
|
|
406
|
+
style={{
|
|
407
|
+
background: 'none',
|
|
408
|
+
border: 'none',
|
|
409
|
+
color: mutedColor,
|
|
410
|
+
cursor: 'pointer',
|
|
411
|
+
fontSize: '14px',
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
Close
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
417
|
+
<iframe
|
|
418
|
+
src={widgetUrl}
|
|
419
|
+
width="100%"
|
|
420
|
+
height={embedHeight}
|
|
421
|
+
style={{ border: 'none', borderRadius: '16px' }}
|
|
422
|
+
allow="payment; clipboard-read; clipboard-write"
|
|
423
|
+
/>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Form mode
|
|
429
|
+
return (
|
|
430
|
+
<div
|
|
431
|
+
className={className}
|
|
432
|
+
style={{
|
|
433
|
+
...containerStyle,
|
|
434
|
+
backgroundColor: bgColor,
|
|
435
|
+
borderColor,
|
|
436
|
+
...style,
|
|
437
|
+
}}
|
|
438
|
+
>
|
|
439
|
+
{/* Header */}
|
|
440
|
+
<div style={headerStyle}>
|
|
441
|
+
<h3 style={{ color: textColor, margin: 0, fontSize: '18px', fontWeight: 600 }}>
|
|
442
|
+
Sell Crypto
|
|
443
|
+
</h3>
|
|
444
|
+
<span style={{ color: mutedColor, fontSize: '12px' }}>
|
|
445
|
+
Cash out to bank
|
|
446
|
+
</span>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
{/* Crypto Input */}
|
|
450
|
+
<div>
|
|
451
|
+
<label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
|
|
452
|
+
You Sell
|
|
453
|
+
</label>
|
|
454
|
+
<div style={rowStyle}>
|
|
455
|
+
<input
|
|
456
|
+
type="number"
|
|
457
|
+
value={cryptoAmount}
|
|
458
|
+
onChange={(e) => setCryptoAmount(e.target.value)}
|
|
459
|
+
placeholder="0.0"
|
|
460
|
+
min="0"
|
|
461
|
+
step="any"
|
|
462
|
+
style={{
|
|
463
|
+
...inputStyle,
|
|
464
|
+
flex: 1,
|
|
465
|
+
backgroundColor: inputBg,
|
|
466
|
+
border: `1px solid ${borderColor}`,
|
|
467
|
+
color: textColor,
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
470
|
+
<select
|
|
471
|
+
value={cryptoCurrency}
|
|
472
|
+
onChange={(e) => setCryptoCurrency(e.target.value)}
|
|
473
|
+
style={{
|
|
474
|
+
...selectStyle,
|
|
475
|
+
backgroundColor: inputBg,
|
|
476
|
+
border: `1px solid ${borderColor}`,
|
|
477
|
+
color: textColor,
|
|
478
|
+
minWidth: '100px',
|
|
479
|
+
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")`,
|
|
480
|
+
backgroundSize: '16px',
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
{supportedCryptos.map((crypto) => (
|
|
484
|
+
<option key={crypto} value={crypto}>{crypto}</option>
|
|
485
|
+
))}
|
|
486
|
+
</select>
|
|
487
|
+
</div>
|
|
488
|
+
{/* Balance row */}
|
|
489
|
+
{balanceData && cryptoCurrency === 'ETH' && (
|
|
490
|
+
<div style={balanceStyle}>
|
|
491
|
+
<span style={{ color: mutedColor }}>
|
|
492
|
+
Balance: {parseFloat(balanceData.displayValue).toFixed(4)} ETH
|
|
493
|
+
</span>
|
|
494
|
+
<button
|
|
495
|
+
onClick={handleUseMax}
|
|
496
|
+
style={{
|
|
497
|
+
background: 'none',
|
|
498
|
+
border: 'none',
|
|
499
|
+
color: accentColor,
|
|
500
|
+
cursor: 'pointer',
|
|
501
|
+
fontSize: '12px',
|
|
502
|
+
padding: 0,
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
Use Max
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
{/* Network Selection */}
|
|
512
|
+
<div>
|
|
513
|
+
<label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
|
|
514
|
+
Network
|
|
515
|
+
</label>
|
|
516
|
+
<select
|
|
517
|
+
value={network}
|
|
518
|
+
onChange={(e) => setNetwork(e.target.value)}
|
|
519
|
+
style={{
|
|
520
|
+
...selectStyle,
|
|
521
|
+
width: '100%',
|
|
522
|
+
backgroundColor: inputBg,
|
|
523
|
+
border: `1px solid ${borderColor}`,
|
|
524
|
+
color: textColor,
|
|
525
|
+
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")`,
|
|
526
|
+
backgroundSize: '16px',
|
|
527
|
+
}}
|
|
528
|
+
>
|
|
529
|
+
{supportedNetworks.map((net) => (
|
|
530
|
+
<option key={net} value={net}>
|
|
531
|
+
{NETWORK_DISPLAY_NAMES[net] || net}
|
|
532
|
+
</option>
|
|
533
|
+
))}
|
|
534
|
+
</select>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
{/* Fiat Output */}
|
|
538
|
+
<div>
|
|
539
|
+
<label style={{ color: mutedColor, fontSize: '14px', marginBottom: '8px', display: 'block' }}>
|
|
540
|
+
You Receive
|
|
541
|
+
</label>
|
|
542
|
+
<div style={rowStyle}>
|
|
543
|
+
<div
|
|
544
|
+
style={{
|
|
545
|
+
...inputStyle,
|
|
546
|
+
flex: 1,
|
|
547
|
+
backgroundColor: inputBg,
|
|
548
|
+
border: `1px solid ${borderColor}`,
|
|
549
|
+
color: textColor,
|
|
550
|
+
display: 'flex',
|
|
551
|
+
alignItems: 'center',
|
|
552
|
+
}}
|
|
553
|
+
>
|
|
554
|
+
<span style={{ color: quote ? textColor : mutedColor }}>
|
|
555
|
+
{quote ? `~$${quote.fiatAmount.toLocaleString()}` : '$0.00'}
|
|
556
|
+
</span>
|
|
557
|
+
</div>
|
|
558
|
+
<select
|
|
559
|
+
value={fiatCurrency}
|
|
560
|
+
onChange={(e) => setFiatCurrency(e.target.value)}
|
|
561
|
+
style={{
|
|
562
|
+
...selectStyle,
|
|
563
|
+
backgroundColor: inputBg,
|
|
564
|
+
border: `1px solid ${borderColor}`,
|
|
565
|
+
color: textColor,
|
|
566
|
+
minWidth: '100px',
|
|
567
|
+
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")`,
|
|
568
|
+
backgroundSize: '16px',
|
|
569
|
+
}}
|
|
570
|
+
>
|
|
571
|
+
{supportedFiats.map((fiat) => (
|
|
572
|
+
<option key={fiat} value={fiat}>{fiat}</option>
|
|
573
|
+
))}
|
|
574
|
+
</select>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
{/* Quote Summary */}
|
|
579
|
+
{quote && (
|
|
580
|
+
<div style={{ ...quoteBoxStyle, backgroundColor: quoteBg }}>
|
|
581
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
582
|
+
<span style={{ color: mutedColor }}>Rate</span>
|
|
583
|
+
<span style={{ color: textColor }}>
|
|
584
|
+
1 {cryptoCurrency} = ${quote.rate.toLocaleString()}
|
|
585
|
+
</span>
|
|
586
|
+
</div>
|
|
587
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
588
|
+
<span style={{ color: mutedColor }}>Fees</span>
|
|
589
|
+
<span style={{ color: textColor }}>
|
|
590
|
+
~${quote.fees.total.toFixed(2)}
|
|
591
|
+
</span>
|
|
592
|
+
</div>
|
|
593
|
+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
594
|
+
<span style={{ color: mutedColor }}>Est. Arrival</span>
|
|
595
|
+
<span style={{ color: textColor }}>{quote.estimatedTime}</span>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
)}
|
|
599
|
+
|
|
600
|
+
{/* Warning */}
|
|
601
|
+
<div style={{
|
|
602
|
+
padding: '12px',
|
|
603
|
+
backgroundColor: isDark ? 'rgba(251, 191, 36, 0.1)' : 'rgba(251, 191, 36, 0.15)',
|
|
604
|
+
borderRadius: '8px',
|
|
605
|
+
fontSize: '12px',
|
|
606
|
+
color: '#fbbf24',
|
|
607
|
+
}}>
|
|
608
|
+
Bank transfers typically take 1-3 business days. KYC verification may be required.
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
{/* Error Message */}
|
|
612
|
+
{error && (
|
|
613
|
+
<div style={{
|
|
614
|
+
color: '#ef4444',
|
|
615
|
+
fontSize: '14px',
|
|
616
|
+
padding: '12px',
|
|
617
|
+
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
618
|
+
borderRadius: '8px',
|
|
619
|
+
}}>
|
|
620
|
+
{error}
|
|
621
|
+
</div>
|
|
622
|
+
)}
|
|
623
|
+
|
|
624
|
+
{/* Sell Button */}
|
|
625
|
+
<button
|
|
626
|
+
onClick={handleSell}
|
|
627
|
+
disabled={isLoading || !walletAddress}
|
|
628
|
+
style={{
|
|
629
|
+
...buttonStyle,
|
|
630
|
+
backgroundColor: isLoading || !walletAddress ? '#6b7280' : accentColor,
|
|
631
|
+
color: '#ffffff',
|
|
632
|
+
cursor: isLoading || !walletAddress ? 'not-allowed' : 'pointer',
|
|
633
|
+
}}
|
|
634
|
+
>
|
|
635
|
+
{isLoading ? 'Loading...' : !walletAddress ? 'Connect Wallet' : `Sell ${cryptoCurrency}`}
|
|
636
|
+
</button>
|
|
637
|
+
|
|
638
|
+
{/* Wallet Address Display */}
|
|
639
|
+
{walletAddress && (
|
|
640
|
+
<div style={{ textAlign: 'center', color: mutedColor, fontSize: '12px' }}>
|
|
641
|
+
Selling from: {walletAddress.slice(0, 6)}...{walletAddress.slice(-4)}
|
|
642
|
+
</div>
|
|
643
|
+
)}
|
|
644
|
+
</div>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ===== Presets =====
|
|
649
|
+
|
|
650
|
+
export function OneSellUSDTWidget(props: Omit<OneOfframpWidgetProps, 'defaultCrypto'>) {
|
|
651
|
+
return <OneOfframpWidget {...props} defaultCrypto="USDT" />;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function OneSellUSDCWidget(props: Omit<OneOfframpWidgetProps, 'defaultCrypto'>) {
|
|
655
|
+
return <OneOfframpWidget {...props} defaultCrypto="USDC" />;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export function OneSellETHWidget(props: Omit<OneOfframpWidgetProps, 'defaultCrypto'>) {
|
|
659
|
+
return <OneOfframpWidget {...props} defaultCrypto="ETH" />;
|
|
660
|
+
}
|