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