@swype-org/react-sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1845 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactAuth = require('@privy-io/react-auth');
5
+ var wagmi = require('wagmi');
6
+ var chains = require('wagmi/chains');
7
+ var reactQuery = require('@tanstack/react-query');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/theme.ts
17
+ var darkTheme = {
18
+ bg: "#0a0a0a",
19
+ bgCard: "#141414",
20
+ bgInput: "#1e1e1e",
21
+ bgHover: "#252525",
22
+ bgOverlay: "rgba(0, 0, 0, 0.6)",
23
+ text: "#f5f5f5",
24
+ textSecondary: "#a1a1aa",
25
+ textMuted: "#71717a",
26
+ textInverse: "#09090b",
27
+ border: "#27272a",
28
+ borderFocus: "#3b82f6",
29
+ accent: "#3b82f6",
30
+ accentHover: "#2563eb",
31
+ accentText: "#ffffff",
32
+ success: "#22c55e",
33
+ successBg: "#052e16",
34
+ error: "#ef4444",
35
+ errorBg: "#450a0a",
36
+ shadow: "0 1px 3px rgba(0,0,0,0.4)",
37
+ shadowLg: "0 8px 32px rgba(0,0,0,0.5)",
38
+ radius: "10px",
39
+ radiusLg: "16px"
40
+ };
41
+ var lightTheme = {
42
+ bg: "#f8fafc",
43
+ bgCard: "#ffffff",
44
+ bgInput: "#f1f5f9",
45
+ bgHover: "#e2e8f0",
46
+ bgOverlay: "rgba(0, 0, 0, 0.3)",
47
+ text: "#0f172a",
48
+ textSecondary: "#475569",
49
+ textMuted: "#94a3b8",
50
+ textInverse: "#ffffff",
51
+ border: "#e2e8f0",
52
+ borderFocus: "#3b82f6",
53
+ accent: "#3b82f6",
54
+ accentHover: "#2563eb",
55
+ accentText: "#ffffff",
56
+ success: "#16a34a",
57
+ successBg: "#f0fdf4",
58
+ error: "#dc2626",
59
+ errorBg: "#fef2f2",
60
+ shadow: "0 1px 3px rgba(0,0,0,0.08)",
61
+ shadowLg: "0 8px 32px rgba(0,0,0,0.12)",
62
+ radius: "10px",
63
+ radiusLg: "16px"
64
+ };
65
+ function getTheme(mode) {
66
+ return mode === "dark" ? darkTheme : lightTheme;
67
+ }
68
+ var SWYPE_PRIVY_APP_ID = "cmlil87uv004n0ck0blwumwek";
69
+ var wagmiConfig = wagmi.createConfig({
70
+ chains: [chains.mainnet, chains.arbitrum, chains.base],
71
+ transports: {
72
+ [chains.mainnet.id]: wagmi.http(),
73
+ [chains.arbitrum.id]: wagmi.http(),
74
+ [chains.base.id]: wagmi.http()
75
+ }
76
+ });
77
+ var SwypeContext = react.createContext(null);
78
+ function SwypeProvider({
79
+ apiBaseUrl,
80
+ theme = "dark",
81
+ children
82
+ }) {
83
+ const queryClientRef = react.useRef(null);
84
+ if (!queryClientRef.current) {
85
+ queryClientRef.current = new reactQuery.QueryClient();
86
+ }
87
+ const value = react.useMemo(
88
+ () => ({
89
+ apiBaseUrl,
90
+ theme,
91
+ tokens: getTheme(theme)
92
+ }),
93
+ [apiBaseUrl, theme]
94
+ );
95
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsxRuntime.jsx(wagmi.WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsxRuntime.jsx(
96
+ reactAuth.PrivyProvider,
97
+ {
98
+ appId: SWYPE_PRIVY_APP_ID,
99
+ config: {
100
+ appearance: {
101
+ theme
102
+ }
103
+ },
104
+ children: /* @__PURE__ */ jsxRuntime.jsx(SwypeContext.Provider, { value, children })
105
+ }
106
+ ) }) });
107
+ }
108
+ function useSwypeConfig() {
109
+ const ctx = react.useContext(SwypeContext);
110
+ if (!ctx) {
111
+ throw new Error("useSwypeConfig must be used within a <SwypeProvider>");
112
+ }
113
+ return ctx;
114
+ }
115
+
116
+ // src/api.ts
117
+ var api_exports = {};
118
+ __export(api_exports, {
119
+ createTransfer: () => createTransfer,
120
+ fetchAccounts: () => fetchAccounts,
121
+ fetchAuthorizationSession: () => fetchAuthorizationSession,
122
+ fetchChains: () => fetchChains,
123
+ fetchProviders: () => fetchProviders,
124
+ fetchTransfer: () => fetchTransfer,
125
+ reportActionCompletion: () => reportActionCompletion,
126
+ updateUserConfig: () => updateUserConfig
127
+ });
128
+ async function throwApiError(res) {
129
+ const body = await res.json().catch(() => null);
130
+ const detail = body?.error ?? body;
131
+ const msg = detail?.message ?? res.statusText;
132
+ const code = detail?.code ?? String(res.status);
133
+ throw new Error(`${res.status} \u2014 ${code}: ${msg}`);
134
+ }
135
+ async function fetchProviders(apiBaseUrl, token) {
136
+ const res = await fetch(`${apiBaseUrl}/v1/providers`, {
137
+ headers: { Authorization: `Bearer ${token}` }
138
+ });
139
+ if (!res.ok) await throwApiError(res);
140
+ const data = await res.json();
141
+ return data.items;
142
+ }
143
+ async function fetchChains(apiBaseUrl, token) {
144
+ const res = await fetch(`${apiBaseUrl}/v1/chains`, {
145
+ headers: { Authorization: `Bearer ${token}` }
146
+ });
147
+ if (!res.ok) await throwApiError(res);
148
+ const data = await res.json();
149
+ return data.items;
150
+ }
151
+ async function fetchAccounts(apiBaseUrl, token) {
152
+ const res = await fetch(`${apiBaseUrl}/v1/accounts`, {
153
+ headers: { Authorization: `Bearer ${token}` }
154
+ });
155
+ if (!res.ok) await throwApiError(res);
156
+ const data = await res.json();
157
+ return data.items;
158
+ }
159
+ async function createTransfer(apiBaseUrl, token, params) {
160
+ const body = {
161
+ id: crypto.randomUUID(),
162
+ sources: [{ [params.sourceType]: params.sourceId }],
163
+ destinations: [
164
+ {
165
+ chainId: params.destination.chainId,
166
+ token: { symbol: params.destination.token.symbol },
167
+ address: params.destination.address
168
+ }
169
+ ],
170
+ amount: {
171
+ amount: params.amount,
172
+ currency: params.currency ?? "USD"
173
+ }
174
+ };
175
+ const res = await fetch(`${apiBaseUrl}/v1/transfers`, {
176
+ method: "POST",
177
+ headers: {
178
+ "Content-Type": "application/json",
179
+ Authorization: `Bearer ${token}`
180
+ },
181
+ body: JSON.stringify(body)
182
+ });
183
+ if (!res.ok) await throwApiError(res);
184
+ return await res.json();
185
+ }
186
+ async function fetchTransfer(apiBaseUrl, token, transferId) {
187
+ const res = await fetch(`${apiBaseUrl}/v1/transfers/${transferId}`, {
188
+ headers: { Authorization: `Bearer ${token}` }
189
+ });
190
+ if (!res.ok) await throwApiError(res);
191
+ return await res.json();
192
+ }
193
+ async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
194
+ const res = await fetch(
195
+ `${apiBaseUrl}/v1/authorization-sessions/${sessionId}`
196
+ );
197
+ if (!res.ok) await throwApiError(res);
198
+ return await res.json();
199
+ }
200
+ async function updateUserConfig(apiBaseUrl, token, config) {
201
+ const res = await fetch(`${apiBaseUrl}/v1/users`, {
202
+ method: "PATCH",
203
+ headers: {
204
+ "Content-Type": "application/json",
205
+ Authorization: `Bearer ${token}`
206
+ },
207
+ body: JSON.stringify({ config })
208
+ });
209
+ if (!res.ok) await throwApiError(res);
210
+ }
211
+ async function reportActionCompletion(apiBaseUrl, actionId, result) {
212
+ const res = await fetch(
213
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
214
+ {
215
+ method: "PATCH",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify({ status: "COMPLETED", result })
218
+ }
219
+ );
220
+ if (!res.ok) await throwApiError(res);
221
+ return await res.json();
222
+ }
223
+ function useTransferPolling(intervalMs = 3e3) {
224
+ const { apiBaseUrl } = useSwypeConfig();
225
+ const { getAccessToken } = reactAuth.usePrivy();
226
+ const [transfer, setTransfer] = react.useState(null);
227
+ const [error, setError] = react.useState(null);
228
+ const [isPolling, setIsPolling] = react.useState(false);
229
+ const intervalRef = react.useRef(null);
230
+ const transferIdRef = react.useRef(null);
231
+ const stopPolling = react.useCallback(() => {
232
+ if (intervalRef.current) {
233
+ clearInterval(intervalRef.current);
234
+ intervalRef.current = null;
235
+ }
236
+ setIsPolling(false);
237
+ }, []);
238
+ const poll = react.useCallback(async () => {
239
+ if (!transferIdRef.current) return;
240
+ try {
241
+ const token = await getAccessToken();
242
+ if (!token) {
243
+ setError("Could not get access token");
244
+ stopPolling();
245
+ return;
246
+ }
247
+ const t = await fetchTransfer(apiBaseUrl, token, transferIdRef.current);
248
+ setTransfer(t);
249
+ if (t.status === "COMPLETED" || t.status === "FAILED") {
250
+ stopPolling();
251
+ }
252
+ } catch (err) {
253
+ setError(err instanceof Error ? err.message : "Polling error");
254
+ stopPolling();
255
+ }
256
+ }, [apiBaseUrl, getAccessToken, stopPolling]);
257
+ const startPolling = react.useCallback(
258
+ (transferId) => {
259
+ stopPolling();
260
+ transferIdRef.current = transferId;
261
+ setIsPolling(true);
262
+ setError(null);
263
+ poll();
264
+ intervalRef.current = setInterval(poll, intervalMs);
265
+ },
266
+ [poll, intervalMs, stopPolling]
267
+ );
268
+ react.useEffect(() => () => stopPolling(), [stopPolling]);
269
+ return { transfer, error, isPolling, startPolling, stopPolling };
270
+ }
271
+ function useAuthorizationExecutor() {
272
+ const { apiBaseUrl } = useSwypeConfig();
273
+ const { address, chainId: currentChainId, isConnected } = wagmi.useAccount();
274
+ const { connectAsync, connectors } = wagmi.useConnect();
275
+ const { switchChainAsync } = wagmi.useSwitchChain();
276
+ const { signTypedDataAsync } = wagmi.useSignTypedData();
277
+ const publicClient = wagmi.usePublicClient();
278
+ const { data: walletClient } = wagmi.useWalletClient();
279
+ const [executing, setExecuting] = react.useState(false);
280
+ const [results, setResults] = react.useState([]);
281
+ const [error, setError] = react.useState(null);
282
+ const executingRef = react.useRef(false);
283
+ const executeOpenProvider = react.useCallback(
284
+ async (action) => {
285
+ try {
286
+ if (isConnected && address) {
287
+ const hexChainId2 = currentChainId ? `0x${currentChainId.toString(16)}` : void 0;
288
+ return {
289
+ actionId: action.id,
290
+ type: action.type,
291
+ status: "success",
292
+ message: `Connected. Account: ${address}, Chain: ${hexChainId2}`,
293
+ data: { accounts: [address], chainId: hexChainId2 }
294
+ };
295
+ }
296
+ const targetId = action.metadata?.wagmiConnectorId;
297
+ const connector = targetId ? connectors.find((c) => c.id === targetId) ?? connectors[0] : connectors[0];
298
+ if (!connector) {
299
+ return {
300
+ actionId: action.id,
301
+ type: action.type,
302
+ status: "error",
303
+ message: "No wallet connector found. Please install a supported wallet."
304
+ };
305
+ }
306
+ const result = await connectAsync({ connector });
307
+ const hexChainId = `0x${result.chainId.toString(16)}`;
308
+ return {
309
+ actionId: action.id,
310
+ type: action.type,
311
+ status: "success",
312
+ message: `Connected to ${connector.name}. Account: ${result.accounts[0]}, Chain: ${hexChainId}`,
313
+ data: { accounts: [...result.accounts], chainId: hexChainId }
314
+ };
315
+ } catch (err) {
316
+ return {
317
+ actionId: action.id,
318
+ type: action.type,
319
+ status: "error",
320
+ message: err instanceof Error ? err.message : "Failed to connect wallet"
321
+ };
322
+ }
323
+ },
324
+ [isConnected, address, currentChainId, connectors, connectAsync]
325
+ );
326
+ const executeSwitchChain = react.useCallback(
327
+ async (action) => {
328
+ try {
329
+ const targetChainIdHex = action.metadata?.targetChainId;
330
+ if (!targetChainIdHex) {
331
+ return {
332
+ actionId: action.id,
333
+ type: action.type,
334
+ status: "error",
335
+ message: "No targetChainId in action metadata."
336
+ };
337
+ }
338
+ const targetChainIdNum = parseInt(targetChainIdHex, 16);
339
+ await switchChainAsync({ chainId: targetChainIdNum });
340
+ const hexChainId = `0x${targetChainIdNum.toString(16)}`;
341
+ return {
342
+ actionId: action.id,
343
+ type: action.type,
344
+ status: "success",
345
+ message: `Switched to chain ${hexChainId}.`,
346
+ data: { chainId: hexChainId, switched: true }
347
+ };
348
+ } catch (err) {
349
+ return {
350
+ actionId: action.id,
351
+ type: action.type,
352
+ status: "error",
353
+ message: err instanceof Error ? err.message : "Failed to switch chain"
354
+ };
355
+ }
356
+ },
357
+ [switchChainAsync]
358
+ );
359
+ const executeApprovePermit2 = react.useCallback(
360
+ async (action) => {
361
+ try {
362
+ const tokenAddress = action.metadata?.tokenAddress;
363
+ const permit2Address = action.metadata?.permit2Address;
364
+ if (!tokenAddress || !permit2Address) {
365
+ return {
366
+ actionId: action.id,
367
+ type: action.type,
368
+ status: "error",
369
+ message: "Missing tokenAddress or permit2Address in action metadata."
370
+ };
371
+ }
372
+ if (!address) {
373
+ return {
374
+ actionId: action.id,
375
+ type: action.type,
376
+ status: "error",
377
+ message: "Wallet not connected."
378
+ };
379
+ }
380
+ const ERC20_ABI = [
381
+ {
382
+ name: "allowance",
383
+ type: "function",
384
+ stateMutability: "view",
385
+ inputs: [
386
+ { name: "owner", type: "address" },
387
+ { name: "spender", type: "address" }
388
+ ],
389
+ outputs: [{ name: "", type: "uint256" }]
390
+ },
391
+ {
392
+ name: "approve",
393
+ type: "function",
394
+ stateMutability: "nonpayable",
395
+ inputs: [
396
+ { name: "spender", type: "address" },
397
+ { name: "amount", type: "uint256" }
398
+ ],
399
+ outputs: [{ name: "", type: "bool" }]
400
+ }
401
+ ];
402
+ if (publicClient) {
403
+ const currentAllowance = await publicClient.readContract({
404
+ address: tokenAddress,
405
+ abi: ERC20_ABI,
406
+ functionName: "allowance",
407
+ args: [address, permit2Address]
408
+ });
409
+ if (currentAllowance > 0n) {
410
+ return {
411
+ actionId: action.id,
412
+ type: action.type,
413
+ status: "success",
414
+ message: `Permit2 already approved (allowance: ${currentAllowance.toString()}). Skipped.`,
415
+ data: { skipped: true, existingAllowance: currentAllowance.toString() }
416
+ };
417
+ }
418
+ }
419
+ if (!walletClient) {
420
+ return {
421
+ actionId: action.id,
422
+ type: action.type,
423
+ status: "error",
424
+ message: "Wallet client not available."
425
+ };
426
+ }
427
+ const MAX_UINT256 = 2n ** 256n - 1n;
428
+ const txHash = await walletClient.writeContract({
429
+ address: tokenAddress,
430
+ abi: ERC20_ABI,
431
+ functionName: "approve",
432
+ args: [permit2Address, MAX_UINT256]
433
+ });
434
+ if (publicClient) {
435
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
436
+ }
437
+ return {
438
+ actionId: action.id,
439
+ type: action.type,
440
+ status: "success",
441
+ message: `Permit2 approved. Tx: ${txHash}`,
442
+ data: { txHash, approved: true }
443
+ };
444
+ } catch (err) {
445
+ return {
446
+ actionId: action.id,
447
+ type: action.type,
448
+ status: "error",
449
+ message: err instanceof Error ? err.message : "Failed to approve Permit2"
450
+ };
451
+ }
452
+ },
453
+ [address, publicClient, walletClient]
454
+ );
455
+ const executeSignPermit2 = react.useCallback(
456
+ async (action) => {
457
+ try {
458
+ const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
459
+ const PERMIT2_ALLOWANCE_ABI = [
460
+ {
461
+ name: "allowance",
462
+ type: "function",
463
+ stateMutability: "view",
464
+ inputs: [
465
+ { name: "owner", type: "address" },
466
+ { name: "token", type: "address" },
467
+ { name: "spender", type: "address" }
468
+ ],
469
+ outputs: [
470
+ { name: "amount", type: "uint160" },
471
+ { name: "expiration", type: "uint48" },
472
+ { name: "nonce", type: "uint48" }
473
+ ]
474
+ }
475
+ ];
476
+ const spenderAddress = action.metadata?.spenderAddress;
477
+ const tokenAddress = action.metadata?.tokenAddress;
478
+ const metadataChainId = action.metadata?.chainId;
479
+ const metadataAmount = action.metadata?.amount;
480
+ if (!spenderAddress || !tokenAddress) {
481
+ return {
482
+ actionId: action.id,
483
+ type: action.type,
484
+ status: "error",
485
+ message: "Missing spenderAddress or tokenAddress in action metadata."
486
+ };
487
+ }
488
+ if (publicClient && address && metadataAmount) {
489
+ try {
490
+ const [existingAmount, existingExpiration] = await publicClient.readContract({
491
+ address: PERMIT2_ADDRESS,
492
+ abi: PERMIT2_ALLOWANCE_ABI,
493
+ functionName: "allowance",
494
+ args: [address, tokenAddress, spenderAddress]
495
+ });
496
+ const requiredSmallestUnit = BigInt(
497
+ Math.ceil(metadataAmount * 1e6)
498
+ );
499
+ const now = Math.floor(Date.now() / 1e3);
500
+ if (existingAmount >= requiredSmallestUnit && existingExpiration > now) {
501
+ return {
502
+ actionId: action.id,
503
+ type: action.type,
504
+ status: "success",
505
+ message: `Permit2 allowance already sufficient (${existingAmount.toString()}). Skipped.`,
506
+ data: { skipped: true, existingAllowance: existingAmount.toString() }
507
+ };
508
+ }
509
+ } catch {
510
+ }
511
+ }
512
+ const metadataAllowance = action.metadata?.allowance;
513
+ const permitAmount = metadataAllowance ? BigInt(Math.ceil(metadataAllowance * 1e6)) : metadataAmount ? BigInt(Math.ceil(metadataAmount * 1e6)) : BigInt(5e6);
514
+ const permit2ChainId = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
515
+ const nonce = Number(action.metadata?.nonce ?? 0);
516
+ const sigDeadline = BigInt(Math.floor(Date.now() / 1e3) + 3600);
517
+ const expiration = Math.floor(Date.now() / 1e3) + 30 * 24 * 60 * 60;
518
+ const signature = await signTypedDataAsync({
519
+ domain: {
520
+ name: "Permit2",
521
+ chainId: permit2ChainId,
522
+ verifyingContract: PERMIT2_ADDRESS
523
+ },
524
+ types: {
525
+ PermitSingle: [
526
+ { name: "details", type: "PermitDetails" },
527
+ { name: "spender", type: "address" },
528
+ { name: "sigDeadline", type: "uint256" }
529
+ ],
530
+ PermitDetails: [
531
+ { name: "token", type: "address" },
532
+ { name: "amount", type: "uint160" },
533
+ { name: "expiration", type: "uint48" },
534
+ { name: "nonce", type: "uint48" }
535
+ ]
536
+ },
537
+ primaryType: "PermitSingle",
538
+ message: {
539
+ details: {
540
+ token: tokenAddress,
541
+ amount: permitAmount,
542
+ expiration,
543
+ nonce
544
+ },
545
+ spender: spenderAddress,
546
+ sigDeadline
547
+ }
548
+ });
549
+ return {
550
+ actionId: action.id,
551
+ type: action.type,
552
+ status: "success",
553
+ message: "Permit2 allowance signature obtained.",
554
+ data: {
555
+ signature,
556
+ signer: address,
557
+ nonce: nonce.toString(),
558
+ sigDeadline: sigDeadline.toString(),
559
+ expiration: expiration.toString(),
560
+ amount: permitAmount.toString()
561
+ }
562
+ };
563
+ } catch (err) {
564
+ return {
565
+ actionId: action.id,
566
+ type: action.type,
567
+ status: "error",
568
+ message: err instanceof Error ? err.message : "Failed to sign Permit2"
569
+ };
570
+ }
571
+ },
572
+ [address, currentChainId, publicClient, signTypedDataAsync]
573
+ );
574
+ const executeAction = react.useCallback(
575
+ async (action) => {
576
+ switch (action.type) {
577
+ case "OPEN_PROVIDER":
578
+ return executeOpenProvider(action);
579
+ case "SWITCH_CHAIN":
580
+ return executeSwitchChain(action);
581
+ case "APPROVE_PERMIT_2":
582
+ return executeApprovePermit2(action);
583
+ case "SIGN_PERMIT2":
584
+ return executeSignPermit2(action);
585
+ default:
586
+ return {
587
+ actionId: action.id,
588
+ type: action.type,
589
+ status: "error",
590
+ message: `Unsupported action type: ${action.type}`
591
+ };
592
+ }
593
+ },
594
+ [executeOpenProvider, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
595
+ );
596
+ const executeSession = react.useCallback(
597
+ async (transfer) => {
598
+ if (executingRef.current) return;
599
+ executingRef.current = true;
600
+ if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
601
+ setError("No authorization sessions available.");
602
+ executingRef.current = false;
603
+ return;
604
+ }
605
+ const sessionId = transfer.authorizationSessions[0].id;
606
+ setExecuting(true);
607
+ setError(null);
608
+ setResults([]);
609
+ try {
610
+ let currentSession = await fetchAuthorizationSession(apiBaseUrl, sessionId);
611
+ const allResults = [];
612
+ const completedActionIds = /* @__PURE__ */ new Set();
613
+ let pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
614
+ while (pendingActions.length > 0) {
615
+ const action = pendingActions[0];
616
+ if (completedActionIds.has(action.id)) break;
617
+ const result = await executeAction(action);
618
+ if (result.status === "error") {
619
+ allResults.push(result);
620
+ setResults([...allResults]);
621
+ setError(result.message);
622
+ break;
623
+ }
624
+ completedActionIds.add(action.id);
625
+ const updatedSession = await reportActionCompletion(
626
+ apiBaseUrl,
627
+ action.id,
628
+ result.data ?? {}
629
+ );
630
+ currentSession = updatedSession;
631
+ pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
632
+ if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
633
+ const chainResults = [result];
634
+ let chainBroken = false;
635
+ while (pendingActions.length > 0) {
636
+ const nextAction = pendingActions[0];
637
+ const nextResult = await executeAction(nextAction);
638
+ if (nextResult.status === "error") {
639
+ chainResults.push(nextResult);
640
+ allResults.push(...chainResults);
641
+ setResults([...allResults]);
642
+ setError(nextResult.message);
643
+ chainBroken = true;
644
+ break;
645
+ }
646
+ completedActionIds.add(nextAction.id);
647
+ const nextSession = await reportActionCompletion(
648
+ apiBaseUrl,
649
+ nextAction.id,
650
+ nextResult.data ?? {}
651
+ );
652
+ currentSession = nextSession;
653
+ chainResults.push(nextResult);
654
+ pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
655
+ }
656
+ if (chainBroken) break;
657
+ allResults.push(...chainResults);
658
+ setResults([...allResults]);
659
+ continue;
660
+ }
661
+ allResults.push(result);
662
+ setResults([...allResults]);
663
+ }
664
+ } catch (err) {
665
+ setError(err instanceof Error ? err.message : "Authorization failed");
666
+ } finally {
667
+ setExecuting(false);
668
+ executingRef.current = false;
669
+ }
670
+ },
671
+ [apiBaseUrl, executeAction]
672
+ );
673
+ return { executing, results, error, executeSession };
674
+ }
675
+ function Spinner({ size = 40, label }) {
676
+ const { tokens } = useSwypeConfig();
677
+ return /* @__PURE__ */ jsxRuntime.jsxs(
678
+ "div",
679
+ {
680
+ style: {
681
+ display: "flex",
682
+ flexDirection: "column",
683
+ alignItems: "center",
684
+ gap: "12px"
685
+ },
686
+ children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx(
688
+ "div",
689
+ {
690
+ style: {
691
+ width: size,
692
+ height: size,
693
+ border: `3px solid ${tokens.border}`,
694
+ borderTopColor: tokens.accent,
695
+ borderRadius: "50%",
696
+ animation: "swype-spin 0.8s linear infinite"
697
+ }
698
+ }
699
+ ),
700
+ label && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: tokens.textSecondary, fontSize: "0.875rem", margin: 0 }, children: label }),
701
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
702
+ @keyframes swype-spin {
703
+ to { transform: rotate(360deg); }
704
+ }
705
+ ` })
706
+ ]
707
+ }
708
+ );
709
+ }
710
+ function ProviderCard({ provider, selected, onClick }) {
711
+ const { tokens } = useSwypeConfig();
712
+ const [hovered, setHovered] = react.useState(false);
713
+ return /* @__PURE__ */ jsxRuntime.jsxs(
714
+ "button",
715
+ {
716
+ onClick,
717
+ onMouseEnter: () => setHovered(true),
718
+ onMouseLeave: () => setHovered(false),
719
+ style: {
720
+ display: "flex",
721
+ alignItems: "center",
722
+ gap: "12px",
723
+ width: "100%",
724
+ padding: "14px 16px",
725
+ background: selected ? tokens.accent + "18" : hovered ? tokens.bgHover : tokens.bgInput,
726
+ border: `1.5px solid ${selected ? tokens.accent : tokens.border}`,
727
+ borderRadius: tokens.radius,
728
+ cursor: "pointer",
729
+ transition: "all 0.15s ease",
730
+ color: tokens.text,
731
+ fontFamily: "inherit",
732
+ fontSize: "0.95rem",
733
+ fontWeight: 500,
734
+ textAlign: "left",
735
+ outline: "none"
736
+ },
737
+ children: [
738
+ provider.logoURI ? /* @__PURE__ */ jsxRuntime.jsx(
739
+ "img",
740
+ {
741
+ src: provider.logoURI,
742
+ alt: provider.name,
743
+ style: {
744
+ width: 32,
745
+ height: 32,
746
+ borderRadius: "8px",
747
+ objectFit: "contain"
748
+ }
749
+ }
750
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
751
+ "div",
752
+ {
753
+ style: {
754
+ width: 32,
755
+ height: 32,
756
+ borderRadius: "8px",
757
+ background: tokens.accent + "30",
758
+ display: "flex",
759
+ alignItems: "center",
760
+ justifyContent: "center",
761
+ fontSize: "0.875rem",
762
+ fontWeight: 700,
763
+ color: tokens.accent,
764
+ flexShrink: 0
765
+ },
766
+ children: provider.name.charAt(0).toUpperCase()
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: provider.name }),
770
+ selected && /* @__PURE__ */ jsxRuntime.jsx(
771
+ "svg",
772
+ {
773
+ width: "18",
774
+ height: "18",
775
+ viewBox: "0 0 24 24",
776
+ fill: "none",
777
+ style: { marginLeft: "auto", flexShrink: 0 },
778
+ children: /* @__PURE__ */ jsxRuntime.jsx(
779
+ "path",
780
+ {
781
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
782
+ fill: tokens.accent
783
+ }
784
+ )
785
+ }
786
+ )
787
+ ]
788
+ }
789
+ );
790
+ }
791
+ function AccountWalletSelector({
792
+ accounts,
793
+ selection,
794
+ onSelect
795
+ }) {
796
+ const { tokens } = useSwypeConfig();
797
+ const [expandedAccountId, setExpandedAccountId] = react.useState(null);
798
+ const activeAccounts = accounts.filter(
799
+ (a) => a.wallets.some((w) => w.status === "ACTIVE")
800
+ );
801
+ if (activeAccounts.length === 0) return null;
802
+ const handleAccountClick = (accountId) => {
803
+ if (expandedAccountId === accountId) {
804
+ setExpandedAccountId(null);
805
+ } else {
806
+ setExpandedAccountId(accountId);
807
+ }
808
+ };
809
+ const handleWalletClick = (account, wallet) => {
810
+ const isAlreadySelected = selection?.accountId === account.id && selection?.walletId === wallet.id;
811
+ if (isAlreadySelected) {
812
+ onSelect(null);
813
+ } else {
814
+ onSelect({
815
+ accountId: account.id,
816
+ walletId: wallet.id,
817
+ accountName: account.name,
818
+ walletName: wallet.name,
819
+ chainName: wallet.chain.name
820
+ });
821
+ }
822
+ };
823
+ const formatBalance = (wallet) => {
824
+ const parts = [];
825
+ for (const src of wallet.sources) {
826
+ if (src.token.status === "ACTIVE") {
827
+ parts.push(
828
+ `${src.balance.available.amount.toFixed(2)} ${src.balance.available.currency} (${src.token.symbol})`
829
+ );
830
+ }
831
+ }
832
+ if (parts.length === 0 && wallet.balance) {
833
+ return `${wallet.balance.available.amount.toFixed(2)} ${wallet.balance.available.currency}`;
834
+ }
835
+ return parts.join(", ") || "No balance";
836
+ };
837
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: "100%" }, children: [
838
+ /* @__PURE__ */ jsxRuntime.jsx(
839
+ "label",
840
+ {
841
+ style: {
842
+ display: "block",
843
+ fontSize: "0.8rem",
844
+ color: tokens.textMuted,
845
+ marginBottom: "8px",
846
+ fontWeight: 500,
847
+ textTransform: "uppercase",
848
+ letterSpacing: "0.05em"
849
+ },
850
+ children: "Or use a connected account"
851
+ }
852
+ ),
853
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: activeAccounts.map((account) => {
854
+ const isExpanded = expandedAccountId === account.id;
855
+ const activeWallets = account.wallets.filter((w) => w.status === "ACTIVE");
856
+ const hasSelection = selection?.accountId === account.id;
857
+ return /* @__PURE__ */ jsxRuntime.jsxs(
858
+ "div",
859
+ {
860
+ style: {
861
+ border: `1px solid ${hasSelection ? tokens.accent : tokens.border}`,
862
+ borderRadius: tokens.radius,
863
+ overflow: "hidden",
864
+ transition: "border-color 0.15s ease"
865
+ },
866
+ children: [
867
+ /* @__PURE__ */ jsxRuntime.jsxs(
868
+ "button",
869
+ {
870
+ onClick: () => handleAccountClick(account.id),
871
+ style: {
872
+ width: "100%",
873
+ display: "flex",
874
+ alignItems: "center",
875
+ justifyContent: "space-between",
876
+ padding: "12px 14px",
877
+ background: hasSelection ? tokens.accent + "10" : tokens.bgInput,
878
+ border: "none",
879
+ cursor: "pointer",
880
+ color: tokens.text,
881
+ fontFamily: "inherit",
882
+ fontSize: "0.9rem",
883
+ fontWeight: 500,
884
+ textAlign: "left",
885
+ outline: "none",
886
+ transition: "background 0.15s ease"
887
+ },
888
+ children: [
889
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [
890
+ /* @__PURE__ */ jsxRuntime.jsx(
891
+ "div",
892
+ {
893
+ style: {
894
+ width: 28,
895
+ height: 28,
896
+ borderRadius: "6px",
897
+ background: tokens.accent + "25",
898
+ display: "flex",
899
+ alignItems: "center",
900
+ justifyContent: "center",
901
+ fontSize: "0.75rem",
902
+ fontWeight: 700,
903
+ color: tokens.accent,
904
+ flexShrink: 0
905
+ },
906
+ children: account.name.charAt(0).toUpperCase()
907
+ }
908
+ ),
909
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
910
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: account.name }),
911
+ /* @__PURE__ */ jsxRuntime.jsxs(
912
+ "div",
913
+ {
914
+ style: {
915
+ fontSize: "0.75rem",
916
+ color: tokens.textMuted,
917
+ marginTop: "2px"
918
+ },
919
+ children: [
920
+ activeWallets.length,
921
+ " wallet",
922
+ activeWallets.length !== 1 ? "s" : ""
923
+ ]
924
+ }
925
+ )
926
+ ] })
927
+ ] }),
928
+ /* @__PURE__ */ jsxRuntime.jsx(
929
+ "svg",
930
+ {
931
+ width: "14",
932
+ height: "14",
933
+ viewBox: "0 0 24 24",
934
+ fill: "none",
935
+ style: {
936
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
937
+ transition: "transform 0.2s ease",
938
+ flexShrink: 0
939
+ },
940
+ children: /* @__PURE__ */ jsxRuntime.jsx(
941
+ "path",
942
+ {
943
+ d: "M7 10l5 5 5-5",
944
+ stroke: tokens.textMuted,
945
+ strokeWidth: "2",
946
+ strokeLinecap: "round",
947
+ strokeLinejoin: "round"
948
+ }
949
+ )
950
+ }
951
+ )
952
+ ]
953
+ }
954
+ ),
955
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx(
956
+ "div",
957
+ {
958
+ style: {
959
+ borderTop: `1px solid ${tokens.border}`,
960
+ background: tokens.bgCard
961
+ },
962
+ children: activeWallets.map((wallet) => {
963
+ const isSelected = selection?.walletId === wallet.id;
964
+ return /* @__PURE__ */ jsxRuntime.jsxs(
965
+ "button",
966
+ {
967
+ onClick: () => handleWalletClick(account, wallet),
968
+ style: {
969
+ width: "100%",
970
+ display: "flex",
971
+ alignItems: "center",
972
+ justifyContent: "space-between",
973
+ padding: "10px 14px 10px 24px",
974
+ background: isSelected ? tokens.accent + "12" : "transparent",
975
+ border: "none",
976
+ borderBottom: `1px solid ${tokens.border}`,
977
+ cursor: "pointer",
978
+ color: tokens.text,
979
+ fontFamily: "inherit",
980
+ fontSize: "0.85rem",
981
+ textAlign: "left",
982
+ outline: "none",
983
+ transition: "background 0.1s ease"
984
+ },
985
+ children: [
986
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
987
+ /* @__PURE__ */ jsxRuntime.jsxs(
988
+ "div",
989
+ {
990
+ style: {
991
+ display: "flex",
992
+ alignItems: "center",
993
+ gap: "6px"
994
+ },
995
+ children: [
996
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: wallet.name }),
997
+ /* @__PURE__ */ jsxRuntime.jsx(
998
+ "span",
999
+ {
1000
+ style: {
1001
+ fontSize: "0.7rem",
1002
+ color: tokens.textMuted,
1003
+ background: tokens.bgHover,
1004
+ padding: "1px 6px",
1005
+ borderRadius: "4px"
1006
+ },
1007
+ children: wallet.chain.name
1008
+ }
1009
+ )
1010
+ ]
1011
+ }
1012
+ ),
1013
+ /* @__PURE__ */ jsxRuntime.jsx(
1014
+ "div",
1015
+ {
1016
+ style: {
1017
+ fontSize: "0.75rem",
1018
+ color: tokens.textSecondary,
1019
+ marginTop: "3px"
1020
+ },
1021
+ children: formatBalance(wallet)
1022
+ }
1023
+ )
1024
+ ] }),
1025
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(
1026
+ "svg",
1027
+ {
1028
+ width: "16",
1029
+ height: "16",
1030
+ viewBox: "0 0 24 24",
1031
+ fill: "none",
1032
+ style: { flexShrink: 0, marginLeft: "8px" },
1033
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1034
+ "path",
1035
+ {
1036
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1037
+ fill: tokens.accent
1038
+ }
1039
+ )
1040
+ }
1041
+ )
1042
+ ]
1043
+ },
1044
+ wallet.id
1045
+ );
1046
+ })
1047
+ }
1048
+ )
1049
+ ]
1050
+ },
1051
+ account.id
1052
+ );
1053
+ }) }),
1054
+ selection && /* @__PURE__ */ jsxRuntime.jsxs(
1055
+ "div",
1056
+ {
1057
+ style: {
1058
+ marginTop: "8px",
1059
+ fontSize: "0.8rem",
1060
+ color: tokens.accent,
1061
+ display: "flex",
1062
+ alignItems: "center",
1063
+ gap: "6px"
1064
+ },
1065
+ children: [
1066
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1067
+ "path",
1068
+ {
1069
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1070
+ fill: tokens.accent
1071
+ }
1072
+ ) }),
1073
+ "Using ",
1074
+ selection.walletName,
1075
+ " on ",
1076
+ selection.chainName
1077
+ ]
1078
+ }
1079
+ )
1080
+ ] });
1081
+ }
1082
+ function isMobile() {
1083
+ if (typeof navigator === "undefined") return false;
1084
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
1085
+ navigator.userAgent
1086
+ );
1087
+ }
1088
+ function SwypePayment({
1089
+ destination,
1090
+ onComplete,
1091
+ onError
1092
+ }) {
1093
+ const { apiBaseUrl, tokens } = useSwypeConfig();
1094
+ const { ready, authenticated, login, getAccessToken } = reactAuth.usePrivy();
1095
+ const [step, setStep] = react.useState("login");
1096
+ const [error, setError] = react.useState(null);
1097
+ const [providers, setProviders] = react.useState([]);
1098
+ const [accounts, setAccounts] = react.useState([]);
1099
+ const [loadingData, setLoadingData] = react.useState(false);
1100
+ const [selectedProviderId, setSelectedProviderId] = react.useState(
1101
+ null
1102
+ );
1103
+ const [walletSelection, setWalletSelection] = react.useState(null);
1104
+ const [amount, setAmount] = react.useState("0.10");
1105
+ const [topUpBalance, setTopUpBalance] = react.useState("");
1106
+ const [transfer, setTransfer] = react.useState(null);
1107
+ const [creatingTransfer, setCreatingTransfer] = react.useState(false);
1108
+ const [mobileFlow, setMobileFlow] = react.useState(false);
1109
+ const pollingTransferIdRef = react.useRef(null);
1110
+ const authExecutor = useAuthorizationExecutor();
1111
+ const polling = useTransferPolling();
1112
+ const sourceType = walletSelection ? "walletId" : "providerId";
1113
+ const sourceId = walletSelection?.walletId ?? selectedProviderId ?? "";
1114
+ react.useEffect(() => {
1115
+ if (ready && authenticated && step === "login") {
1116
+ setStep("select-source");
1117
+ }
1118
+ }, [ready, authenticated, step]);
1119
+ react.useEffect(() => {
1120
+ if (step !== "select-source") return;
1121
+ if (providers.length > 0) return;
1122
+ let cancelled = false;
1123
+ const load = async () => {
1124
+ setLoadingData(true);
1125
+ setError(null);
1126
+ try {
1127
+ const token = await getAccessToken();
1128
+ if (!token) throw new Error("Not authenticated");
1129
+ const [prov, accts] = await Promise.all([
1130
+ fetchProviders(apiBaseUrl, token),
1131
+ fetchAccounts(apiBaseUrl, token)
1132
+ ]);
1133
+ if (cancelled) return;
1134
+ setProviders(prov);
1135
+ setAccounts(accts);
1136
+ if (prov.length > 0) setSelectedProviderId(prov[0].id);
1137
+ } catch (err) {
1138
+ if (!cancelled) {
1139
+ const msg = err instanceof Error ? err.message : "Failed to load data";
1140
+ setError(msg);
1141
+ }
1142
+ } finally {
1143
+ if (!cancelled) setLoadingData(false);
1144
+ }
1145
+ };
1146
+ load();
1147
+ return () => {
1148
+ cancelled = true;
1149
+ };
1150
+ }, [step, providers.length, apiBaseUrl, getAccessToken]);
1151
+ react.useEffect(() => {
1152
+ if (!polling.transfer) return;
1153
+ if (polling.transfer.status === "COMPLETED") {
1154
+ setStep("complete");
1155
+ setTransfer(polling.transfer);
1156
+ onComplete?.(polling.transfer);
1157
+ } else if (polling.transfer.status === "FAILED") {
1158
+ setStep("complete");
1159
+ setTransfer(polling.transfer);
1160
+ setError("Transfer failed.");
1161
+ }
1162
+ }, [polling.transfer, onComplete]);
1163
+ react.useEffect(() => {
1164
+ if (!mobileFlow || !polling.isPolling) return;
1165
+ const handleVisibility = () => {
1166
+ if (document.visibilityState === "visible") {
1167
+ if (pollingTransferIdRef.current) {
1168
+ polling.startPolling(pollingTransferIdRef.current);
1169
+ }
1170
+ }
1171
+ };
1172
+ document.addEventListener("visibilitychange", handleVisibility);
1173
+ return () => {
1174
+ document.removeEventListener("visibilitychange", handleVisibility);
1175
+ };
1176
+ }, [mobileFlow, polling]);
1177
+ const handlePay = react.useCallback(async () => {
1178
+ const parsedAmount = parseFloat(amount);
1179
+ if (isNaN(parsedAmount) || parsedAmount <= 0) {
1180
+ setError("Enter a valid amount.");
1181
+ return;
1182
+ }
1183
+ if (!sourceId) {
1184
+ setError("Select a source.");
1185
+ return;
1186
+ }
1187
+ setStep("processing");
1188
+ setError(null);
1189
+ setCreatingTransfer(true);
1190
+ setMobileFlow(false);
1191
+ try {
1192
+ const token = await getAccessToken();
1193
+ if (!token) throw new Error("Not authenticated");
1194
+ const parsedTopUp = parseFloat(topUpBalance);
1195
+ if (!isNaN(parsedTopUp) && parsedTopUp > 0) {
1196
+ await updateUserConfig(apiBaseUrl, token, {
1197
+ defaultAllowance: parsedTopUp
1198
+ });
1199
+ }
1200
+ const t = await createTransfer(apiBaseUrl, token, {
1201
+ sourceType,
1202
+ sourceId,
1203
+ destination,
1204
+ amount: parsedAmount
1205
+ });
1206
+ setTransfer(t);
1207
+ if (t.authorizationSessions && t.authorizationSessions.length > 0) {
1208
+ if (isMobile()) {
1209
+ setMobileFlow(true);
1210
+ pollingTransferIdRef.current = t.id;
1211
+ polling.startPolling(t.id);
1212
+ window.location.href = t.authorizationSessions[0].uri;
1213
+ return;
1214
+ } else {
1215
+ await authExecutor.executeSession(t);
1216
+ }
1217
+ }
1218
+ polling.startPolling(t.id);
1219
+ } catch (err) {
1220
+ const msg = err instanceof Error ? err.message : "Transfer creation failed";
1221
+ setError(msg);
1222
+ onError?.(msg);
1223
+ setStep("enter-amount");
1224
+ } finally {
1225
+ setCreatingTransfer(false);
1226
+ }
1227
+ }, [
1228
+ amount,
1229
+ sourceId,
1230
+ sourceType,
1231
+ destination,
1232
+ apiBaseUrl,
1233
+ getAccessToken,
1234
+ authExecutor,
1235
+ polling,
1236
+ onError,
1237
+ topUpBalance
1238
+ ]);
1239
+ const handleNewPayment = () => {
1240
+ setStep("select-source");
1241
+ setTransfer(null);
1242
+ setError(null);
1243
+ setAmount("0.10");
1244
+ setTopUpBalance("");
1245
+ setMobileFlow(false);
1246
+ pollingTransferIdRef.current = null;
1247
+ setWalletSelection(null);
1248
+ if (providers.length > 0) setSelectedProviderId(providers[0].id);
1249
+ };
1250
+ const cardStyle = {
1251
+ background: tokens.bgCard,
1252
+ borderRadius: tokens.radiusLg,
1253
+ border: `1px solid ${tokens.border}`,
1254
+ padding: "28px 24px",
1255
+ maxWidth: 420,
1256
+ width: "100%",
1257
+ boxShadow: tokens.shadowLg,
1258
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1259
+ color: tokens.text
1260
+ };
1261
+ const headingStyle = {
1262
+ fontSize: "1.2rem",
1263
+ fontWeight: 600,
1264
+ margin: "0 0 20px 0",
1265
+ color: tokens.text,
1266
+ textAlign: "center"
1267
+ };
1268
+ const btnPrimary = {
1269
+ width: "100%",
1270
+ padding: "14px",
1271
+ background: tokens.accent,
1272
+ color: tokens.accentText,
1273
+ border: "none",
1274
+ borderRadius: tokens.radius,
1275
+ fontSize: "1rem",
1276
+ fontWeight: 600,
1277
+ cursor: "pointer",
1278
+ transition: "background 0.15s ease",
1279
+ fontFamily: "inherit"
1280
+ };
1281
+ const btnDisabled = {
1282
+ ...btnPrimary,
1283
+ opacity: 0.5,
1284
+ cursor: "not-allowed"
1285
+ };
1286
+ const btnSecondary = {
1287
+ ...btnPrimary,
1288
+ background: "transparent",
1289
+ color: tokens.textSecondary,
1290
+ border: `1px solid ${tokens.border}`,
1291
+ width: "auto",
1292
+ flex: "0 0 auto",
1293
+ padding: "14px 20px"
1294
+ };
1295
+ const errorStyle = {
1296
+ background: tokens.errorBg,
1297
+ border: `1px solid ${tokens.error}`,
1298
+ borderRadius: tokens.radius,
1299
+ padding: "10px 14px",
1300
+ color: tokens.error,
1301
+ fontSize: "0.825rem",
1302
+ marginBottom: "14px",
1303
+ lineHeight: 1.5
1304
+ };
1305
+ const stepBadge = (label) => /* @__PURE__ */ jsxRuntime.jsxs(
1306
+ "div",
1307
+ {
1308
+ style: {
1309
+ display: "flex",
1310
+ alignItems: "center",
1311
+ gap: "8px",
1312
+ marginBottom: "20px"
1313
+ },
1314
+ children: [
1315
+ /* @__PURE__ */ jsxRuntime.jsx(
1316
+ "div",
1317
+ {
1318
+ style: {
1319
+ width: 6,
1320
+ height: 6,
1321
+ borderRadius: "50%",
1322
+ background: tokens.accent
1323
+ }
1324
+ }
1325
+ ),
1326
+ /* @__PURE__ */ jsxRuntime.jsx(
1327
+ "span",
1328
+ {
1329
+ style: {
1330
+ fontSize: "0.75rem",
1331
+ textTransform: "uppercase",
1332
+ letterSpacing: "0.06em",
1333
+ color: tokens.textMuted,
1334
+ fontWeight: 600
1335
+ },
1336
+ children: label
1337
+ }
1338
+ )
1339
+ ]
1340
+ }
1341
+ );
1342
+ if (!ready) {
1343
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "24px 0" }, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Initializing..." }) }) });
1344
+ }
1345
+ if (step === "login" && !authenticated) {
1346
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center" }, children: [
1347
+ /* @__PURE__ */ jsxRuntime.jsxs(
1348
+ "svg",
1349
+ {
1350
+ width: "48",
1351
+ height: "48",
1352
+ viewBox: "0 0 48 48",
1353
+ fill: "none",
1354
+ style: { margin: "0 auto 16px" },
1355
+ children: [
1356
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "48", height: "48", rx: "12", fill: tokens.accent + "20" }),
1357
+ /* @__PURE__ */ jsxRuntime.jsx(
1358
+ "path",
1359
+ {
1360
+ d: "M24 14v20M14 24h20",
1361
+ stroke: tokens.accent,
1362
+ strokeWidth: "2.5",
1363
+ strokeLinecap: "round"
1364
+ }
1365
+ )
1366
+ ]
1367
+ }
1368
+ ),
1369
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Pay with Swype" }),
1370
+ /* @__PURE__ */ jsxRuntime.jsx(
1371
+ "p",
1372
+ {
1373
+ style: {
1374
+ fontSize: "0.875rem",
1375
+ color: tokens.textSecondary,
1376
+ margin: "0 0 24px 0",
1377
+ lineHeight: 1.5
1378
+ },
1379
+ children: "Connect your account to continue"
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ jsxRuntime.jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
1383
+ ] }) });
1384
+ }
1385
+ if (step === "select-source") {
1386
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle, children: [
1387
+ stepBadge("Select payment source"),
1388
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle, children: "Choose a provider" }),
1389
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, children: error }),
1390
+ loadingData ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Loading providers..." }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1391
+ accounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1392
+ AccountWalletSelector,
1393
+ {
1394
+ accounts,
1395
+ selection: walletSelection,
1396
+ onSelect: (sel) => {
1397
+ setWalletSelection(sel);
1398
+ if (sel) setSelectedProviderId(null);
1399
+ }
1400
+ }
1401
+ ) }),
1402
+ !walletSelection && /* @__PURE__ */ jsxRuntime.jsx(
1403
+ "div",
1404
+ {
1405
+ style: {
1406
+ display: "flex",
1407
+ flexDirection: "column",
1408
+ gap: "8px",
1409
+ marginBottom: "20px"
1410
+ },
1411
+ children: providers.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
1412
+ ProviderCard,
1413
+ {
1414
+ provider: p,
1415
+ selected: selectedProviderId === p.id,
1416
+ onClick: () => {
1417
+ setSelectedProviderId(p.id);
1418
+ setWalletSelection(null);
1419
+ }
1420
+ },
1421
+ p.id
1422
+ ))
1423
+ }
1424
+ ),
1425
+ /* @__PURE__ */ jsxRuntime.jsx(
1426
+ "button",
1427
+ {
1428
+ style: sourceId ? btnPrimary : btnDisabled,
1429
+ disabled: !sourceId,
1430
+ onClick: () => {
1431
+ setError(null);
1432
+ setStep("enter-amount");
1433
+ },
1434
+ children: "Continue"
1435
+ }
1436
+ )
1437
+ ] })
1438
+ ] });
1439
+ }
1440
+ if (step === "enter-amount") {
1441
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle, children: [
1442
+ stepBadge("Enter amount"),
1443
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle, children: "How much?" }),
1444
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, children: error }),
1445
+ /* @__PURE__ */ jsxRuntime.jsxs(
1446
+ "div",
1447
+ {
1448
+ style: {
1449
+ display: "flex",
1450
+ alignItems: "center",
1451
+ gap: "8px",
1452
+ background: tokens.bgInput,
1453
+ border: `1px solid ${tokens.border}`,
1454
+ borderRadius: tokens.radius,
1455
+ padding: "4px 14px 4px 4px",
1456
+ marginBottom: "20px"
1457
+ },
1458
+ children: [
1459
+ /* @__PURE__ */ jsxRuntime.jsx(
1460
+ "span",
1461
+ {
1462
+ style: {
1463
+ fontSize: "1.5rem",
1464
+ fontWeight: 600,
1465
+ color: tokens.textMuted,
1466
+ paddingLeft: "10px",
1467
+ userSelect: "none"
1468
+ },
1469
+ children: "$"
1470
+ }
1471
+ ),
1472
+ /* @__PURE__ */ jsxRuntime.jsx(
1473
+ "input",
1474
+ {
1475
+ type: "number",
1476
+ min: "0.01",
1477
+ step: "0.01",
1478
+ value: amount,
1479
+ onChange: (e) => setAmount(e.target.value),
1480
+ style: {
1481
+ flex: 1,
1482
+ background: "transparent",
1483
+ border: "none",
1484
+ outline: "none",
1485
+ color: tokens.text,
1486
+ fontSize: "1.5rem",
1487
+ fontWeight: 600,
1488
+ fontFamily: "inherit",
1489
+ padding: "10px 0"
1490
+ },
1491
+ autoFocus: true
1492
+ }
1493
+ ),
1494
+ /* @__PURE__ */ jsxRuntime.jsx(
1495
+ "span",
1496
+ {
1497
+ style: {
1498
+ fontSize: "0.825rem",
1499
+ fontWeight: 600,
1500
+ color: tokens.textMuted,
1501
+ background: tokens.bgHover,
1502
+ padding: "4px 10px",
1503
+ borderRadius: "6px"
1504
+ },
1505
+ children: "USD"
1506
+ }
1507
+ )
1508
+ ]
1509
+ }
1510
+ ),
1511
+ /* @__PURE__ */ jsxRuntime.jsxs(
1512
+ "div",
1513
+ {
1514
+ style: {
1515
+ display: "flex",
1516
+ alignItems: "center",
1517
+ gap: "8px",
1518
+ background: tokens.bgInput,
1519
+ border: `1px solid ${tokens.border}`,
1520
+ borderRadius: tokens.radius,
1521
+ padding: "4px 14px 4px 4px",
1522
+ marginBottom: "20px"
1523
+ },
1524
+ children: [
1525
+ /* @__PURE__ */ jsxRuntime.jsx(
1526
+ "span",
1527
+ {
1528
+ style: {
1529
+ fontSize: "1rem",
1530
+ fontWeight: 600,
1531
+ color: tokens.textMuted,
1532
+ paddingLeft: "10px",
1533
+ userSelect: "none"
1534
+ },
1535
+ children: "$"
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsxRuntime.jsx(
1539
+ "input",
1540
+ {
1541
+ type: "number",
1542
+ min: "0",
1543
+ step: "1",
1544
+ placeholder: "Top up balance (optional)",
1545
+ value: topUpBalance,
1546
+ onChange: (e) => setTopUpBalance(e.target.value),
1547
+ style: {
1548
+ flex: 1,
1549
+ background: "transparent",
1550
+ border: "none",
1551
+ outline: "none",
1552
+ color: tokens.text,
1553
+ fontSize: "1rem",
1554
+ fontWeight: 600,
1555
+ fontFamily: "inherit",
1556
+ padding: "10px 0"
1557
+ }
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsxRuntime.jsx(
1561
+ "span",
1562
+ {
1563
+ style: {
1564
+ fontSize: "0.75rem",
1565
+ fontWeight: 600,
1566
+ color: tokens.textMuted,
1567
+ background: tokens.bgHover,
1568
+ padding: "4px 10px",
1569
+ borderRadius: "6px",
1570
+ whiteSpace: "nowrap"
1571
+ },
1572
+ children: "USD"
1573
+ }
1574
+ )
1575
+ ]
1576
+ }
1577
+ ),
1578
+ /* @__PURE__ */ jsxRuntime.jsx(
1579
+ "p",
1580
+ {
1581
+ style: {
1582
+ fontSize: "0.75rem",
1583
+ color: tokens.textMuted,
1584
+ margin: "-12px 0 16px 0",
1585
+ lineHeight: 1.4
1586
+ },
1587
+ children: "Set a higher allowance to skip wallet prompts on future payments under this amount."
1588
+ }
1589
+ ),
1590
+ /* @__PURE__ */ jsxRuntime.jsxs(
1591
+ "div",
1592
+ {
1593
+ style: {
1594
+ fontSize: "0.825rem",
1595
+ color: tokens.textSecondary,
1596
+ marginBottom: "20px",
1597
+ padding: "12px 14px",
1598
+ background: tokens.bgInput,
1599
+ borderRadius: tokens.radius,
1600
+ lineHeight: 1.7
1601
+ },
1602
+ children: [
1603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1604
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "To" }),
1605
+ /* @__PURE__ */ jsxRuntime.jsxs(
1606
+ "span",
1607
+ {
1608
+ style: {
1609
+ fontFamily: '"SF Mono", "Fira Code", monospace',
1610
+ fontSize: "0.8rem"
1611
+ },
1612
+ children: [
1613
+ destination.address.slice(0, 6),
1614
+ "...",
1615
+ destination.address.slice(-4)
1616
+ ]
1617
+ }
1618
+ )
1619
+ ] }),
1620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1621
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Token" }),
1622
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
1623
+ ] }),
1624
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Source" }),
1626
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: walletSelection ? `${walletSelection.walletName} (${walletSelection.chainName})` : providers.find((p) => p.id === selectedProviderId)?.name ?? "Provider" })
1627
+ ] })
1628
+ ]
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "10px" }, children: [
1632
+ /* @__PURE__ */ jsxRuntime.jsx(
1633
+ "button",
1634
+ {
1635
+ style: btnSecondary,
1636
+ onClick: () => {
1637
+ setError(null);
1638
+ setStep("select-source");
1639
+ },
1640
+ children: "Back"
1641
+ }
1642
+ ),
1643
+ /* @__PURE__ */ jsxRuntime.jsxs(
1644
+ "button",
1645
+ {
1646
+ style: parseFloat(amount) > 0 ? btnPrimary : btnDisabled,
1647
+ disabled: !(parseFloat(amount) > 0),
1648
+ onClick: handlePay,
1649
+ children: [
1650
+ "Pay $",
1651
+ parseFloat(amount || "0").toFixed(2)
1652
+ ]
1653
+ }
1654
+ )
1655
+ ] })
1656
+ ] });
1657
+ }
1658
+ if (step === "processing") {
1659
+ const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing ? "Authorizing..." : polling.isPolling ? "Processing payment..." : "Please wait...";
1660
+ const statusDescription = creatingTransfer ? "Setting up your transfer..." : mobileFlow ? "Complete the authorization in your wallet app, then return here." : authExecutor.executing ? "Complete the wallet prompts to authorize this payment." : polling.isPolling ? "Your payment is being processed. This usually takes a few moments." : "Hang tight...";
1661
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
1662
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
1663
+ /* @__PURE__ */ jsxRuntime.jsx(
1664
+ "h2",
1665
+ {
1666
+ style: {
1667
+ ...headingStyle,
1668
+ marginTop: "20px",
1669
+ marginBottom: "8px"
1670
+ },
1671
+ children: statusLabel
1672
+ }
1673
+ ),
1674
+ /* @__PURE__ */ jsxRuntime.jsx(
1675
+ "p",
1676
+ {
1677
+ style: {
1678
+ fontSize: "0.85rem",
1679
+ color: tokens.textSecondary,
1680
+ margin: 0,
1681
+ lineHeight: 1.5
1682
+ },
1683
+ children: statusDescription
1684
+ }
1685
+ ),
1686
+ !mobileFlow && authExecutor.results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: "20px", textAlign: "left" }, children: authExecutor.results.map((r) => /* @__PURE__ */ jsxRuntime.jsxs(
1687
+ "div",
1688
+ {
1689
+ style: {
1690
+ display: "flex",
1691
+ alignItems: "center",
1692
+ gap: "8px",
1693
+ padding: "6px 0",
1694
+ fontSize: "0.8rem",
1695
+ color: r.status === "success" ? tokens.success : tokens.error
1696
+ },
1697
+ children: [
1698
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: r.status === "success" ? "\u2713" : "\u2717" }),
1699
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: r.type.replace(/_/g, " ") })
1700
+ ]
1701
+ },
1702
+ r.actionId
1703
+ )) }),
1704
+ (error || authExecutor.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
1705
+ "div",
1706
+ {
1707
+ style: { ...errorStyle, marginTop: "16px", textAlign: "left" },
1708
+ children: error || authExecutor.error || polling.error
1709
+ }
1710
+ )
1711
+ ] }) });
1712
+ }
1713
+ if (step === "complete") {
1714
+ const succeeded = transfer?.status === "COMPLETED";
1715
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center" }, children: [
1716
+ /* @__PURE__ */ jsxRuntime.jsx(
1717
+ "div",
1718
+ {
1719
+ style: {
1720
+ width: 56,
1721
+ height: 56,
1722
+ borderRadius: "50%",
1723
+ background: succeeded ? tokens.success + "20" : tokens.error + "20",
1724
+ display: "flex",
1725
+ alignItems: "center",
1726
+ justifyContent: "center",
1727
+ margin: "0 auto 16px"
1728
+ },
1729
+ children: succeeded ? /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1730
+ "path",
1731
+ {
1732
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1733
+ fill: tokens.success
1734
+ }
1735
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1736
+ "path",
1737
+ {
1738
+ d: "M18.3 5.71L12 12.01 5.7 5.71 4.29 7.12l6.3 6.3-6.3 6.29 1.41 1.41 6.3-6.29 6.29 6.29 1.41-1.41-6.29-6.29 6.29-6.3-1.41-1.41z",
1739
+ fill: tokens.error
1740
+ }
1741
+ ) })
1742
+ }
1743
+ ),
1744
+ /* @__PURE__ */ jsxRuntime.jsx(
1745
+ "h2",
1746
+ {
1747
+ style: {
1748
+ ...headingStyle,
1749
+ marginBottom: "8px",
1750
+ color: succeeded ? tokens.success : tokens.error
1751
+ },
1752
+ children: succeeded ? "Payment Complete" : "Payment Failed"
1753
+ }
1754
+ ),
1755
+ transfer && /* @__PURE__ */ jsxRuntime.jsxs(
1756
+ "div",
1757
+ {
1758
+ style: {
1759
+ fontSize: "0.825rem",
1760
+ color: tokens.textSecondary,
1761
+ margin: "0 0 24px 0",
1762
+ padding: "14px",
1763
+ background: tokens.bgInput,
1764
+ borderRadius: tokens.radius,
1765
+ textAlign: "left",
1766
+ lineHeight: 1.8
1767
+ },
1768
+ children: [
1769
+ /* @__PURE__ */ jsxRuntime.jsxs(
1770
+ "div",
1771
+ {
1772
+ style: { display: "flex", justifyContent: "space-between" },
1773
+ children: [
1774
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Amount" }),
1775
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
1776
+ "$",
1777
+ transfer.amount?.amount?.toFixed(2),
1778
+ " ",
1779
+ transfer.amount?.currency
1780
+ ] })
1781
+ ]
1782
+ }
1783
+ ),
1784
+ /* @__PURE__ */ jsxRuntime.jsxs(
1785
+ "div",
1786
+ {
1787
+ style: { display: "flex", justifyContent: "space-between" },
1788
+ children: [
1789
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Status" }),
1790
+ /* @__PURE__ */ jsxRuntime.jsx(
1791
+ "span",
1792
+ {
1793
+ style: {
1794
+ fontWeight: 600,
1795
+ color: succeeded ? tokens.success : tokens.error
1796
+ },
1797
+ children: transfer.status
1798
+ }
1799
+ )
1800
+ ]
1801
+ }
1802
+ ),
1803
+ /* @__PURE__ */ jsxRuntime.jsxs(
1804
+ "div",
1805
+ {
1806
+ style: { display: "flex", justifyContent: "space-between" },
1807
+ children: [
1808
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Transfer ID" }),
1809
+ /* @__PURE__ */ jsxRuntime.jsxs(
1810
+ "span",
1811
+ {
1812
+ style: {
1813
+ fontFamily: '"SF Mono", "Fira Code", monospace',
1814
+ fontSize: "0.75rem"
1815
+ },
1816
+ children: [
1817
+ transfer.id.slice(0, 8),
1818
+ "..."
1819
+ ]
1820
+ }
1821
+ )
1822
+ ]
1823
+ }
1824
+ )
1825
+ ]
1826
+ }
1827
+ ),
1828
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...errorStyle, textAlign: "left" }, children: error }),
1829
+ /* @__PURE__ */ jsxRuntime.jsx("button", { style: btnPrimary, onClick: handleNewPayment, children: succeeded ? "Make Another Payment" : "Try Again" })
1830
+ ] }) });
1831
+ }
1832
+ return null;
1833
+ }
1834
+
1835
+ exports.SwypePayment = SwypePayment;
1836
+ exports.SwypeProvider = SwypeProvider;
1837
+ exports.darkTheme = darkTheme;
1838
+ exports.getTheme = getTheme;
1839
+ exports.lightTheme = lightTheme;
1840
+ exports.swypeApi = api_exports;
1841
+ exports.useAuthorizationExecutor = useAuthorizationExecutor;
1842
+ exports.useSwypeConfig = useSwypeConfig;
1843
+ exports.useTransferPolling = useTransferPolling;
1844
+ //# sourceMappingURL=index.cjs.map
1845
+ //# sourceMappingURL=index.cjs.map