@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,160 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { PayEmbed, type PayEmbedProps } from 'thirdweb/react';
5
+ import { base, ethereum, polygon, arbitrum } from 'thirdweb/chains';
6
+ import { useThirdwebClient } from '../providers/ThirdwebProvider';
7
+
8
+ // ===== Types =====
9
+
10
+ export type PayMode = 'fund_wallet' | 'direct_payment' | 'transaction';
11
+
12
+ export interface OnePayWidgetProps {
13
+ // Mode configuration
14
+ mode?: PayMode;
15
+
16
+ // For direct_payment mode
17
+ recipientAddress?: string;
18
+ amount?: string;
19
+ tokenAddress?: string;
20
+ chainId?: number;
21
+
22
+ // For transaction mode
23
+ transaction?: any; // PreparedTransaction from thirdweb
24
+
25
+ // Appearance
26
+ theme?: 'light' | 'dark';
27
+ className?: string;
28
+ style?: React.CSSProperties;
29
+
30
+ // Payment options
31
+ buyWithCrypto?: boolean;
32
+ buyWithFiat?: boolean;
33
+
34
+ // Supported tokens (token addresses per chain)
35
+ supportedTokens?: {
36
+ [chainId: number]: string[];
37
+ };
38
+
39
+ // Callbacks
40
+ onSuccess?: (result: any) => void;
41
+ onError?: (error: Error) => void;
42
+ onCancel?: () => void;
43
+ }
44
+
45
+ // ===== Default Configuration =====
46
+
47
+ const DEFAULT_SUPPORTED_TOKENS = {
48
+ [base.id]: [
49
+ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
50
+ ],
51
+ [ethereum.id]: [
52
+ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
53
+ '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT on Ethereum
54
+ ],
55
+ [polygon.id]: [
56
+ '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // USDC on Polygon
57
+ ],
58
+ [arbitrum.id]: [
59
+ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum
60
+ ],
61
+ };
62
+
63
+ // ===== Component =====
64
+
65
+ export function OnePayWidget({
66
+ mode = 'fund_wallet',
67
+ recipientAddress,
68
+ amount,
69
+ tokenAddress,
70
+ chainId,
71
+ transaction,
72
+ theme = 'dark',
73
+ className,
74
+ style,
75
+ buyWithCrypto = true,
76
+ buyWithFiat = true,
77
+ supportedTokens = DEFAULT_SUPPORTED_TOKENS,
78
+ onSuccess,
79
+ onError,
80
+ onCancel,
81
+ }: OnePayWidgetProps) {
82
+ const client = useThirdwebClient();
83
+
84
+ // Build pay options based on mode
85
+ let payOptions: PayEmbedProps['payOptions'];
86
+
87
+ switch (mode) {
88
+ case 'direct_payment':
89
+ if (!recipientAddress) {
90
+ console.warn('OnePayWidget: recipientAddress required for direct_payment mode');
91
+ }
92
+ payOptions = {
93
+ mode: 'direct_payment',
94
+ paymentInfo: {
95
+ sellerAddress: recipientAddress || '',
96
+ amount: amount || '0',
97
+ chain: chainId ? { id: chainId } as any : base,
98
+ token: tokenAddress ? { address: tokenAddress } as any : undefined,
99
+ },
100
+ buyWithCrypto: buyWithCrypto ? {} : false,
101
+ buyWithFiat: buyWithFiat ? {} : false,
102
+ };
103
+ break;
104
+
105
+ case 'transaction':
106
+ if (!transaction) {
107
+ console.warn('OnePayWidget: transaction required for transaction mode');
108
+ }
109
+ payOptions = {
110
+ mode: 'transaction',
111
+ transaction,
112
+ buyWithCrypto: buyWithCrypto ? {} : false,
113
+ buyWithFiat: buyWithFiat ? {} : false,
114
+ };
115
+ break;
116
+
117
+ case 'fund_wallet':
118
+ default:
119
+ payOptions = {
120
+ mode: 'fund_wallet',
121
+ buyWithCrypto: buyWithCrypto ? {} : false,
122
+ buyWithFiat: buyWithFiat ? {} : false,
123
+ };
124
+ break;
125
+ }
126
+
127
+ return (
128
+ <div className={className} style={style}>
129
+ <PayEmbed
130
+ client={client}
131
+ payOptions={payOptions}
132
+ theme={theme}
133
+ supportedTokens={supportedTokens as any}
134
+ />
135
+ </div>
136
+ );
137
+ }
138
+
139
+ // ===== Presets =====
140
+
141
+ export function OneFundWalletWidget(props: Omit<OnePayWidgetProps, 'mode'>) {
142
+ return <OnePayWidget {...props} mode="fund_wallet" />;
143
+ }
144
+
145
+ export function OneDirectPayWidget(
146
+ props: Omit<OnePayWidgetProps, 'mode'> & {
147
+ recipientAddress: string;
148
+ amount: string;
149
+ }
150
+ ) {
151
+ return <OnePayWidget {...props} mode="direct_payment" />;
152
+ }
153
+
154
+ export function OneCryptoOnlyPayWidget(props: Omit<OnePayWidgetProps, 'buyWithFiat'>) {
155
+ return <OnePayWidget {...props} buyWithFiat={false} />;
156
+ }
157
+
158
+ export function OneFiatOnlyPayWidget(props: Omit<OnePayWidgetProps, 'buyWithCrypto'>) {
159
+ return <OnePayWidget {...props} buyWithCrypto={false} />;
160
+ }
@@ -0,0 +1,272 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect } from 'react';
4
+ import { useActiveAccount } from 'thirdweb/react';
5
+ import type { Chain } from 'thirdweb/chains';
6
+ import { base, ethereum, polygon, arbitrum } from 'thirdweb/chains';
7
+
8
+ // ===== Types =====
9
+
10
+ export interface OneReceiveWidgetProps {
11
+ // Display options
12
+ chains?: Chain[];
13
+ defaultChain?: Chain;
14
+ showChainSelector?: boolean;
15
+ showCopyButton?: boolean;
16
+ showQRCode?: boolean;
17
+
18
+ // Appearance
19
+ theme?: 'light' | 'dark';
20
+ className?: string;
21
+ style?: React.CSSProperties;
22
+ qrSize?: number;
23
+
24
+ // Callbacks
25
+ onCopy?: (address: string) => void;
26
+ onChainChange?: (chain: Chain) => void;
27
+ }
28
+
29
+ // ===== QR Code Generator (Simple SVG-based) =====
30
+
31
+ function generateQRMatrix(data: string): boolean[][] {
32
+ // Simple QR-like pattern generator (for demo - in production use a proper QR library)
33
+ const size = 21;
34
+ const matrix: boolean[][] = Array(size).fill(null).map(() => Array(size).fill(false));
35
+
36
+ // Add finder patterns
37
+ const addFinderPattern = (x: number, y: number) => {
38
+ for (let i = 0; i < 7; i++) {
39
+ for (let j = 0; j < 7; j++) {
40
+ if (i === 0 || i === 6 || j === 0 || j === 6 || (i >= 2 && i <= 4 && j >= 2 && j <= 4)) {
41
+ matrix[y + i][x + j] = true;
42
+ }
43
+ }
44
+ }
45
+ };
46
+
47
+ addFinderPattern(0, 0);
48
+ addFinderPattern(14, 0);
49
+ addFinderPattern(0, 14);
50
+
51
+ // Add data pattern based on address hash
52
+ let hash = 0;
53
+ for (let i = 0; i < data.length; i++) {
54
+ hash = ((hash << 5) - hash) + data.charCodeAt(i);
55
+ hash = hash & hash;
56
+ }
57
+
58
+ for (let i = 8; i < 13; i++) {
59
+ for (let j = 8; j < 13; j++) {
60
+ matrix[i][j] = ((hash >> ((i * 21 + j) % 32)) & 1) === 1;
61
+ }
62
+ }
63
+
64
+ return matrix;
65
+ }
66
+
67
+ function QRCode({ data, size = 200, isDark = true }: { data: string; size?: number; isDark?: boolean }) {
68
+ const matrix = generateQRMatrix(data);
69
+ const cellSize = size / matrix.length;
70
+
71
+ return (
72
+ <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
73
+ <rect width={size} height={size} fill={isDark ? '#ffffff' : '#ffffff'} rx="8" />
74
+ {matrix.map((row, y) =>
75
+ row.map((cell, x) =>
76
+ cell ? (
77
+ <rect
78
+ key={`${x}-${y}`}
79
+ x={x * cellSize}
80
+ y={y * cellSize}
81
+ width={cellSize}
82
+ height={cellSize}
83
+ fill={isDark ? '#111827' : '#111827'}
84
+ />
85
+ ) : null
86
+ )
87
+ )}
88
+ </svg>
89
+ );
90
+ }
91
+
92
+ // ===== Styles =====
93
+
94
+ const containerStyle: React.CSSProperties = {
95
+ display: 'flex',
96
+ flexDirection: 'column',
97
+ alignItems: 'center',
98
+ gap: '20px',
99
+ padding: '24px',
100
+ backgroundColor: '#1f2937',
101
+ borderRadius: '16px',
102
+ border: '1px solid #374151',
103
+ };
104
+
105
+ const addressStyle: React.CSSProperties = {
106
+ display: 'flex',
107
+ alignItems: 'center',
108
+ gap: '8px',
109
+ padding: '12px 16px',
110
+ backgroundColor: '#374151',
111
+ borderRadius: '12px',
112
+ maxWidth: '100%',
113
+ };
114
+
115
+ const buttonStyle: React.CSSProperties = {
116
+ padding: '8px 16px',
117
+ backgroundColor: '#10b981',
118
+ color: '#ffffff',
119
+ border: 'none',
120
+ borderRadius: '8px',
121
+ cursor: 'pointer',
122
+ fontSize: '14px',
123
+ fontWeight: 500,
124
+ };
125
+
126
+ // ===== Chain Info =====
127
+
128
+ const CHAIN_INFO: Record<number, { name: string; icon: string }> = {
129
+ 1: { name: 'Ethereum', icon: '⟠' },
130
+ 8453: { name: 'Base', icon: '🔵' },
131
+ 137: { name: 'Polygon', icon: '🟣' },
132
+ 42161: { name: 'Arbitrum', icon: '🔷' },
133
+ 10: { name: 'Optimism', icon: '🔴' },
134
+ };
135
+
136
+ // ===== Component =====
137
+
138
+ export function OneReceiveWidget({
139
+ chains = [base, ethereum, polygon, arbitrum],
140
+ defaultChain = base,
141
+ showChainSelector = true,
142
+ showCopyButton = true,
143
+ showQRCode = true,
144
+ theme = 'dark',
145
+ className,
146
+ style,
147
+ qrSize = 200,
148
+ onCopy,
149
+ onChainChange,
150
+ }: OneReceiveWidgetProps) {
151
+ const account = useActiveAccount();
152
+ const [selectedChain, setSelectedChain] = useState<Chain>(defaultChain);
153
+ const [copied, setCopied] = useState(false);
154
+
155
+ const address = account?.address || '';
156
+ const isDark = theme === 'dark';
157
+
158
+ const handleCopy = async () => {
159
+ if (!address) return;
160
+
161
+ try {
162
+ await navigator.clipboard.writeText(address);
163
+ setCopied(true);
164
+ onCopy?.(address);
165
+ setTimeout(() => setCopied(false), 2000);
166
+ } catch (err) {
167
+ console.error('Failed to copy:', err);
168
+ }
169
+ };
170
+
171
+ const handleChainChange = (chain: Chain) => {
172
+ setSelectedChain(chain);
173
+ onChainChange?.(chain);
174
+ };
175
+
176
+ const shortenAddress = (addr: string) => {
177
+ if (!addr) return '';
178
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
179
+ };
180
+
181
+ if (!account) {
182
+ return (
183
+ <div className={className} style={{ ...style, textAlign: 'center', padding: '24px', color: isDark ? '#9ca3af' : '#6b7280' }}>
184
+ Connect your wallet to receive funds
185
+ </div>
186
+ );
187
+ }
188
+
189
+ return (
190
+ <div
191
+ className={className}
192
+ style={{
193
+ ...containerStyle,
194
+ backgroundColor: isDark ? '#1f2937' : '#ffffff',
195
+ border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,
196
+ ...style,
197
+ }}
198
+ >
199
+ <h3 style={{ margin: 0, color: isDark ? '#ffffff' : '#111827', fontSize: '18px', fontWeight: 600 }}>
200
+ Receive
201
+ </h3>
202
+
203
+ {/* Chain Selector */}
204
+ {showChainSelector && chains.length > 1 && (
205
+ <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', justifyContent: 'center' }}>
206
+ {chains.map(chain => (
207
+ <button
208
+ key={chain.id}
209
+ onClick={() => handleChainChange(chain)}
210
+ style={{
211
+ padding: '8px 12px',
212
+ backgroundColor: selectedChain.id === chain.id ? '#10b981' : (isDark ? '#374151' : '#e5e7eb'),
213
+ border: 'none',
214
+ borderRadius: '8px',
215
+ color: selectedChain.id === chain.id ? '#ffffff' : (isDark ? '#9ca3af' : '#6b7280'),
216
+ cursor: 'pointer',
217
+ fontSize: '12px',
218
+ }}
219
+ >
220
+ {CHAIN_INFO[chain.id]?.icon} {CHAIN_INFO[chain.id]?.name}
221
+ </button>
222
+ ))}
223
+ </div>
224
+ )}
225
+
226
+ {/* QR Code */}
227
+ {showQRCode && (
228
+ <div style={{ padding: '16px', backgroundColor: '#ffffff', borderRadius: '12px' }}>
229
+ <QRCode data={address} size={qrSize} isDark={isDark} />
230
+ </div>
231
+ )}
232
+
233
+ {/* Address Display */}
234
+ <div style={{ ...addressStyle, backgroundColor: isDark ? '#374151' : '#f3f4f6', width: '100%' }}>
235
+ <span style={{
236
+ color: isDark ? '#ffffff' : '#111827',
237
+ fontSize: '14px',
238
+ fontFamily: 'monospace',
239
+ flex: 1,
240
+ overflow: 'hidden',
241
+ textOverflow: 'ellipsis',
242
+ }}>
243
+ {address}
244
+ </span>
245
+ </div>
246
+
247
+ {/* Copy Button */}
248
+ {showCopyButton && (
249
+ <button
250
+ onClick={handleCopy}
251
+ style={{
252
+ ...buttonStyle,
253
+ backgroundColor: copied ? '#059669' : '#10b981',
254
+ width: '100%',
255
+ }}
256
+ >
257
+ {copied ? '✓ Copied!' : 'Copy Address'}
258
+ </button>
259
+ )}
260
+
261
+ {/* Warning */}
262
+ <p style={{
263
+ margin: 0,
264
+ color: isDark ? '#9ca3af' : '#6b7280',
265
+ fontSize: '12px',
266
+ textAlign: 'center',
267
+ }}>
268
+ Only send {CHAIN_INFO[selectedChain.id]?.name || 'compatible'} assets to this address
269
+ </p>
270
+ </div>
271
+ );
272
+ }
@@ -0,0 +1,248 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useCallback } from 'react';
4
+ import { useSendTransaction, useActiveAccount } from 'thirdweb/react';
5
+ import { prepareTransaction, toWei } from 'thirdweb';
6
+ import type { Chain } from 'thirdweb/chains';
7
+ import { base } from 'thirdweb/chains';
8
+ import { useThirdwebClient } from '../providers/ThirdwebProvider';
9
+
10
+ // ===== Types =====
11
+
12
+ export interface OneSendWidgetProps {
13
+ // Default values
14
+ defaultRecipient?: string;
15
+ defaultAmount?: string;
16
+ defaultChain?: Chain;
17
+
18
+ // Token config (if sending ERC20)
19
+ tokenAddress?: string;
20
+ tokenSymbol?: string;
21
+ tokenDecimals?: number;
22
+
23
+ // Appearance
24
+ theme?: 'light' | 'dark';
25
+ className?: string;
26
+ style?: React.CSSProperties;
27
+
28
+ // Callbacks
29
+ onSuccess?: (txHash: string) => void;
30
+ onError?: (error: Error) => void;
31
+ onCancel?: () => void;
32
+ }
33
+
34
+ // ===== Styles =====
35
+
36
+ const containerStyle: React.CSSProperties = {
37
+ display: 'flex',
38
+ flexDirection: 'column',
39
+ gap: '16px',
40
+ padding: '24px',
41
+ backgroundColor: '#1f2937',
42
+ borderRadius: '16px',
43
+ border: '1px solid #374151',
44
+ };
45
+
46
+ const inputStyle: React.CSSProperties = {
47
+ width: '100%',
48
+ padding: '12px 16px',
49
+ backgroundColor: '#374151',
50
+ border: '1px solid #4b5563',
51
+ borderRadius: '12px',
52
+ color: '#ffffff',
53
+ fontSize: '16px',
54
+ outline: 'none',
55
+ };
56
+
57
+ const labelStyle: React.CSSProperties = {
58
+ color: '#9ca3af',
59
+ fontSize: '14px',
60
+ marginBottom: '4px',
61
+ };
62
+
63
+ const buttonStyle: React.CSSProperties = {
64
+ width: '100%',
65
+ padding: '14px',
66
+ backgroundColor: '#10b981',
67
+ color: '#ffffff',
68
+ border: 'none',
69
+ borderRadius: '12px',
70
+ fontSize: '16px',
71
+ fontWeight: 600,
72
+ cursor: 'pointer',
73
+ };
74
+
75
+ const buttonDisabledStyle: React.CSSProperties = {
76
+ ...buttonStyle,
77
+ backgroundColor: '#6b7280',
78
+ cursor: 'not-allowed',
79
+ };
80
+
81
+ // ===== Component =====
82
+
83
+ export function OneSendWidget({
84
+ defaultRecipient = '',
85
+ defaultAmount = '',
86
+ defaultChain = base,
87
+ tokenAddress,
88
+ tokenSymbol = 'ETH',
89
+ tokenDecimals = 18,
90
+ theme = 'dark',
91
+ className,
92
+ style,
93
+ onSuccess,
94
+ onError,
95
+ onCancel,
96
+ }: OneSendWidgetProps) {
97
+ const client = useThirdwebClient();
98
+ const account = useActiveAccount();
99
+ const { mutate: sendTransaction, isPending } = useSendTransaction();
100
+
101
+ const [recipient, setRecipient] = useState(defaultRecipient);
102
+ const [amount, setAmount] = useState(defaultAmount);
103
+ const [error, setError] = useState<string | null>(null);
104
+
105
+ const isValidAddress = (address: string) => /^0x[a-fA-F0-9]{40}$/.test(address);
106
+ const isValidAmount = (amt: string) => !isNaN(parseFloat(amt)) && parseFloat(amt) > 0;
107
+
108
+ const canSend = isValidAddress(recipient) && isValidAmount(amount) && !isPending;
109
+
110
+ const handleSend = useCallback(async () => {
111
+ if (!account || !canSend) return;
112
+
113
+ setError(null);
114
+
115
+ try {
116
+ let tx;
117
+
118
+ if (tokenAddress) {
119
+ // ERC20 transfer
120
+ const amountInWei = BigInt(Math.floor(parseFloat(amount) * Math.pow(10, tokenDecimals)));
121
+ const transferData = `0xa9059cbb${recipient.slice(2).padStart(64, '0')}${amountInWei.toString(16).padStart(64, '0')}` as `0x${string}`;
122
+
123
+ tx = prepareTransaction({
124
+ to: tokenAddress,
125
+ data: transferData,
126
+ chain: defaultChain,
127
+ client,
128
+ });
129
+ } else {
130
+ // Native token transfer
131
+ tx = prepareTransaction({
132
+ to: recipient as `0x${string}`,
133
+ value: toWei(amount),
134
+ chain: defaultChain,
135
+ client,
136
+ });
137
+ }
138
+
139
+ sendTransaction(tx, {
140
+ onSuccess: (result) => {
141
+ onSuccess?.(result.transactionHash);
142
+ setRecipient('');
143
+ setAmount('');
144
+ },
145
+ onError: (err) => {
146
+ setError(err.message);
147
+ onError?.(err);
148
+ },
149
+ });
150
+ } catch (err) {
151
+ const error = err instanceof Error ? err : new Error('Transaction failed');
152
+ setError(error.message);
153
+ onError?.(error);
154
+ }
155
+ }, [account, canSend, recipient, amount, tokenAddress, tokenDecimals, defaultChain, client, sendTransaction, onSuccess, onError]);
156
+
157
+ const isDark = theme === 'dark';
158
+
159
+ return (
160
+ <div
161
+ className={className}
162
+ style={{
163
+ ...containerStyle,
164
+ backgroundColor: isDark ? '#1f2937' : '#ffffff',
165
+ border: `1px solid ${isDark ? '#374151' : '#e5e7eb'}`,
166
+ ...style,
167
+ }}
168
+ >
169
+ <h3 style={{ color: isDark ? '#ffffff' : '#111827', margin: 0, fontSize: '18px', fontWeight: 600 }}>
170
+ Send {tokenSymbol}
171
+ </h3>
172
+
173
+ <div>
174
+ <label style={{ ...labelStyle, color: isDark ? '#9ca3af' : '#6b7280' }}>Recipient Address</label>
175
+ <input
176
+ type="text"
177
+ value={recipient}
178
+ onChange={(e) => setRecipient(e.target.value)}
179
+ placeholder="0x..."
180
+ style={{
181
+ ...inputStyle,
182
+ backgroundColor: isDark ? '#374151' : '#f3f4f6',
183
+ color: isDark ? '#ffffff' : '#111827',
184
+ border: `1px solid ${isDark ? '#4b5563' : '#d1d5db'}`,
185
+ }}
186
+ />
187
+ </div>
188
+
189
+ <div>
190
+ <label style={{ ...labelStyle, color: isDark ? '#9ca3af' : '#6b7280' }}>Amount ({tokenSymbol})</label>
191
+ <input
192
+ type="number"
193
+ value={amount}
194
+ onChange={(e) => setAmount(e.target.value)}
195
+ placeholder="0.0"
196
+ min="0"
197
+ step="any"
198
+ style={{
199
+ ...inputStyle,
200
+ backgroundColor: isDark ? '#374151' : '#f3f4f6',
201
+ color: isDark ? '#ffffff' : '#111827',
202
+ border: `1px solid ${isDark ? '#4b5563' : '#d1d5db'}`,
203
+ }}
204
+ />
205
+ </div>
206
+
207
+ {error && (
208
+ <div style={{ color: '#ef4444', fontSize: '14px', padding: '8px 12px', backgroundColor: 'rgba(239, 68, 68, 0.1)', borderRadius: '8px' }}>
209
+ {error}
210
+ </div>
211
+ )}
212
+
213
+ <div style={{ display: 'flex', gap: '12px' }}>
214
+ {onCancel && (
215
+ <button
216
+ onClick={onCancel}
217
+ style={{
218
+ ...buttonStyle,
219
+ flex: 1,
220
+ backgroundColor: 'transparent',
221
+ border: `1px solid ${isDark ? '#4b5563' : '#d1d5db'}`,
222
+ color: isDark ? '#9ca3af' : '#6b7280',
223
+ }}
224
+ >
225
+ Cancel
226
+ </button>
227
+ )}
228
+ <button
229
+ onClick={handleSend}
230
+ disabled={!canSend}
231
+ style={canSend ? { ...buttonStyle, flex: 1 } : { ...buttonDisabledStyle, flex: 1 }}
232
+ >
233
+ {isPending ? 'Sending...' : `Send ${tokenSymbol}`}
234
+ </button>
235
+ </div>
236
+ </div>
237
+ );
238
+ }
239
+
240
+ // ===== Presets =====
241
+
242
+ export function OneSendETHWidget(props: Omit<OneSendWidgetProps, 'tokenAddress' | 'tokenSymbol' | 'tokenDecimals'>) {
243
+ return <OneSendWidget {...props} tokenSymbol="ETH" tokenDecimals={18} />;
244
+ }
245
+
246
+ export function OneSendUSDCWidget(props: Omit<OneSendWidgetProps, 'tokenSymbol' | 'tokenDecimals'> & { tokenAddress: string }) {
247
+ return <OneSendWidget {...props} tokenSymbol="USDC" tokenDecimals={6} />;
248
+ }