@swype-org/react-sdk 0.1.1 → 0.1.3

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 CHANGED
@@ -1,9 +1,11 @@
1
- import { createContext, useRef, useMemo, useContext, useState, useCallback, useEffect } from 'react';
1
+ import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect } from 'react';
2
2
  import { PrivyProvider, usePrivy } from '@privy-io/react-auth';
3
- import { createConfig, http, WagmiProvider, useAccount, useConnect, useSwitchChain, useSignTypedData, usePublicClient, useWalletClient } from 'wagmi';
3
+ import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChain } from 'wagmi';
4
4
  import { mainnet, arbitrum, base } from 'wagmi/chains';
5
5
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { walletActions, createClient, custom } from 'viem';
8
+ import { parseAccount, getAddress } from 'viem/utils';
7
9
 
8
10
  var __defProp = Object.defineProperty;
9
11
  var __export = (target, all) => {
@@ -82,13 +84,19 @@ function SwypeProvider({
82
84
  if (!queryClientRef.current) {
83
85
  queryClientRef.current = new QueryClient();
84
86
  }
87
+ const [depositAmount, setDepositAmountRaw] = useState(null);
88
+ const setDepositAmount = useCallback((amount) => {
89
+ setDepositAmountRaw(amount);
90
+ }, []);
85
91
  const value = useMemo(
86
92
  () => ({
87
93
  apiBaseUrl,
88
94
  theme,
89
- tokens: getTheme(theme)
95
+ tokens: getTheme(theme),
96
+ depositAmount,
97
+ setDepositAmount
90
98
  }),
91
- [apiBaseUrl, theme]
99
+ [apiBaseUrl, theme, depositAmount, setDepositAmount]
92
100
  );
93
101
  return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsx(WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsx(
94
102
  PrivyProvider,
@@ -110,6 +118,20 @@ function useSwypeConfig() {
110
118
  }
111
119
  return ctx;
112
120
  }
121
+ function useSwypeDepositAmount() {
122
+ const ctx = useContext(SwypeContext);
123
+ if (!ctx) {
124
+ throw new Error(
125
+ "useSwypeDepositAmount must be used within a <SwypeProvider>"
126
+ );
127
+ }
128
+ return {
129
+ /** Current deposit amount, or null if not set */
130
+ amount: ctx.depositAmount,
131
+ /** Set the deposit amount (pass null to clear) */
132
+ setAmount: ctx.setDepositAmount
133
+ };
134
+ }
113
135
 
114
136
  // src/api.ts
115
137
  var api_exports = {};
@@ -121,7 +143,9 @@ __export(api_exports, {
121
143
  fetchProviders: () => fetchProviders,
122
144
  fetchTransfer: () => fetchTransfer,
123
145
  reportActionCompletion: () => reportActionCompletion,
124
- updateUserConfig: () => updateUserConfig
146
+ signTransfer: () => signTransfer,
147
+ updateUserConfig: () => updateUserConfig,
148
+ updateUserConfigBySession: () => updateUserConfigBySession
125
149
  });
126
150
  async function throwApiError(res) {
127
151
  const body = await res.json().catch(() => null);
@@ -188,6 +212,18 @@ async function fetchTransfer(apiBaseUrl, token, transferId) {
188
212
  if (!res.ok) await throwApiError(res);
189
213
  return await res.json();
190
214
  }
215
+ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp) {
216
+ const res = await fetch(`${apiBaseUrl}/v1/transfers/${transferId}`, {
217
+ method: "PATCH",
218
+ headers: {
219
+ "Content-Type": "application/json",
220
+ Authorization: `Bearer ${token}`
221
+ },
222
+ body: JSON.stringify({ signedUserOp })
223
+ });
224
+ if (!res.ok) await throwApiError(res);
225
+ return await res.json();
226
+ }
191
227
  async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
192
228
  const res = await fetch(
193
229
  `${apiBaseUrl}/v1/authorization-sessions/${sessionId}`
@@ -206,6 +242,17 @@ async function updateUserConfig(apiBaseUrl, token, config) {
206
242
  });
207
243
  if (!res.ok) await throwApiError(res);
208
244
  }
245
+ async function updateUserConfigBySession(apiBaseUrl, sessionId, config) {
246
+ const res = await fetch(
247
+ `${apiBaseUrl}/v1/authorization-sessions/${sessionId}/user-config`,
248
+ {
249
+ method: "PATCH",
250
+ headers: { "Content-Type": "application/json" },
251
+ body: JSON.stringify({ config })
252
+ }
253
+ );
254
+ if (!res.ok) await throwApiError(res);
255
+ }
209
256
  async function reportActionCompletion(apiBaseUrl, actionId, result) {
210
257
  const res = await fetch(
211
258
  `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
@@ -218,6 +265,443 @@ async function reportActionCompletion(apiBaseUrl, actionId, result) {
218
265
  if (!res.ok) await throwApiError(res);
219
266
  return await res.json();
220
267
  }
268
+
269
+ // node_modules/@wagmi/core/dist/esm/version.js
270
+ var version = "2.22.1";
271
+
272
+ // node_modules/@wagmi/core/dist/esm/utils/getVersion.js
273
+ var getVersion = () => `@wagmi/core@${version}`;
274
+
275
+ // node_modules/@wagmi/core/dist/esm/errors/base.js
276
+ var __classPrivateFieldGet = function(receiver, state, kind, f) {
277
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
278
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
279
+ };
280
+ var _BaseError_instances;
281
+ var _BaseError_walk;
282
+ var BaseError = class _BaseError extends Error {
283
+ get docsBaseUrl() {
284
+ return "https://wagmi.sh/core";
285
+ }
286
+ get version() {
287
+ return getVersion();
288
+ }
289
+ constructor(shortMessage, options = {}) {
290
+ super();
291
+ _BaseError_instances.add(this);
292
+ Object.defineProperty(this, "details", {
293
+ enumerable: true,
294
+ configurable: true,
295
+ writable: true,
296
+ value: void 0
297
+ });
298
+ Object.defineProperty(this, "docsPath", {
299
+ enumerable: true,
300
+ configurable: true,
301
+ writable: true,
302
+ value: void 0
303
+ });
304
+ Object.defineProperty(this, "metaMessages", {
305
+ enumerable: true,
306
+ configurable: true,
307
+ writable: true,
308
+ value: void 0
309
+ });
310
+ Object.defineProperty(this, "shortMessage", {
311
+ enumerable: true,
312
+ configurable: true,
313
+ writable: true,
314
+ value: void 0
315
+ });
316
+ Object.defineProperty(this, "name", {
317
+ enumerable: true,
318
+ configurable: true,
319
+ writable: true,
320
+ value: "WagmiCoreError"
321
+ });
322
+ const details = options.cause instanceof _BaseError ? options.cause.details : options.cause?.message ? options.cause.message : options.details;
323
+ const docsPath = options.cause instanceof _BaseError ? options.cause.docsPath || options.docsPath : options.docsPath;
324
+ this.message = [
325
+ shortMessage || "An error occurred.",
326
+ "",
327
+ ...options.metaMessages ? [...options.metaMessages, ""] : [],
328
+ ...docsPath ? [
329
+ `Docs: ${this.docsBaseUrl}${docsPath}.html${options.docsSlug ? `#${options.docsSlug}` : ""}`
330
+ ] : [],
331
+ ...details ? [`Details: ${details}`] : [],
332
+ `Version: ${this.version}`
333
+ ].join("\n");
334
+ if (options.cause)
335
+ this.cause = options.cause;
336
+ this.details = details;
337
+ this.docsPath = docsPath;
338
+ this.metaMessages = options.metaMessages;
339
+ this.shortMessage = shortMessage;
340
+ }
341
+ walk(fn) {
342
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk).call(this, this, fn);
343
+ }
344
+ };
345
+ _BaseError_instances = /* @__PURE__ */ new WeakSet(), _BaseError_walk = function _BaseError_walk2(err, fn) {
346
+ if (fn?.(err))
347
+ return err;
348
+ if (err.cause)
349
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk2).call(this, err.cause, fn);
350
+ return err;
351
+ };
352
+
353
+ // node_modules/@wagmi/core/dist/esm/errors/config.js
354
+ var ConnectorNotConnectedError = class extends BaseError {
355
+ constructor() {
356
+ super("Connector not connected.");
357
+ Object.defineProperty(this, "name", {
358
+ enumerable: true,
359
+ configurable: true,
360
+ writable: true,
361
+ value: "ConnectorNotConnectedError"
362
+ });
363
+ }
364
+ };
365
+ var ConnectorAccountNotFoundError = class extends BaseError {
366
+ constructor({ address, connector }) {
367
+ super(`Account "${address}" not found for connector "${connector.name}".`);
368
+ Object.defineProperty(this, "name", {
369
+ enumerable: true,
370
+ configurable: true,
371
+ writable: true,
372
+ value: "ConnectorAccountNotFoundError"
373
+ });
374
+ }
375
+ };
376
+ var ConnectorChainMismatchError = class extends BaseError {
377
+ constructor({ connectionChainId, connectorChainId }) {
378
+ super(`The current chain of the connector (id: ${connectorChainId}) does not match the connection's chain (id: ${connectionChainId}).`, {
379
+ metaMessages: [
380
+ `Current Chain ID: ${connectorChainId}`,
381
+ `Expected Chain ID: ${connectionChainId}`
382
+ ]
383
+ });
384
+ Object.defineProperty(this, "name", {
385
+ enumerable: true,
386
+ configurable: true,
387
+ writable: true,
388
+ value: "ConnectorChainMismatchError"
389
+ });
390
+ }
391
+ };
392
+ var ConnectorUnavailableReconnectingError = class extends BaseError {
393
+ constructor({ connector }) {
394
+ super(`Connector "${connector.name}" unavailable while reconnecting.`, {
395
+ details: [
396
+ "During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.",
397
+ "All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.",
398
+ "This error commonly occurs for connectors that asynchronously inject after reconnection has already started."
399
+ ].join(" ")
400
+ });
401
+ Object.defineProperty(this, "name", {
402
+ enumerable: true,
403
+ configurable: true,
404
+ writable: true,
405
+ value: "ConnectorUnavailableReconnectingError"
406
+ });
407
+ }
408
+ };
409
+ async function getConnectorClient(config, parameters = {}) {
410
+ const { assertChainId = true } = parameters;
411
+ let connection;
412
+ if (parameters.connector) {
413
+ const { connector: connector2 } = parameters;
414
+ if (config.state.status === "reconnecting" && !connector2.getAccounts && !connector2.getChainId)
415
+ throw new ConnectorUnavailableReconnectingError({ connector: connector2 });
416
+ const [accounts, chainId2] = await Promise.all([
417
+ connector2.getAccounts().catch((e) => {
418
+ if (parameters.account === null)
419
+ return [];
420
+ throw e;
421
+ }),
422
+ connector2.getChainId()
423
+ ]);
424
+ connection = {
425
+ accounts,
426
+ chainId: chainId2,
427
+ connector: connector2
428
+ };
429
+ } else
430
+ connection = config.state.connections.get(config.state.current);
431
+ if (!connection)
432
+ throw new ConnectorNotConnectedError();
433
+ const chainId = parameters.chainId ?? connection.chainId;
434
+ const connectorChainId = await connection.connector.getChainId();
435
+ if (assertChainId && connectorChainId !== chainId)
436
+ throw new ConnectorChainMismatchError({
437
+ connectionChainId: chainId,
438
+ connectorChainId
439
+ });
440
+ const connector = connection.connector;
441
+ if (connector.getClient)
442
+ return connector.getClient({ chainId });
443
+ const account = parseAccount(parameters.account ?? connection.accounts[0]);
444
+ if (account)
445
+ account.address = getAddress(account.address);
446
+ if (parameters.account && !connection.accounts.some((x) => x.toLowerCase() === account.address.toLowerCase()))
447
+ throw new ConnectorAccountNotFoundError({
448
+ address: account.address,
449
+ connector
450
+ });
451
+ const chain = config.chains.find((chain2) => chain2.id === chainId);
452
+ const provider = await connection.connector.getProvider({ chainId });
453
+ return createClient({
454
+ account,
455
+ chain,
456
+ name: "Connector Client",
457
+ transport: (opts) => custom(provider)({ ...opts, retryCount: 0 })
458
+ });
459
+ }
460
+
461
+ // node_modules/@wagmi/core/dist/esm/actions/getAccount.js
462
+ function getAccount(config) {
463
+ const uid = config.state.current;
464
+ const connection = config.state.connections.get(uid);
465
+ const addresses = connection?.accounts;
466
+ const address = addresses?.[0];
467
+ const chain = config.chains.find((chain2) => chain2.id === connection?.chainId);
468
+ const status = config.state.status;
469
+ switch (status) {
470
+ case "connected":
471
+ return {
472
+ address,
473
+ addresses,
474
+ chain,
475
+ chainId: connection?.chainId,
476
+ connector: connection?.connector,
477
+ isConnected: true,
478
+ isConnecting: false,
479
+ isDisconnected: false,
480
+ isReconnecting: false,
481
+ status
482
+ };
483
+ case "reconnecting":
484
+ return {
485
+ address,
486
+ addresses,
487
+ chain,
488
+ chainId: connection?.chainId,
489
+ connector: connection?.connector,
490
+ isConnected: !!address,
491
+ isConnecting: false,
492
+ isDisconnected: false,
493
+ isReconnecting: true,
494
+ status
495
+ };
496
+ case "connecting":
497
+ return {
498
+ address,
499
+ addresses,
500
+ chain,
501
+ chainId: connection?.chainId,
502
+ connector: connection?.connector,
503
+ isConnected: false,
504
+ isConnecting: true,
505
+ isDisconnected: false,
506
+ isReconnecting: false,
507
+ status
508
+ };
509
+ case "disconnected":
510
+ return {
511
+ address: void 0,
512
+ addresses: void 0,
513
+ chain: void 0,
514
+ chainId: void 0,
515
+ connector: void 0,
516
+ isConnected: false,
517
+ isConnecting: false,
518
+ isDisconnected: true,
519
+ isReconnecting: false,
520
+ status
521
+ };
522
+ }
523
+ }
524
+ async function getWalletClient(config, parameters = {}) {
525
+ const client = await getConnectorClient(config, parameters);
526
+ return client.extend(walletActions);
527
+ }
528
+
529
+ // src/authorization/upgrade.ts
530
+ function isNonEmptyString(value) {
531
+ return typeof value === "string" && value.trim().length > 0;
532
+ }
533
+ function isValidChainId(value) {
534
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
535
+ }
536
+ function isRpcClient(value) {
537
+ return typeof value === "object" && value !== null && "request" in value && typeof value.request === "function";
538
+ }
539
+ function sleep(ms) {
540
+ return new Promise((resolve) => setTimeout(resolve, ms));
541
+ }
542
+ async function pollCallsStatus(client, bundleId, label, timeoutMs = 12e4, intervalMs = 3e3) {
543
+ const deadline = Date.now() + timeoutMs;
544
+ while (Date.now() < deadline) {
545
+ const raw = await client.request({
546
+ method: "wallet_getCallsStatus",
547
+ params: [bundleId]
548
+ });
549
+ const result = raw;
550
+ const status = result.status;
551
+ console.info(`[swype-sdk][upgrade] ${label} status:`, status);
552
+ if (status === 200 || status === "CONFIRMED" || status === "confirmed") {
553
+ const txHash = result.receipts?.[0]?.transactionHash ?? "";
554
+ if (!txHash) {
555
+ console.warn(`[swype-sdk][upgrade] ${label}: confirmed but no receipt txHash.`);
556
+ }
557
+ return txHash;
558
+ }
559
+ if (typeof status === "number" && status >= 400) {
560
+ throw new Error(
561
+ `${label} failed with status ${status}. Receipts: ${JSON.stringify(result.receipts ?? [])}`
562
+ );
563
+ }
564
+ if (typeof status === "string" && (status.toLowerCase().includes("fail") || status.toLowerCase().includes("error"))) {
565
+ throw new Error(
566
+ `${label} failed: ${status}. Receipts: ${JSON.stringify(result.receipts ?? [])}`
567
+ );
568
+ }
569
+ await sleep(intervalMs);
570
+ }
571
+ throw new Error(`${label} timed out after ${timeoutMs / 1e3}s.`);
572
+ }
573
+ async function sendCalls(client, callsParams, label) {
574
+ try {
575
+ const result = await client.request({
576
+ method: "wallet_sendCalls",
577
+ params: [callsParams]
578
+ });
579
+ const bundleId = typeof result === "string" ? result : result?.id;
580
+ if (!bundleId) {
581
+ throw new Error(`${label}: wallet_sendCalls returned an unexpected result: ${JSON.stringify(result)}`);
582
+ }
583
+ return bundleId;
584
+ } catch (err) {
585
+ const msg = err.message ?? "";
586
+ if (msg.includes("Unauthorized") || msg.includes("-32006")) {
587
+ throw new Error(
588
+ "MetaMask smart accounts may not be enabled or may not be supported on this chain. Please ensure you are using a supported network and that smart accounts are enabled in MetaMask Settings \u2192 Experimental."
589
+ );
590
+ }
591
+ throw new Error(`${label} failed: ${msg}`);
592
+ }
593
+ }
594
+ async function submitUpgradeTransaction(params) {
595
+ const { walletClient, sender, metadata } = params;
596
+ const addKeyCalldata = metadata?.addKeyCalldata;
597
+ if (!isNonEmptyString(addKeyCalldata)) {
598
+ throw new Error("Incomplete upgrade metadata: missing addKeyCalldata.");
599
+ }
600
+ const resolvedSmartAccountAddress = metadata?.smartAccountAddress ?? sender;
601
+ if (!isNonEmptyString(resolvedSmartAccountAddress)) {
602
+ throw new Error("No connected account address found for smart account upgrade.");
603
+ }
604
+ const chainId = metadata?.upgradePayload?.authorization?.chainId;
605
+ if (!isValidChainId(chainId)) {
606
+ throw new Error("Invalid or missing chainId in upgrade metadata.");
607
+ }
608
+ if (!isRpcClient(walletClient)) {
609
+ throw new Error(
610
+ "Connected wallet client does not support request(). EIP-7702 upgrade requires a wallet client with raw RPC access."
611
+ );
612
+ }
613
+ const hexChainId = `0x${chainId.toString(16)}`;
614
+ console.info(
615
+ "[swype-sdk][upgrade] Step 1/2: Triggering EIP-7702 upgrade via wallet_sendCalls.",
616
+ { from: resolvedSmartAccountAddress, chainId: hexChainId }
617
+ );
618
+ const upgradeBundleId = await sendCalls(
619
+ walletClient,
620
+ {
621
+ version: "2.0.0",
622
+ from: resolvedSmartAccountAddress,
623
+ chainId: hexChainId,
624
+ atomicRequired: false,
625
+ calls: [{ to: resolvedSmartAccountAddress, value: "0x0" }]
626
+ },
627
+ "EIP-7702 upgrade"
628
+ );
629
+ console.info("[swype-sdk][upgrade] Upgrade bundle submitted. Polling for confirmation\u2026", {
630
+ bundleId: upgradeBundleId
631
+ });
632
+ const upgradeTxHash = await pollCallsStatus(
633
+ walletClient,
634
+ upgradeBundleId,
635
+ "EIP-7702 upgrade"
636
+ );
637
+ console.info("[swype-sdk][upgrade] Step 1/2 complete: account upgraded.", {
638
+ txHash: upgradeTxHash
639
+ });
640
+ console.info(
641
+ "[swype-sdk][upgrade] Step 2/2: Registering passkey via wallet_sendCalls (addKey)."
642
+ );
643
+ const addKeyBundleId = await sendCalls(
644
+ walletClient,
645
+ {
646
+ version: "2.0.0",
647
+ from: resolvedSmartAccountAddress,
648
+ chainId: hexChainId,
649
+ atomicRequired: false,
650
+ calls: [
651
+ {
652
+ to: resolvedSmartAccountAddress,
653
+ value: "0x0",
654
+ data: addKeyCalldata
655
+ }
656
+ ]
657
+ },
658
+ "Passkey registration (addKey)"
659
+ );
660
+ console.info("[swype-sdk][upgrade] addKey bundle submitted. Polling for confirmation\u2026", {
661
+ bundleId: addKeyBundleId
662
+ });
663
+ const addKeyTxHash = await pollCallsStatus(
664
+ walletClient,
665
+ addKeyBundleId,
666
+ "Passkey registration"
667
+ );
668
+ console.info("[swype-sdk][upgrade] Step 2/2 complete: passkey registered.", {
669
+ txHash: addKeyTxHash
670
+ });
671
+ return {
672
+ txHash: addKeyTxHash || upgradeTxHash,
673
+ smartAccountAddress: resolvedSmartAccountAddress
674
+ };
675
+ }
676
+
677
+ // src/hooks.ts
678
+ async function waitForWalletClient(wagmiConfig2, maxAttempts = 15, intervalMs = 200) {
679
+ for (let i = 0; i < maxAttempts; i++) {
680
+ try {
681
+ return await getWalletClient(wagmiConfig2);
682
+ } catch {
683
+ if (i === maxAttempts - 1) {
684
+ throw new Error("Wallet not ready. Please try again.");
685
+ }
686
+ await new Promise((r) => setTimeout(r, intervalMs));
687
+ }
688
+ }
689
+ throw new Error("Wallet not ready. Please try again.");
690
+ }
691
+ function extractErrorMessage(err) {
692
+ if (err instanceof Error) return err.message;
693
+ if (typeof err === "string") return err;
694
+ if (err && typeof err === "object") {
695
+ const maybeMessage = err.message;
696
+ if (typeof maybeMessage === "string") return maybeMessage;
697
+ }
698
+ return "Failed to upgrade account";
699
+ }
700
+ function isMetaMaskConnector(account) {
701
+ const connectorName = account.connector?.name?.toLowerCase() ?? "";
702
+ const connectorId = account.connector?.id?.toLowerCase() ?? "";
703
+ return connectorName.includes("metamask") || connectorId.includes("metamask");
704
+ }
221
705
  function useTransferPolling(intervalMs = 3e3) {
222
706
  const { apiBaseUrl } = useSwypeConfig();
223
707
  const { getAccessToken } = usePrivy();
@@ -268,31 +752,45 @@ function useTransferPolling(intervalMs = 3e3) {
268
752
  }
269
753
  function useAuthorizationExecutor() {
270
754
  const { apiBaseUrl } = useSwypeConfig();
271
- const { address, chainId: currentChainId, isConnected } = useAccount();
755
+ const wagmiConfig2 = useConfig();
272
756
  const { connectAsync, connectors } = useConnect();
273
757
  const { switchChainAsync } = useSwitchChain();
274
- const { signTypedDataAsync } = useSignTypedData();
275
- const publicClient = usePublicClient();
276
- const { data: walletClient } = useWalletClient();
277
758
  const [executing, setExecuting] = useState(false);
278
759
  const [results, setResults] = useState([]);
279
760
  const [error, setError] = useState(null);
761
+ const [currentAction, setCurrentAction] = useState(null);
280
762
  const executingRef = useRef(false);
763
+ const [pendingSelectSource, setPendingSelectSource] = useState(null);
764
+ const selectSourceResolverRef = useRef(null);
765
+ const resolveSelectSource = useCallback((selection) => {
766
+ if (selectSourceResolverRef.current) {
767
+ selectSourceResolverRef.current(selection);
768
+ selectSourceResolverRef.current = null;
769
+ setPendingSelectSource(null);
770
+ }
771
+ }, []);
772
+ const sessionIdRef = useRef(null);
281
773
  const executeOpenProvider = useCallback(
282
774
  async (action) => {
283
775
  try {
284
- if (isConnected && address) {
285
- const hexChainId2 = currentChainId ? `0x${currentChainId.toString(16)}` : void 0;
776
+ const account = getAccount(wagmiConfig2);
777
+ if (account.isConnected && account.address) {
778
+ const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
286
779
  return {
287
780
  actionId: action.id,
288
781
  type: action.type,
289
782
  status: "success",
290
- message: `Connected. Account: ${address}, Chain: ${hexChainId2}`,
291
- data: { accounts: [address], chainId: hexChainId2 }
783
+ message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
784
+ data: { accounts: [account.address], chainId: hexChainId2 }
292
785
  };
293
786
  }
294
787
  const targetId = action.metadata?.wagmiConnectorId;
295
- const connector = targetId ? connectors.find((c) => c.id === targetId) ?? connectors[0] : connectors[0];
788
+ const metaMaskConnector = connectors.find((c) => {
789
+ const id = c.id.toLowerCase();
790
+ const name = c.name.toLowerCase();
791
+ return id.includes("metamask") || name.includes("metamask");
792
+ });
793
+ const connector = targetId ? connectors.find((c) => c.id === targetId) ?? metaMaskConnector ?? connectors[0] : metaMaskConnector ?? connectors[0];
296
794
  if (!connector) {
297
795
  return {
298
796
  actionId: action.id,
@@ -319,243 +817,231 @@ function useAuthorizationExecutor() {
319
817
  };
320
818
  }
321
819
  },
322
- [isConnected, address, currentChainId, connectors, connectAsync]
820
+ [wagmiConfig2, connectors, connectAsync]
323
821
  );
324
- const executeSwitchChain = useCallback(
822
+ const executeSelectSource = useCallback(
325
823
  async (action) => {
326
824
  try {
327
- const targetChainIdHex = action.metadata?.targetChainId;
328
- if (!targetChainIdHex) {
825
+ const options = action.metadata?.options;
826
+ const recommended = action.metadata?.recommended;
827
+ if (!options || options.length <= 1) {
828
+ const selection2 = recommended ?? { chainName: "Base", tokenSymbol: "USDC" };
329
829
  return {
330
830
  actionId: action.id,
331
831
  type: action.type,
332
- status: "error",
333
- message: "No targetChainId in action metadata."
832
+ status: "success",
833
+ message: `Auto-selected ${selection2.tokenSymbol} on ${selection2.chainName}.`,
834
+ data: {
835
+ selectedChainName: selection2.chainName,
836
+ selectedTokenSymbol: selection2.tokenSymbol
837
+ }
334
838
  };
335
839
  }
336
- const targetChainIdNum = parseInt(targetChainIdHex, 16);
337
- await switchChainAsync({ chainId: targetChainIdNum });
338
- const hexChainId = `0x${targetChainIdNum.toString(16)}`;
840
+ const selection = await new Promise((resolve) => {
841
+ selectSourceResolverRef.current = resolve;
842
+ setPendingSelectSource(action);
843
+ });
339
844
  return {
340
845
  actionId: action.id,
341
846
  type: action.type,
342
847
  status: "success",
343
- message: `Switched to chain ${hexChainId}.`,
344
- data: { chainId: hexChainId, switched: true }
848
+ message: `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
849
+ data: {
850
+ selectedChainName: selection.chainName,
851
+ selectedTokenSymbol: selection.tokenSymbol
852
+ }
345
853
  };
346
854
  } catch (err) {
347
855
  return {
348
856
  actionId: action.id,
349
857
  type: action.type,
350
858
  status: "error",
351
- message: err instanceof Error ? err.message : "Failed to switch chain"
859
+ message: err instanceof Error ? err.message : "Failed to select source"
352
860
  };
353
861
  }
354
862
  },
355
- [switchChainAsync]
863
+ []
356
864
  );
357
- const executeApprovePermit2 = useCallback(
865
+ const executeSwitchChain = useCallback(
358
866
  async (action) => {
359
867
  try {
360
- const tokenAddress = action.metadata?.tokenAddress;
361
- const permit2Address = action.metadata?.permit2Address;
362
- if (!tokenAddress || !permit2Address) {
868
+ const account = getAccount(wagmiConfig2);
869
+ const targetChainIdHex = action.metadata?.targetChainId;
870
+ if (!targetChainIdHex) {
363
871
  return {
364
872
  actionId: action.id,
365
873
  type: action.type,
366
874
  status: "error",
367
- message: "Missing tokenAddress or permit2Address in action metadata."
875
+ message: "No targetChainId in action metadata."
368
876
  };
369
877
  }
370
- if (!address) {
878
+ const targetChainIdNum = parseInt(targetChainIdHex, 16);
879
+ const hexChainId = `0x${targetChainIdNum.toString(16)}`;
880
+ if (account.chainId === targetChainIdNum) {
371
881
  return {
372
882
  actionId: action.id,
373
883
  type: action.type,
374
- status: "error",
375
- message: "Wallet not connected."
884
+ status: "success",
885
+ message: `Already on chain ${hexChainId}. Skipped.`,
886
+ data: { chainId: hexChainId, switched: false }
376
887
  };
377
888
  }
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" }
889
+ await switchChainAsync({ chainId: targetChainIdNum });
890
+ return {
891
+ actionId: action.id,
892
+ type: action.type,
893
+ status: "success",
894
+ message: `Switched to chain ${hexChainId}.`,
895
+ data: { chainId: hexChainId, switched: true }
896
+ };
897
+ } catch (err) {
898
+ return {
899
+ actionId: action.id,
900
+ type: action.type,
901
+ status: "error",
902
+ message: err instanceof Error ? err.message : "Failed to switch chain"
903
+ };
904
+ }
905
+ },
906
+ [wagmiConfig2, switchChainAsync]
907
+ );
908
+ const executeRegisterPasskey = useCallback(
909
+ async (action) => {
910
+ try {
911
+ const account = getAccount(wagmiConfig2);
912
+ const challenge = new Uint8Array(32);
913
+ crypto.getRandomValues(challenge);
914
+ const credential = await navigator.credentials.create({
915
+ publicKey: {
916
+ challenge,
917
+ rp: {
918
+ name: "Swype",
919
+ id: window.location.hostname
920
+ },
921
+ user: {
922
+ id: new TextEncoder().encode(account.address ?? "user"),
923
+ name: account.address ?? "Swype User",
924
+ displayName: "Swype User"
925
+ },
926
+ pubKeyCredParams: [
927
+ { alg: -7, type: "public-key" },
928
+ // ES256 (P-256)
929
+ { alg: -257, type: "public-key" }
930
+ // RS256
396
931
  ],
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
- };
932
+ authenticatorSelection: {
933
+ authenticatorAttachment: "platform",
934
+ residentKey: "preferred",
935
+ userVerification: "required"
936
+ },
937
+ timeout: 6e4
415
938
  }
416
- }
417
- if (!walletClient) {
939
+ });
940
+ if (!credential) {
418
941
  return {
419
942
  actionId: action.id,
420
943
  type: action.type,
421
944
  status: "error",
422
- message: "Wallet client not available."
945
+ message: "Passkey creation was cancelled."
423
946
  };
424
947
  }
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
- }
948
+ const response = credential.response;
949
+ const publicKeyBytes = response.getPublicKey?.();
950
+ const credentialId = btoa(
951
+ String.fromCharCode(...new Uint8Array(credential.rawId))
952
+ );
953
+ const publicKey = publicKeyBytes ? btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))) : "";
435
954
  return {
436
955
  actionId: action.id,
437
956
  type: action.type,
438
957
  status: "success",
439
- message: `Permit2 approved. Tx: ${txHash}`,
440
- data: { txHash, approved: true }
958
+ message: "Passkey created successfully.",
959
+ data: {
960
+ credentialId,
961
+ publicKey
962
+ }
441
963
  };
442
964
  } catch (err) {
443
965
  return {
444
966
  actionId: action.id,
445
967
  type: action.type,
446
968
  status: "error",
447
- message: err instanceof Error ? err.message : "Failed to approve Permit2"
969
+ message: err instanceof Error ? err.message : "Failed to create passkey"
448
970
  };
449
971
  }
450
972
  },
451
- [address, publicClient, walletClient]
973
+ [wagmiConfig2]
452
974
  );
453
- const executeSignPermit2 = useCallback(
975
+ const executeUpgradeSmartAccount = useCallback(
454
976
  async (action) => {
455
977
  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
- ]
978
+ const account = getAccount(wagmiConfig2);
979
+ const walletClient = await waitForWalletClient(wagmiConfig2);
980
+ const sender = account.address ?? walletClient.account?.address;
981
+ if (!sender) {
982
+ throw new Error("Wallet account not available. Please connect your wallet.");
983
+ }
984
+ if (!isMetaMaskConnector(account)) {
985
+ throw new Error(
986
+ "EIP-7702 smart account upgrade requires a MetaMask-compatible wallet. Please reconnect using MetaMask and try again."
987
+ );
988
+ }
989
+ const metadata = action.metadata;
990
+ const { txHash, smartAccountAddress } = await submitUpgradeTransaction({
991
+ walletClient,
992
+ sender,
993
+ metadata
994
+ });
995
+ return {
996
+ actionId: action.id,
997
+ type: action.type,
998
+ status: "success",
999
+ message: "Account upgrade transaction submitted.",
1000
+ data: {
1001
+ txHash,
1002
+ smartAccountAddress
472
1003
  }
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) {
1004
+ };
1005
+ } catch (err) {
1006
+ console.error("Failed to upgrade account", err);
1007
+ const message = extractErrorMessage(err);
1008
+ const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
1009
+ return {
1010
+ actionId: action.id,
1011
+ type: action.type,
1012
+ status: "error",
1013
+ message: isRejected ? "You rejected the upgrade transaction. Please approve the transaction prompt in your wallet to continue." : message
1014
+ };
1015
+ }
1016
+ },
1017
+ [wagmiConfig2]
1018
+ );
1019
+ const executeGrantPermissions = useCallback(
1020
+ async (action) => {
1021
+ try {
1022
+ const walletClient = await waitForWalletClient(wagmiConfig2);
1023
+ const signingPayload = action.metadata?.signingPayload;
1024
+ const tokens = action.metadata?.tokens;
1025
+ if (!signingPayload) {
479
1026
  return {
480
1027
  actionId: action.id,
481
1028
  type: action.type,
482
1029
  status: "error",
483
- message: "Missing spenderAddress or tokenAddress in action metadata."
1030
+ message: "No signing payload in action metadata."
484
1031
  };
485
1032
  }
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
- }
1033
+ const signature = await walletClient.signMessage({
1034
+ message: JSON.stringify(signingPayload)
546
1035
  });
1036
+ const tokenSummary = tokens?.map((t) => `${t.symbol} on ${t.chainName}`).join(", ") ?? "all tokens";
547
1037
  return {
548
1038
  actionId: action.id,
549
1039
  type: action.type,
550
1040
  status: "success",
551
- message: "Permit2 allowance signature obtained.",
1041
+ message: `Permissions granted for ${tokenSummary}.`,
552
1042
  data: {
553
- signature,
554
- signer: address,
555
- nonce: nonce.toString(),
556
- sigDeadline: sigDeadline.toString(),
557
- expiration: expiration.toString(),
558
- amount: permitAmount.toString()
1043
+ signedDelegation: signature,
1044
+ tokens
559
1045
  }
560
1046
  };
561
1047
  } catch (err) {
@@ -563,23 +1049,28 @@ function useAuthorizationExecutor() {
563
1049
  actionId: action.id,
564
1050
  type: action.type,
565
1051
  status: "error",
566
- message: err instanceof Error ? err.message : "Failed to sign Permit2"
1052
+ message: err instanceof Error ? err.message : "Failed to grant permissions"
567
1053
  };
568
1054
  }
569
1055
  },
570
- [address, currentChainId, publicClient, signTypedDataAsync]
1056
+ [wagmiConfig2]
571
1057
  );
572
1058
  const executeAction = useCallback(
573
1059
  async (action) => {
1060
+ setCurrentAction(action);
574
1061
  switch (action.type) {
575
1062
  case "OPEN_PROVIDER":
576
1063
  return executeOpenProvider(action);
1064
+ case "SELECT_SOURCE":
1065
+ return executeSelectSource(action);
577
1066
  case "SWITCH_CHAIN":
578
1067
  return executeSwitchChain(action);
579
- case "APPROVE_PERMIT_2":
580
- return executeApprovePermit2(action);
581
- case "SIGN_PERMIT2":
582
- return executeSignPermit2(action);
1068
+ case "REGISTER_PASSKEY":
1069
+ return executeRegisterPasskey(action);
1070
+ case "UPGRADE_SMART_ACCOUNT":
1071
+ return executeUpgradeSmartAccount(action);
1072
+ case "GRANT_PERMISSIONS":
1073
+ return executeGrantPermissions(action);
583
1074
  default:
584
1075
  return {
585
1076
  actionId: action.id,
@@ -589,18 +1080,18 @@ function useAuthorizationExecutor() {
589
1080
  };
590
1081
  }
591
1082
  },
592
- [executeOpenProvider, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
1083
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeUpgradeSmartAccount, executeGrantPermissions]
593
1084
  );
594
1085
  const executeSession = useCallback(
595
1086
  async (transfer) => {
596
1087
  if (executingRef.current) return;
597
1088
  executingRef.current = true;
598
1089
  if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
599
- setError("No authorization sessions available.");
600
1090
  executingRef.current = false;
601
- return;
1091
+ throw new Error("No authorization sessions available.");
602
1092
  }
603
1093
  const sessionId = transfer.authorizationSessions[0].id;
1094
+ sessionIdRef.current = sessionId;
604
1095
  setExecuting(true);
605
1096
  setError(null);
606
1097
  setResults([]);
@@ -609,6 +1100,18 @@ function useAuthorizationExecutor() {
609
1100
  const allResults = [];
610
1101
  const completedActionIds = /* @__PURE__ */ new Set();
611
1102
  let pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
1103
+ const ACTION_POLL_INTERVAL_MS = 500;
1104
+ const ACTION_POLL_MAX_RETRIES = 20;
1105
+ let actionPollRetries = 0;
1106
+ while (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED" && actionPollRetries < ACTION_POLL_MAX_RETRIES) {
1107
+ await new Promise((r) => setTimeout(r, ACTION_POLL_INTERVAL_MS));
1108
+ currentSession = await fetchAuthorizationSession(apiBaseUrl, sessionId);
1109
+ pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
1110
+ actionPollRetries++;
1111
+ }
1112
+ if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
1113
+ throw new Error("Authorization actions were not created in time. Please try again.");
1114
+ }
612
1115
  while (pendingActions.length > 0) {
613
1116
  const action = pendingActions[0];
614
1117
  if (completedActionIds.has(action.id)) break;
@@ -616,8 +1119,7 @@ function useAuthorizationExecutor() {
616
1119
  if (result.status === "error") {
617
1120
  allResults.push(result);
618
1121
  setResults([...allResults]);
619
- setError(result.message);
620
- break;
1122
+ throw new Error(result.message);
621
1123
  }
622
1124
  completedActionIds.add(action.id);
623
1125
  const updatedSession = await reportActionCompletion(
@@ -629,7 +1131,6 @@ function useAuthorizationExecutor() {
629
1131
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
630
1132
  if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
631
1133
  const chainResults = [result];
632
- let chainBroken = false;
633
1134
  while (pendingActions.length > 0) {
634
1135
  const nextAction = pendingActions[0];
635
1136
  const nextResult = await executeAction(nextAction);
@@ -637,9 +1138,7 @@ function useAuthorizationExecutor() {
637
1138
  chainResults.push(nextResult);
638
1139
  allResults.push(...chainResults);
639
1140
  setResults([...allResults]);
640
- setError(nextResult.message);
641
- chainBroken = true;
642
- break;
1141
+ throw new Error(nextResult.message);
643
1142
  }
644
1143
  completedActionIds.add(nextAction.id);
645
1144
  const nextSession = await reportActionCompletion(
@@ -651,7 +1150,6 @@ function useAuthorizationExecutor() {
651
1150
  chainResults.push(nextResult);
652
1151
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
653
1152
  }
654
- if (chainBroken) break;
655
1153
  allResults.push(...chainResults);
656
1154
  setResults([...allResults]);
657
1155
  continue;
@@ -660,15 +1158,113 @@ function useAuthorizationExecutor() {
660
1158
  setResults([...allResults]);
661
1159
  }
662
1160
  } catch (err) {
663
- setError(err instanceof Error ? err.message : "Authorization failed");
1161
+ const msg = err instanceof Error ? err.message : "Authorization failed";
1162
+ setError(msg);
1163
+ throw err;
664
1164
  } finally {
1165
+ setCurrentAction(null);
665
1166
  setExecuting(false);
666
1167
  executingRef.current = false;
667
1168
  }
668
1169
  },
669
1170
  [apiBaseUrl, executeAction]
670
1171
  );
671
- return { executing, results, error, executeSession };
1172
+ return {
1173
+ executing,
1174
+ results,
1175
+ error,
1176
+ currentAction,
1177
+ pendingSelectSource,
1178
+ resolveSelectSource,
1179
+ executeSession
1180
+ };
1181
+ }
1182
+ function useTransferSigning(pollIntervalMs = 2e3) {
1183
+ const { apiBaseUrl } = useSwypeConfig();
1184
+ const { getAccessToken } = usePrivy();
1185
+ const [signing, setSigning] = useState(false);
1186
+ const [signPayload, setSignPayload] = useState(null);
1187
+ const [error, setError] = useState(null);
1188
+ const signTransfer2 = useCallback(
1189
+ async (transferId) => {
1190
+ setSigning(true);
1191
+ setError(null);
1192
+ setSignPayload(null);
1193
+ try {
1194
+ const token = await getAccessToken();
1195
+ if (!token) {
1196
+ throw new Error("Could not get access token");
1197
+ }
1198
+ const MAX_POLLS = 60;
1199
+ let payload = null;
1200
+ for (let i = 0; i < MAX_POLLS; i++) {
1201
+ const transfer = await fetchTransfer(apiBaseUrl, token, transferId);
1202
+ if (transfer.signPayload) {
1203
+ payload = transfer.signPayload;
1204
+ setSignPayload(payload);
1205
+ break;
1206
+ }
1207
+ if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
1208
+ throw new Error(`Unexpected transfer status: ${transfer.status}`);
1209
+ }
1210
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1211
+ }
1212
+ if (!payload) {
1213
+ throw new Error("Timed out waiting for sign payload. Please try again.");
1214
+ }
1215
+ const userOpHashHex = payload.userOpHash;
1216
+ const hashBytes = new Uint8Array(
1217
+ (userOpHashHex.startsWith("0x") ? userOpHashHex.slice(2) : userOpHashHex).match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
1218
+ );
1219
+ const assertion = await navigator.credentials.get({
1220
+ publicKey: {
1221
+ challenge: hashBytes,
1222
+ rpId: window.location.hostname,
1223
+ userVerification: "required",
1224
+ timeout: 6e4
1225
+ }
1226
+ });
1227
+ if (!assertion) {
1228
+ throw new Error("Passkey authentication was cancelled.");
1229
+ }
1230
+ const response = assertion.response;
1231
+ const signature = btoa(
1232
+ String.fromCharCode(...new Uint8Array(response.signature))
1233
+ );
1234
+ const authenticatorData = btoa(
1235
+ String.fromCharCode(
1236
+ ...new Uint8Array(response.authenticatorData)
1237
+ )
1238
+ );
1239
+ const clientDataJSON = btoa(
1240
+ String.fromCharCode(
1241
+ ...new Uint8Array(response.clientDataJSON)
1242
+ )
1243
+ );
1244
+ const signedUserOp = {
1245
+ ...payload.userOp,
1246
+ signature,
1247
+ authenticatorData,
1248
+ clientDataJSON
1249
+ };
1250
+ const updatedTransfer = await signTransfer(
1251
+ apiBaseUrl,
1252
+ token,
1253
+ transferId,
1254
+ signedUserOp
1255
+ );
1256
+ return updatedTransfer;
1257
+ } catch (err) {
1258
+ const msg = err instanceof Error ? err.message : "Failed to sign transfer";
1259
+ setError(msg);
1260
+ throw err;
1261
+ } finally {
1262
+ setSigning(false);
1263
+ }
1264
+ },
1265
+ [apiBaseUrl, getAccessToken, pollIntervalMs]
1266
+ );
1267
+ return { signing, signPayload, error, signTransfer: signTransfer2 };
672
1268
  }
673
1269
  function Spinner({ size = 40, label }) {
674
1270
  const { tokens } = useSwypeConfig();
@@ -786,163 +1382,232 @@ function ProviderCard({ provider, selected, onClick }) {
786
1382
  }
787
1383
  );
788
1384
  }
789
- function AccountWalletSelector({
1385
+ function AccountDropdown({
790
1386
  accounts,
791
- selection,
792
- onSelect
1387
+ selectedAccountId,
1388
+ onSelect,
1389
+ selectedWalletId,
1390
+ onWalletSelect
793
1391
  }) {
794
1392
  const { tokens } = useSwypeConfig();
795
- const [expandedAccountId, setExpandedAccountId] = useState(null);
796
- const activeAccounts = accounts.filter(
797
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
1393
+ const [open, setOpen] = useState(false);
1394
+ const containerRef = useRef(null);
1395
+ const selected = accounts.find((a) => a.id === selectedAccountId);
1396
+ const selectedWallet = selected?.wallets.find(
1397
+ (w) => w.id === selectedWalletId
798
1398
  );
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
- );
1399
+ useEffect(() => {
1400
+ if (!open) return;
1401
+ const handleClick = (e) => {
1402
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
1403
+ setOpen(false);
828
1404
  }
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",
1405
+ };
1406
+ document.addEventListener("mousedown", handleClick);
1407
+ return () => document.removeEventListener("mousedown", handleClick);
1408
+ }, [open]);
1409
+ if (accounts.length === 0) return null;
1410
+ const hasMultipleChoices = accounts.length > 1 || accounts.length === 1 && onWalletSelect && accounts[0].wallets.filter((w) => w.balance.available.amount > 0).length > 1;
1411
+ if (!hasMultipleChoices) {
1412
+ return /* @__PURE__ */ jsxs(
1413
+ "div",
838
1414
  {
839
1415
  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"
1416
+ display: "flex",
1417
+ alignItems: "center",
1418
+ gap: "6px",
1419
+ fontSize: "0.85rem",
1420
+ color: tokens.textSecondary
847
1421
  },
848
- children: "Or use a connected account"
1422
+ children: [
1423
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: accounts[0].name }),
1424
+ (selectedWallet ?? accounts[0].wallets[0]) && /* @__PURE__ */ jsx(
1425
+ "span",
1426
+ {
1427
+ style: {
1428
+ fontSize: "0.75rem",
1429
+ color: tokens.textMuted,
1430
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1431
+ },
1432
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : accounts[0].wallets[0]?.name
1433
+ }
1434
+ )
1435
+ ]
849
1436
  }
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: [
1437
+ );
1438
+ }
1439
+ const getAccountBalance = (account) => {
1440
+ let total = 0;
1441
+ for (const w of account.wallets) {
1442
+ total += w.balance.available.amount;
1443
+ }
1444
+ return total > 0 ? `$${total.toFixed(2)}` : "";
1445
+ };
1446
+ const hasActiveWallet = (account) => account.wallets.some((w) => w.status === "ACTIVE");
1447
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [
1448
+ /* @__PURE__ */ jsxs(
1449
+ "button",
1450
+ {
1451
+ onClick: () => setOpen(!open),
1452
+ style: {
1453
+ display: "flex",
1454
+ alignItems: "center",
1455
+ gap: "6px",
1456
+ background: "transparent",
1457
+ border: "none",
1458
+ cursor: "pointer",
1459
+ padding: "4px 0",
1460
+ color: tokens.textSecondary,
1461
+ fontFamily: "inherit",
1462
+ fontSize: "0.85rem",
1463
+ outline: "none"
1464
+ },
1465
+ children: [
1466
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: tokens.text }, children: selected?.name ?? "Select account" }),
1467
+ (selectedWallet ?? selected?.wallets?.[0]) && /* @__PURE__ */ jsx(
1468
+ "span",
1469
+ {
1470
+ style: {
1471
+ fontSize: "0.75rem",
1472
+ color: tokens.textMuted,
1473
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1474
+ },
1475
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : selected.wallets[0].name
1476
+ }
1477
+ ),
1478
+ /* @__PURE__ */ jsx(
1479
+ "svg",
1480
+ {
1481
+ width: "12",
1482
+ height: "12",
1483
+ viewBox: "0 0 24 24",
1484
+ fill: "none",
1485
+ style: {
1486
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1487
+ transition: "transform 0.15s ease",
1488
+ flexShrink: 0
1489
+ },
1490
+ children: /* @__PURE__ */ jsx(
1491
+ "path",
1492
+ {
1493
+ d: "M7 10l5 5 5-5",
1494
+ stroke: tokens.textMuted,
1495
+ strokeWidth: "2",
1496
+ strokeLinecap: "round",
1497
+ strokeLinejoin: "round"
1498
+ }
1499
+ )
1500
+ }
1501
+ )
1502
+ ]
1503
+ }
1504
+ ),
1505
+ open && /* @__PURE__ */ jsx(
1506
+ "div",
1507
+ {
1508
+ style: {
1509
+ position: "absolute",
1510
+ top: "100%",
1511
+ left: 0,
1512
+ right: 0,
1513
+ marginTop: "4px",
1514
+ background: tokens.bgCard,
1515
+ border: `1px solid ${tokens.border}`,
1516
+ borderRadius: tokens.radius,
1517
+ boxShadow: tokens.shadowLg,
1518
+ zIndex: 50,
1519
+ overflow: "hidden",
1520
+ minWidth: "220px"
1521
+ },
1522
+ children: accounts.map((account) => {
1523
+ const isAcctSelected = account.id === selectedAccountId;
1524
+ const balance = getAccountBalance(account);
1525
+ const active = hasActiveWallet(account);
1526
+ const walletsWithBalance = account.wallets.filter(
1527
+ (w) => w.balance.available.amount > 0
1528
+ );
1529
+ const showWallets = onWalletSelect && walletsWithBalance.length > 0;
1530
+ return /* @__PURE__ */ jsxs("div", { children: [
865
1531
  /* @__PURE__ */ jsxs(
866
1532
  "button",
867
1533
  {
868
- onClick: () => handleAccountClick(account.id),
1534
+ onClick: () => {
1535
+ onSelect(account.id);
1536
+ if (!showWallets) setOpen(false);
1537
+ },
869
1538
  style: {
870
- width: "100%",
871
1539
  display: "flex",
872
1540
  alignItems: "center",
873
1541
  justifyContent: "space-between",
874
- padding: "12px 14px",
875
- background: hasSelection ? tokens.accent + "10" : tokens.bgInput,
1542
+ width: "100%",
1543
+ padding: "10px 14px",
1544
+ background: isAcctSelected && !selectedWalletId ? tokens.accent + "12" : "transparent",
876
1545
  border: "none",
1546
+ borderBottom: showWallets ? "none" : `1px solid ${tokens.border}`,
877
1547
  cursor: "pointer",
878
1548
  color: tokens.text,
879
1549
  fontFamily: "inherit",
880
- fontSize: "0.9rem",
881
- fontWeight: 500,
1550
+ fontSize: "0.85rem",
882
1551
  textAlign: "left",
883
1552
  outline: "none",
884
- transition: "background 0.15s ease"
1553
+ transition: "background 0.1s ease"
885
1554
  },
886
1555
  children: [
887
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [
888
- /* @__PURE__ */ jsx(
1556
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
1557
+ /* @__PURE__ */ jsxs(
889
1558
  "div",
890
1559
  {
891
1560
  style: {
892
- width: 28,
893
- height: 28,
894
- borderRadius: "6px",
895
- background: tokens.accent + "25",
896
1561
  display: "flex",
897
1562
  alignItems: "center",
898
- justifyContent: "center",
899
- fontSize: "0.75rem",
900
- fontWeight: 700,
901
- color: tokens.accent,
902
- flexShrink: 0
1563
+ gap: "6px"
903
1564
  },
904
- children: account.name.charAt(0).toUpperCase()
1565
+ children: [
1566
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: account.name }),
1567
+ active && /* @__PURE__ */ jsx(
1568
+ "span",
1569
+ {
1570
+ style: {
1571
+ fontSize: "0.6rem",
1572
+ fontWeight: 600,
1573
+ color: tokens.success,
1574
+ background: tokens.successBg,
1575
+ padding: "1px 5px",
1576
+ borderRadius: "3px",
1577
+ textTransform: "uppercase",
1578
+ letterSpacing: "0.03em"
1579
+ },
1580
+ children: "Active"
1581
+ }
1582
+ )
1583
+ ]
905
1584
  }
906
1585
  ),
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
- ] })
1586
+ balance && /* @__PURE__ */ jsx(
1587
+ "div",
1588
+ {
1589
+ style: {
1590
+ fontSize: "0.75rem",
1591
+ color: tokens.textMuted,
1592
+ marginTop: "2px"
1593
+ },
1594
+ children: balance
1595
+ }
1596
+ )
925
1597
  ] }),
926
- /* @__PURE__ */ jsx(
1598
+ isAcctSelected && !selectedWalletId && /* @__PURE__ */ jsx(
927
1599
  "svg",
928
1600
  {
929
1601
  width: "14",
930
1602
  height: "14",
931
1603
  viewBox: "0 0 24 24",
932
1604
  fill: "none",
933
- style: {
934
- transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
935
- transition: "transform 0.2s ease",
936
- flexShrink: 0
937
- },
1605
+ style: { flexShrink: 0, marginLeft: "8px" },
938
1606
  children: /* @__PURE__ */ jsx(
939
1607
  "path",
940
1608
  {
941
- d: "M7 10l5 5 5-5",
942
- stroke: tokens.textMuted,
943
- strokeWidth: "2",
944
- strokeLinecap: "round",
945
- strokeLinejoin: "round"
1609
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1610
+ fill: tokens.accent
946
1611
  }
947
1612
  )
948
1613
  }
@@ -950,128 +1615,383 @@ function AccountWalletSelector({
950
1615
  ]
951
1616
  }
952
1617
  ),
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",
1618
+ showWallets && walletsWithBalance.map((wallet, wIdx) => {
1619
+ const isWalletSelected = isAcctSelected && wallet.id === selectedWalletId;
1620
+ const walletBal = wallet.balance.available.amount > 0 ? `$${wallet.balance.available.amount.toFixed(2)}` : "";
1621
+ const isLastWallet = wIdx === walletsWithBalance.length - 1;
1622
+ return /* @__PURE__ */ jsxs(
1623
+ "button",
1624
+ {
1625
+ onClick: () => {
1626
+ onWalletSelect(account.id, wallet.id);
1627
+ setOpen(false);
1628
+ },
1629
+ style: {
1630
+ display: "flex",
1631
+ alignItems: "center",
1632
+ justifyContent: "space-between",
1633
+ width: "100%",
1634
+ padding: "8px 14px 8px 28px",
1635
+ background: isWalletSelected ? tokens.accent + "12" : "transparent",
1636
+ border: "none",
1637
+ borderBottom: isLastWallet ? `1px solid ${tokens.border}` : "none",
1638
+ cursor: "pointer",
1639
+ color: tokens.text,
1640
+ fontFamily: "inherit",
1641
+ fontSize: "0.8rem",
1642
+ textAlign: "left",
1643
+ outline: "none",
1644
+ transition: "background 0.1s ease"
1645
+ },
1646
+ children: [
1647
+ /* @__PURE__ */ jsxs(
1648
+ "div",
1649
+ {
1650
+ style: {
1651
+ display: "flex",
1652
+ alignItems: "center",
1653
+ gap: "6px",
1654
+ minWidth: 0,
1655
+ flex: 1
1656
+ },
1657
+ children: [
1658
+ /* @__PURE__ */ jsx(
1659
+ "span",
987
1660
  {
988
1661
  style: {
989
- display: "flex",
990
- alignItems: "center",
991
- gap: "6px"
1662
+ fontWeight: 500,
1663
+ fontSize: "0.8rem"
992
1664
  },
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
- ]
1665
+ children: wallet.chain.name
1009
1666
  }
1010
1667
  ),
1011
1668
  /* @__PURE__ */ jsx(
1012
- "div",
1669
+ "span",
1670
+ {
1671
+ style: {
1672
+ fontSize: "0.7rem",
1673
+ color: tokens.textMuted,
1674
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1675
+ },
1676
+ children: wallet.name
1677
+ }
1678
+ ),
1679
+ walletBal && /* @__PURE__ */ jsx(
1680
+ "span",
1013
1681
  {
1014
1682
  style: {
1015
- fontSize: "0.75rem",
1016
- color: tokens.textSecondary,
1017
- marginTop: "3px"
1683
+ fontSize: "0.7rem",
1684
+ color: tokens.textMuted,
1685
+ marginLeft: "auto"
1018
1686
  },
1019
- children: formatBalance(wallet)
1687
+ children: walletBal
1020
1688
  }
1021
1689
  )
1022
- ] }),
1023
- isSelected && /* @__PURE__ */ jsx(
1024
- "svg",
1690
+ ]
1691
+ }
1692
+ ),
1693
+ isWalletSelected && /* @__PURE__ */ jsx(
1694
+ "svg",
1695
+ {
1696
+ width: "12",
1697
+ height: "12",
1698
+ viewBox: "0 0 24 24",
1699
+ fill: "none",
1700
+ style: {
1701
+ flexShrink: 0,
1702
+ marginLeft: "8px"
1703
+ },
1704
+ children: /* @__PURE__ */ jsx(
1705
+ "path",
1025
1706
  {
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
- )
1707
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1708
+ fill: tokens.accent
1038
1709
  }
1039
1710
  )
1040
- ]
1041
- },
1042
- wallet.id
1043
- );
1044
- })
1045
- }
1046
- )
1047
- ]
1048
- },
1049
- account.id
1050
- );
1051
- }) }),
1052
- selection && /* @__PURE__ */ jsxs(
1053
- "div",
1711
+ }
1712
+ )
1713
+ ]
1714
+ },
1715
+ wallet.id
1716
+ );
1717
+ })
1718
+ ] }, account.id);
1719
+ })
1720
+ }
1721
+ )
1722
+ ] });
1723
+ }
1724
+ var ASSETS = ["USDC", "USDT"];
1725
+ function AdvancedSettings({
1726
+ settings,
1727
+ onChange,
1728
+ chains,
1729
+ providers,
1730
+ onConnectNewAccount,
1731
+ connectingNewAccount
1732
+ }) {
1733
+ const { tokens } = useSwypeConfig();
1734
+ const [open, setOpen] = useState(false);
1735
+ const [showProviders, setShowProviders] = useState(false);
1736
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: "12px" }, children: [
1737
+ /* @__PURE__ */ jsxs(
1738
+ "button",
1054
1739
  {
1740
+ onClick: () => setOpen(!open),
1055
1741
  style: {
1056
- marginTop: "8px",
1057
- fontSize: "0.8rem",
1058
- color: tokens.accent,
1059
1742
  display: "flex",
1060
1743
  alignItems: "center",
1061
- gap: "6px"
1744
+ gap: "6px",
1745
+ background: "transparent",
1746
+ border: "none",
1747
+ cursor: "pointer",
1748
+ padding: "4px 0",
1749
+ color: tokens.textMuted,
1750
+ fontFamily: "inherit",
1751
+ fontSize: "0.8rem",
1752
+ fontWeight: 500,
1753
+ outline: "none",
1754
+ letterSpacing: "0.02em"
1062
1755
  },
1063
1756
  children: [
1064
- /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
1065
- "path",
1757
+ /* @__PURE__ */ jsx(
1758
+ "svg",
1066
1759
  {
1067
- d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1068
- fill: tokens.accent
1760
+ width: "10",
1761
+ height: "10",
1762
+ viewBox: "0 0 24 24",
1763
+ fill: "none",
1764
+ style: {
1765
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1766
+ transition: "transform 0.15s ease"
1767
+ },
1768
+ children: /* @__PURE__ */ jsx(
1769
+ "path",
1770
+ {
1771
+ d: "M7 10l5 5 5-5",
1772
+ stroke: tokens.textMuted,
1773
+ strokeWidth: "2.5",
1774
+ strokeLinecap: "round",
1775
+ strokeLinejoin: "round"
1776
+ }
1777
+ )
1778
+ }
1779
+ ),
1780
+ "Advanced options"
1781
+ ]
1782
+ }
1783
+ ),
1784
+ open && /* @__PURE__ */ jsxs(
1785
+ "div",
1786
+ {
1787
+ style: {
1788
+ marginTop: "10px",
1789
+ padding: "14px",
1790
+ background: tokens.bgInput,
1791
+ borderRadius: tokens.radius,
1792
+ border: `1px solid ${tokens.border}`
1793
+ },
1794
+ children: [
1795
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
1796
+ /* @__PURE__ */ jsx(
1797
+ "label",
1798
+ {
1799
+ style: {
1800
+ display: "block",
1801
+ fontSize: "0.7rem",
1802
+ fontWeight: 600,
1803
+ color: tokens.textMuted,
1804
+ textTransform: "uppercase",
1805
+ letterSpacing: "0.05em",
1806
+ marginBottom: "6px"
1807
+ },
1808
+ children: "Asset"
1809
+ }
1810
+ ),
1811
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px" }, children: ASSETS.map((asset) => {
1812
+ const isSelected = settings.asset === asset;
1813
+ return /* @__PURE__ */ jsx(
1814
+ "button",
1815
+ {
1816
+ onClick: () => onChange({
1817
+ ...settings,
1818
+ asset: isSelected ? null : asset
1819
+ }),
1820
+ style: {
1821
+ padding: "6px 14px",
1822
+ fontSize: "0.8rem",
1823
+ fontWeight: 600,
1824
+ fontFamily: "inherit",
1825
+ borderRadius: "6px",
1826
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1827
+ background: isSelected ? tokens.accent + "18" : "transparent",
1828
+ color: isSelected ? tokens.accent : tokens.text,
1829
+ cursor: "pointer",
1830
+ outline: "none",
1831
+ transition: "all 0.12s ease"
1832
+ },
1833
+ children: asset
1834
+ },
1835
+ asset
1836
+ );
1837
+ }) })
1838
+ ] }),
1839
+ chains.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
1840
+ /* @__PURE__ */ jsx(
1841
+ "label",
1842
+ {
1843
+ style: {
1844
+ display: "block",
1845
+ fontSize: "0.7rem",
1846
+ fontWeight: 600,
1847
+ color: tokens.textMuted,
1848
+ textTransform: "uppercase",
1849
+ letterSpacing: "0.05em",
1850
+ marginBottom: "6px"
1851
+ },
1852
+ children: "Chain"
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: chains.map((chain) => {
1856
+ const isSelected = settings.chain === chain.name;
1857
+ return /* @__PURE__ */ jsx(
1858
+ "button",
1859
+ {
1860
+ onClick: () => onChange({
1861
+ ...settings,
1862
+ chain: isSelected ? null : chain.name
1863
+ }),
1864
+ style: {
1865
+ padding: "6px 14px",
1866
+ fontSize: "0.8rem",
1867
+ fontWeight: 600,
1868
+ fontFamily: "inherit",
1869
+ borderRadius: "6px",
1870
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1871
+ background: isSelected ? tokens.accent + "18" : "transparent",
1872
+ color: isSelected ? tokens.accent : tokens.text,
1873
+ cursor: "pointer",
1874
+ outline: "none",
1875
+ transition: "all 0.12s ease"
1876
+ },
1877
+ children: chain.name
1878
+ },
1879
+ chain.id
1880
+ );
1881
+ }) })
1882
+ ] }),
1883
+ /* @__PURE__ */ jsx("div", { children: !showProviders ? /* @__PURE__ */ jsxs(
1884
+ "button",
1885
+ {
1886
+ onClick: () => setShowProviders(true),
1887
+ disabled: connectingNewAccount,
1888
+ style: {
1889
+ display: "flex",
1890
+ alignItems: "center",
1891
+ gap: "6px",
1892
+ background: "transparent",
1893
+ border: `1px dashed ${tokens.border}`,
1894
+ borderRadius: tokens.radius,
1895
+ padding: "10px 14px",
1896
+ width: "100%",
1897
+ cursor: connectingNewAccount ? "not-allowed" : "pointer",
1898
+ color: tokens.textSecondary,
1899
+ fontFamily: "inherit",
1900
+ fontSize: "0.825rem",
1901
+ fontWeight: 500,
1902
+ outline: "none",
1903
+ opacity: connectingNewAccount ? 0.5 : 1,
1904
+ transition: "opacity 0.1s ease"
1905
+ },
1906
+ children: [
1907
+ /* @__PURE__ */ jsx(
1908
+ "svg",
1909
+ {
1910
+ width: "14",
1911
+ height: "14",
1912
+ viewBox: "0 0 24 24",
1913
+ fill: "none",
1914
+ children: /* @__PURE__ */ jsx(
1915
+ "path",
1916
+ {
1917
+ d: "M12 5v14M5 12h14",
1918
+ stroke: tokens.textMuted,
1919
+ strokeWidth: "2",
1920
+ strokeLinecap: "round"
1921
+ }
1922
+ )
1923
+ }
1924
+ ),
1925
+ "Connect new account"
1926
+ ]
1069
1927
  }
1070
- ) }),
1071
- "Using ",
1072
- selection.walletName,
1073
- " on ",
1074
- selection.chainName
1928
+ ) : /* @__PURE__ */ jsxs("div", { children: [
1929
+ /* @__PURE__ */ jsxs(
1930
+ "div",
1931
+ {
1932
+ style: {
1933
+ display: "flex",
1934
+ alignItems: "center",
1935
+ justifyContent: "space-between",
1936
+ marginBottom: "8px"
1937
+ },
1938
+ children: [
1939
+ /* @__PURE__ */ jsx(
1940
+ "label",
1941
+ {
1942
+ style: {
1943
+ fontSize: "0.7rem",
1944
+ fontWeight: 600,
1945
+ color: tokens.textMuted,
1946
+ textTransform: "uppercase",
1947
+ letterSpacing: "0.05em"
1948
+ },
1949
+ children: "Select provider"
1950
+ }
1951
+ ),
1952
+ /* @__PURE__ */ jsx(
1953
+ "button",
1954
+ {
1955
+ onClick: () => setShowProviders(false),
1956
+ style: {
1957
+ background: "transparent",
1958
+ border: "none",
1959
+ cursor: "pointer",
1960
+ color: tokens.textMuted,
1961
+ fontSize: "0.75rem",
1962
+ fontFamily: "inherit",
1963
+ outline: "none",
1964
+ padding: "2px 4px"
1965
+ },
1966
+ children: "Cancel"
1967
+ }
1968
+ )
1969
+ ]
1970
+ }
1971
+ ),
1972
+ /* @__PURE__ */ jsx(
1973
+ "div",
1974
+ {
1975
+ style: {
1976
+ display: "flex",
1977
+ flexDirection: "column",
1978
+ gap: "6px"
1979
+ },
1980
+ children: providers.map((p) => /* @__PURE__ */ jsx(
1981
+ ProviderCard,
1982
+ {
1983
+ provider: p,
1984
+ selected: false,
1985
+ onClick: () => {
1986
+ onConnectNewAccount(p.id);
1987
+ setShowProviders(false);
1988
+ }
1989
+ },
1990
+ p.id
1991
+ ))
1992
+ }
1993
+ )
1994
+ ] }) })
1075
1995
  ]
1076
1996
  }
1077
1997
  )
@@ -1083,69 +2003,135 @@ function isMobile() {
1083
2003
  navigator.userAgent
1084
2004
  );
1085
2005
  }
2006
+ function computeSmartDefaults(accts, transferAmount) {
2007
+ if (accts.length === 0) return null;
2008
+ for (const acct of accts) {
2009
+ for (const wallet of acct.wallets) {
2010
+ if (wallet.status === "ACTIVE") {
2011
+ const bestSource = wallet.sources.find(
2012
+ (s) => s.balance.available.amount >= transferAmount
2013
+ );
2014
+ if (bestSource) {
2015
+ return { accountId: acct.id, walletId: wallet.id };
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ let bestAccount = null;
2021
+ let bestWallet = null;
2022
+ let bestBalance = -1;
2023
+ for (const acct of accts) {
2024
+ for (const wallet of acct.wallets) {
2025
+ const walletBal = wallet.balance.available.amount;
2026
+ if (walletBal > bestBalance) {
2027
+ bestBalance = walletBal;
2028
+ bestAccount = acct;
2029
+ bestWallet = wallet;
2030
+ }
2031
+ }
2032
+ }
2033
+ if (bestAccount) {
2034
+ return {
2035
+ accountId: bestAccount.id,
2036
+ walletId: bestWallet?.id ?? null
2037
+ };
2038
+ }
2039
+ return { accountId: accts[0].id, walletId: null };
2040
+ }
1086
2041
  function SwypePayment({
1087
2042
  destination,
1088
2043
  onComplete,
1089
2044
  onError
1090
2045
  }) {
1091
- const { apiBaseUrl, tokens } = useSwypeConfig();
2046
+ const { apiBaseUrl, tokens, depositAmount } = useSwypeConfig();
1092
2047
  const { ready, authenticated, login, getAccessToken } = usePrivy();
1093
2048
  const [step, setStep] = useState("login");
1094
2049
  const [error, setError] = useState(null);
1095
2050
  const [providers, setProviders] = useState([]);
1096
2051
  const [accounts, setAccounts] = useState([]);
2052
+ const [chains, setChains] = useState([]);
1097
2053
  const [loadingData, setLoadingData] = useState(false);
1098
- const [selectedProviderId, setSelectedProviderId] = useState(
1099
- null
2054
+ const [selectedAccountId, setSelectedAccountId] = useState(null);
2055
+ const [selectedWalletId, setSelectedWalletId] = useState(null);
2056
+ const [selectedProviderId, setSelectedProviderId] = useState(null);
2057
+ const [connectingNewAccount, setConnectingNewAccount] = useState(false);
2058
+ const [amount, setAmount] = useState(
2059
+ depositAmount != null ? depositAmount.toString() : ""
1100
2060
  );
1101
- const [walletSelection, setWalletSelection] = useState(null);
1102
- const [amount, setAmount] = useState("0.10");
1103
- const [topUpBalance, setTopUpBalance] = useState("");
2061
+ const [advancedSettings, setAdvancedSettings] = useState({
2062
+ asset: null,
2063
+ chain: null
2064
+ });
1104
2065
  const [transfer, setTransfer] = useState(null);
1105
2066
  const [creatingTransfer, setCreatingTransfer] = useState(false);
1106
2067
  const [mobileFlow, setMobileFlow] = useState(false);
1107
2068
  const pollingTransferIdRef = useRef(null);
1108
2069
  const authExecutor = useAuthorizationExecutor();
1109
2070
  const polling = useTransferPolling();
1110
- const sourceType = walletSelection ? "walletId" : "providerId";
1111
- const sourceId = walletSelection?.walletId ?? selectedProviderId ?? "";
2071
+ const transferSigning = useTransferSigning();
2072
+ const sourceType = connectingNewAccount ? "providerId" : selectedWalletId ? "walletId" : selectedAccountId ? "accountId" : "providerId";
2073
+ const sourceId = connectingNewAccount ? selectedProviderId ?? "" : selectedWalletId ? selectedWalletId : selectedAccountId ? selectedAccountId : selectedProviderId ?? "";
2074
+ useEffect(() => {
2075
+ if (depositAmount != null) {
2076
+ setAmount(depositAmount.toString());
2077
+ }
2078
+ }, [depositAmount]);
1112
2079
  useEffect(() => {
1113
2080
  if (ready && authenticated && step === "login") {
1114
- setStep("select-source");
2081
+ if (depositAmount != null && depositAmount > 0) {
2082
+ setStep("ready");
2083
+ } else {
2084
+ setStep("enter-amount");
2085
+ }
1115
2086
  }
1116
- }, [ready, authenticated, step]);
2087
+ }, [ready, authenticated, step, depositAmount]);
2088
+ const loadingDataRef = useRef(false);
1117
2089
  useEffect(() => {
1118
- if (step !== "select-source") return;
1119
- if (providers.length > 0) return;
2090
+ if (!authenticated) return;
2091
+ if (accounts.length > 0 || loadingDataRef.current) return;
1120
2092
  let cancelled = false;
2093
+ loadingDataRef.current = true;
1121
2094
  const load = async () => {
1122
2095
  setLoadingData(true);
1123
2096
  setError(null);
1124
2097
  try {
1125
2098
  const token = await getAccessToken();
1126
2099
  if (!token) throw new Error("Not authenticated");
1127
- const [prov, accts] = await Promise.all([
2100
+ const [prov, accts, chn] = await Promise.all([
1128
2101
  fetchProviders(apiBaseUrl, token),
1129
- fetchAccounts(apiBaseUrl, token)
2102
+ fetchAccounts(apiBaseUrl, token),
2103
+ fetchChains(apiBaseUrl, token)
1130
2104
  ]);
1131
2105
  if (cancelled) return;
1132
2106
  setProviders(prov);
1133
2107
  setAccounts(accts);
1134
- if (prov.length > 0) setSelectedProviderId(prov[0].id);
2108
+ setChains(chn);
2109
+ const parsedAmt = depositAmount != null ? depositAmount : 0;
2110
+ const defaults = computeSmartDefaults(accts, parsedAmt);
2111
+ if (defaults) {
2112
+ setSelectedAccountId(defaults.accountId);
2113
+ setSelectedWalletId(defaults.walletId);
2114
+ } else if (prov.length > 0) {
2115
+ setSelectedProviderId(prov[0].id);
2116
+ }
1135
2117
  } catch (err) {
1136
2118
  if (!cancelled) {
1137
2119
  const msg = err instanceof Error ? err.message : "Failed to load data";
1138
2120
  setError(msg);
1139
2121
  }
1140
2122
  } finally {
1141
- if (!cancelled) setLoadingData(false);
2123
+ if (!cancelled) {
2124
+ setLoadingData(false);
2125
+ loadingDataRef.current = false;
2126
+ }
1142
2127
  }
1143
2128
  };
1144
2129
  load();
1145
2130
  return () => {
1146
2131
  cancelled = true;
2132
+ loadingDataRef.current = false;
1147
2133
  };
1148
- }, [step, providers.length, apiBaseUrl, getAccessToken]);
2134
+ }, [authenticated, accounts.length, apiBaseUrl, getAccessToken]);
1149
2135
  useEffect(() => {
1150
2136
  if (!polling.transfer) return;
1151
2137
  if (polling.transfer.status === "COMPLETED") {
@@ -1172,6 +2158,61 @@ function SwypePayment({
1172
2158
  document.removeEventListener("visibilitychange", handleVisibility);
1173
2159
  };
1174
2160
  }, [mobileFlow, polling]);
2161
+ useEffect(() => {
2162
+ if (!authExecutor.pendingSelectSource) return;
2163
+ const action = authExecutor.pendingSelectSource;
2164
+ const hasAdvancedOverride = advancedSettings.asset !== null || advancedSettings.chain !== null;
2165
+ if (hasAdvancedOverride) {
2166
+ const options = action.metadata?.options ?? [];
2167
+ const recommended = action.metadata?.recommended;
2168
+ const match = options.find(
2169
+ (opt) => (advancedSettings.chain === null || opt.chainName === advancedSettings.chain) && (advancedSettings.asset === null || opt.tokenSymbol === advancedSettings.asset)
2170
+ );
2171
+ if (match) {
2172
+ authExecutor.resolveSelectSource({
2173
+ chainName: match.chainName,
2174
+ tokenSymbol: match.tokenSymbol
2175
+ });
2176
+ } else if (recommended) {
2177
+ authExecutor.resolveSelectSource({
2178
+ chainName: recommended.chainName,
2179
+ tokenSymbol: recommended.tokenSymbol
2180
+ });
2181
+ }
2182
+ } else {
2183
+ const options = action.metadata?.options ?? [];
2184
+ const recommended = action.metadata?.recommended;
2185
+ const selWallet = selectedWalletId ? accounts.find((a) => a.id === selectedAccountId)?.wallets.find((w) => w.id === selectedWalletId) : null;
2186
+ if (selWallet) {
2187
+ const walletMatch = options.find(
2188
+ (opt) => opt.chainName === selWallet.chain.name
2189
+ );
2190
+ if (walletMatch) {
2191
+ authExecutor.resolveSelectSource({
2192
+ chainName: walletMatch.chainName,
2193
+ tokenSymbol: walletMatch.tokenSymbol
2194
+ });
2195
+ return;
2196
+ }
2197
+ }
2198
+ if (recommended) {
2199
+ authExecutor.resolveSelectSource({
2200
+ chainName: recommended.chainName,
2201
+ tokenSymbol: recommended.tokenSymbol
2202
+ });
2203
+ } else if (options.length > 0) {
2204
+ authExecutor.resolveSelectSource({
2205
+ chainName: options[0].chainName,
2206
+ tokenSymbol: options[0].tokenSymbol
2207
+ });
2208
+ } else {
2209
+ authExecutor.resolveSelectSource({
2210
+ chainName: "Base",
2211
+ tokenSymbol: "USDC"
2212
+ });
2213
+ }
2214
+ }
2215
+ }, [authExecutor, authExecutor.pendingSelectSource, advancedSettings, selectedWalletId, selectedAccountId, accounts]);
1175
2216
  const handlePay = useCallback(async () => {
1176
2217
  const parsedAmount = parseFloat(amount);
1177
2218
  if (isNaN(parsedAmount) || parsedAmount <= 0) {
@@ -1179,7 +2220,7 @@ function SwypePayment({
1179
2220
  return;
1180
2221
  }
1181
2222
  if (!sourceId) {
1182
- setError("Select a source.");
2223
+ setError("No account or provider selected.");
1183
2224
  return;
1184
2225
  }
1185
2226
  setStep("processing");
@@ -1189,12 +2230,6 @@ function SwypePayment({
1189
2230
  try {
1190
2231
  const token = await getAccessToken();
1191
2232
  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
2233
  const t = await createTransfer(apiBaseUrl, token, {
1199
2234
  sourceType,
1200
2235
  sourceId,
@@ -1213,12 +2248,14 @@ function SwypePayment({
1213
2248
  await authExecutor.executeSession(t);
1214
2249
  }
1215
2250
  }
2251
+ const signedTransfer = await transferSigning.signTransfer(t.id);
2252
+ setTransfer(signedTransfer);
1216
2253
  polling.startPolling(t.id);
1217
2254
  } catch (err) {
1218
- const msg = err instanceof Error ? err.message : "Transfer creation failed";
2255
+ const msg = err instanceof Error ? err.message : "Transfer failed";
1219
2256
  setError(msg);
1220
2257
  onError?.(msg);
1221
- setStep("enter-amount");
2258
+ setStep("ready");
1222
2259
  } finally {
1223
2260
  setCreatingTransfer(false);
1224
2261
  }
@@ -1230,20 +2267,26 @@ function SwypePayment({
1230
2267
  apiBaseUrl,
1231
2268
  getAccessToken,
1232
2269
  authExecutor,
2270
+ transferSigning,
1233
2271
  polling,
1234
- onError,
1235
- topUpBalance
2272
+ onError
1236
2273
  ]);
1237
2274
  const handleNewPayment = () => {
1238
- setStep("select-source");
2275
+ setStep("ready");
1239
2276
  setTransfer(null);
1240
2277
  setError(null);
1241
- setAmount("0.10");
1242
- setTopUpBalance("");
2278
+ setAmount(depositAmount != null ? depositAmount.toString() : "");
1243
2279
  setMobileFlow(false);
1244
2280
  pollingTransferIdRef.current = null;
1245
- setWalletSelection(null);
1246
- if (providers.length > 0) setSelectedProviderId(providers[0].id);
2281
+ setConnectingNewAccount(false);
2282
+ setSelectedWalletId(null);
2283
+ setAdvancedSettings({ asset: null, chain: null });
2284
+ if (accounts.length > 0) setSelectedAccountId(accounts[0].id);
2285
+ };
2286
+ const handleConnectNewAccount = (providerId) => {
2287
+ setSelectedProviderId(providerId);
2288
+ setSelectedAccountId(null);
2289
+ setConnectingNewAccount(true);
1247
2290
  };
1248
2291
  const cardStyle = {
1249
2292
  background: tokens.bgCard,
@@ -1281,15 +2324,10 @@ function SwypePayment({
1281
2324
  opacity: 0.5,
1282
2325
  cursor: "not-allowed"
1283
2326
  };
1284
- const btnSecondary = {
2327
+ ({
1285
2328
  ...btnPrimary,
1286
- background: "transparent",
1287
2329
  color: tokens.textSecondary,
1288
- border: `1px solid ${tokens.border}`,
1289
- width: "auto",
1290
- flex: "0 0 auto",
1291
- padding: "14px 20px"
1292
- };
2330
+ border: `1px solid ${tokens.border}`});
1293
2331
  const errorStyle = {
1294
2332
  background: tokens.errorBg,
1295
2333
  border: `1px solid ${tokens.error}`,
@@ -1380,62 +2418,20 @@ function SwypePayment({
1380
2418
  /* @__PURE__ */ jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
1381
2419
  ] }) });
1382
2420
  }
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
2421
  if (step === "enter-amount") {
2422
+ const parsedAmount = parseFloat(amount);
2423
+ const canContinue = !isNaN(parsedAmount) && parsedAmount > 0;
2424
+ let maxSourceBalance = null;
2425
+ for (const acct of accounts) {
2426
+ for (const wallet of acct.wallets) {
2427
+ for (const source of wallet.sources) {
2428
+ const bal = source.balance.available.amount;
2429
+ if (maxSourceBalance === null || bal > maxSourceBalance) {
2430
+ maxSourceBalance = bal;
2431
+ }
2432
+ }
2433
+ }
2434
+ }
1439
2435
  return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1440
2436
  stepBadge("Enter amount"),
1441
2437
  /* @__PURE__ */ jsx("h2", { style: headingStyle, children: "How much?" }),
@@ -1451,7 +2447,7 @@ function SwypePayment({
1451
2447
  border: `1px solid ${tokens.border}`,
1452
2448
  borderRadius: tokens.radius,
1453
2449
  padding: "4px 14px 4px 4px",
1454
- marginBottom: "20px"
2450
+ marginBottom: "8px"
1455
2451
  },
1456
2452
  children: [
1457
2453
  /* @__PURE__ */ jsx(
@@ -1475,6 +2471,7 @@ function SwypePayment({
1475
2471
  step: "0.01",
1476
2472
  value: amount,
1477
2473
  onChange: (e) => setAmount(e.target.value),
2474
+ placeholder: "0.00",
1478
2475
  style: {
1479
2476
  flex: 1,
1480
2477
  background: "transparent",
@@ -1506,156 +2503,301 @@ function SwypePayment({
1506
2503
  ]
1507
2504
  }
1508
2505
  ),
1509
- /* @__PURE__ */ jsxs(
2506
+ /* @__PURE__ */ jsx(
1510
2507
  "div",
1511
2508
  {
1512
2509
  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"
2510
+ fontSize: "0.8rem",
2511
+ color: tokens.textMuted,
2512
+ marginBottom: "20px",
2513
+ paddingLeft: "2px"
1521
2514
  },
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
- ]
2515
+ children: loadingData ? /* @__PURE__ */ jsx("span", { children: "Loading balance..." }) : maxSourceBalance !== null && maxSourceBalance > 0 ? /* @__PURE__ */ jsxs("span", { children: [
2516
+ "Available: ",
2517
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600, color: tokens.textSecondary }, children: [
2518
+ "$",
2519
+ maxSourceBalance.toFixed(2)
2520
+ ] })
2521
+ ] }) : null
1574
2522
  }
1575
2523
  ),
1576
2524
  /* @__PURE__ */ jsx(
1577
- "p",
2525
+ "button",
1578
2526
  {
1579
- style: {
1580
- fontSize: "0.75rem",
1581
- color: tokens.textMuted,
1582
- margin: "-12px 0 16px 0",
1583
- lineHeight: 1.4
2527
+ style: canContinue ? btnPrimary : btnDisabled,
2528
+ disabled: !canContinue,
2529
+ onClick: () => {
2530
+ setError(null);
2531
+ setStep("ready");
1584
2532
  },
1585
- children: "Set a higher allowance to skip wallet prompts on future payments under this amount."
2533
+ children: "Continue"
1586
2534
  }
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" }),
2535
+ )
2536
+ ] });
2537
+ }
2538
+ if (step === "ready") {
2539
+ const parsedAmount = parseFloat(amount);
2540
+ const canPay = !isNaN(parsedAmount) && parsedAmount > 0 && !!sourceId && !loadingData;
2541
+ const noAccounts = !loadingData && accounts.length === 0;
2542
+ return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
2543
+ stepBadge("Review & pay"),
2544
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle, children: error }),
2545
+ loadingData ? /* @__PURE__ */ jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Loading..." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2546
+ /* @__PURE__ */ jsxs(
2547
+ "div",
2548
+ {
2549
+ style: {
2550
+ textAlign: "center",
2551
+ marginBottom: "20px"
2552
+ },
2553
+ children: [
1603
2554
  /* @__PURE__ */ jsxs(
1604
- "span",
2555
+ "div",
1605
2556
  {
1606
2557
  style: {
1607
- fontFamily: '"SF Mono", "Fira Code", monospace',
1608
- fontSize: "0.8rem"
2558
+ fontSize: "2rem",
2559
+ fontWeight: 700,
2560
+ color: tokens.text,
2561
+ lineHeight: 1.2
1609
2562
  },
1610
2563
  children: [
1611
- destination.address.slice(0, 6),
1612
- "...",
1613
- destination.address.slice(-4)
2564
+ "$",
2565
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1614
2566
  ]
1615
2567
  }
2568
+ ),
2569
+ /* @__PURE__ */ jsx(
2570
+ "button",
2571
+ {
2572
+ onClick: () => setStep("enter-amount"),
2573
+ style: {
2574
+ background: "transparent",
2575
+ border: "none",
2576
+ cursor: "pointer",
2577
+ color: tokens.textMuted,
2578
+ fontSize: "0.75rem",
2579
+ fontFamily: "inherit",
2580
+ outline: "none",
2581
+ padding: "4px 8px",
2582
+ marginTop: "4px"
2583
+ },
2584
+ children: "Change amount"
2585
+ }
1616
2586
  )
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",
2587
+ ]
2588
+ }
2589
+ ),
2590
+ /* @__PURE__ */ jsxs(
2591
+ "div",
1632
2592
  {
1633
- style: btnSecondary,
1634
- onClick: () => {
1635
- setError(null);
1636
- setStep("select-source");
2593
+ style: {
2594
+ fontSize: "0.825rem",
2595
+ color: tokens.textSecondary,
2596
+ marginBottom: "16px",
2597
+ padding: "12px 14px",
2598
+ background: tokens.bgInput,
2599
+ borderRadius: tokens.radius,
2600
+ lineHeight: 1.7
1637
2601
  },
1638
- children: "Back"
2602
+ children: [
2603
+ /* @__PURE__ */ jsxs(
2604
+ "div",
2605
+ {
2606
+ style: { display: "flex", justifyContent: "space-between" },
2607
+ children: [
2608
+ /* @__PURE__ */ jsx("span", { children: "To" }),
2609
+ /* @__PURE__ */ jsxs(
2610
+ "span",
2611
+ {
2612
+ style: {
2613
+ fontFamily: '"SF Mono", "Fira Code", monospace',
2614
+ fontSize: "0.8rem"
2615
+ },
2616
+ children: [
2617
+ destination.address.slice(0, 6),
2618
+ "...",
2619
+ destination.address.slice(-4)
2620
+ ]
2621
+ }
2622
+ )
2623
+ ]
2624
+ }
2625
+ ),
2626
+ /* @__PURE__ */ jsxs(
2627
+ "div",
2628
+ {
2629
+ style: { display: "flex", justifyContent: "space-between" },
2630
+ children: [
2631
+ /* @__PURE__ */ jsx("span", { children: "Token" }),
2632
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
2633
+ ]
2634
+ }
2635
+ ),
2636
+ /* @__PURE__ */ jsxs(
2637
+ "div",
2638
+ {
2639
+ style: {
2640
+ display: "flex",
2641
+ justifyContent: "space-between",
2642
+ alignItems: "center"
2643
+ },
2644
+ children: [
2645
+ /* @__PURE__ */ jsx("span", { children: "From" }),
2646
+ noAccounts ? /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: tokens.textMuted }, children: "New account" }) : /* @__PURE__ */ jsx(
2647
+ AccountDropdown,
2648
+ {
2649
+ accounts,
2650
+ selectedAccountId,
2651
+ selectedWalletId,
2652
+ onSelect: (id) => {
2653
+ setSelectedAccountId(id);
2654
+ setSelectedWalletId(null);
2655
+ setConnectingNewAccount(false);
2656
+ setSelectedProviderId(null);
2657
+ },
2658
+ onWalletSelect: (accountId, walletId) => {
2659
+ setSelectedAccountId(accountId);
2660
+ setSelectedWalletId(walletId);
2661
+ setConnectingNewAccount(false);
2662
+ setSelectedProviderId(null);
2663
+ }
2664
+ }
2665
+ )
2666
+ ]
2667
+ }
2668
+ )
2669
+ ]
1639
2670
  }
1640
2671
  ),
2672
+ noAccounts && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
2673
+ /* @__PURE__ */ jsx(
2674
+ "label",
2675
+ {
2676
+ style: {
2677
+ display: "block",
2678
+ fontSize: "0.8rem",
2679
+ color: tokens.textMuted,
2680
+ marginBottom: "8px",
2681
+ fontWeight: 500,
2682
+ textTransform: "uppercase",
2683
+ letterSpacing: "0.05em"
2684
+ },
2685
+ children: "Connect a wallet"
2686
+ }
2687
+ ),
2688
+ /* @__PURE__ */ jsx(
2689
+ "div",
2690
+ {
2691
+ style: {
2692
+ display: "flex",
2693
+ flexDirection: "column",
2694
+ gap: "8px"
2695
+ },
2696
+ children: providers.map((p) => /* @__PURE__ */ jsx(
2697
+ ProviderCard,
2698
+ {
2699
+ provider: p,
2700
+ selected: selectedProviderId === p.id,
2701
+ onClick: () => {
2702
+ setSelectedProviderId(p.id);
2703
+ setSelectedAccountId(null);
2704
+ setConnectingNewAccount(false);
2705
+ }
2706
+ },
2707
+ p.id
2708
+ ))
2709
+ }
2710
+ )
2711
+ ] }),
1641
2712
  /* @__PURE__ */ jsxs(
1642
2713
  "button",
1643
2714
  {
1644
- style: parseFloat(amount) > 0 ? btnPrimary : btnDisabled,
1645
- disabled: !(parseFloat(amount) > 0),
2715
+ style: canPay ? btnPrimary : btnDisabled,
2716
+ disabled: !canPay,
1646
2717
  onClick: handlePay,
1647
2718
  children: [
1648
2719
  "Pay $",
1649
- parseFloat(amount || "0").toFixed(2)
2720
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1650
2721
  ]
1651
2722
  }
2723
+ ),
2724
+ !noAccounts && /* @__PURE__ */ jsx(
2725
+ AdvancedSettings,
2726
+ {
2727
+ settings: advancedSettings,
2728
+ onChange: setAdvancedSettings,
2729
+ chains,
2730
+ providers,
2731
+ onConnectNewAccount: handleConnectNewAccount,
2732
+ connectingNewAccount
2733
+ }
1652
2734
  )
1653
2735
  ] })
1654
2736
  ] });
1655
2737
  }
1656
2738
  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...";
2739
+ if (transferSigning.signing && transferSigning.signPayload) {
2740
+ const payload = transferSigning.signPayload;
2741
+ return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2742
+ /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", style: { margin: "0 auto 16px" }, children: [
2743
+ /* @__PURE__ */ jsx("rect", { width: "48", height: "48", rx: "12", fill: tokens.accent + "20" }),
2744
+ /* @__PURE__ */ jsx("path", { d: "M24 14v8M20 18h8M24 26v2M24 32v2", stroke: tokens.accent, strokeWidth: "2", strokeLinecap: "round" })
2745
+ ] }),
2746
+ /* @__PURE__ */ jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Authorize Transfer" }),
2747
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.85rem", color: tokens.textSecondary, margin: "0 0 16px 0", lineHeight: 1.5 }, children: "Use your passkey to confirm this payment." }),
2748
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.825rem", color: tokens.textSecondary, padding: "12px 14px", background: tokens.bgInput, borderRadius: tokens.radius, textAlign: "left", lineHeight: 1.7, marginBottom: "16px" }, children: [
2749
+ payload.amount && payload.tokenSymbol && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2750
+ /* @__PURE__ */ jsx("span", { children: "Amount" }),
2751
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
2752
+ payload.amount,
2753
+ " ",
2754
+ payload.tokenSymbol
2755
+ ] })
2756
+ ] }),
2757
+ payload.bridgeRelayAddress && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2758
+ /* @__PURE__ */ jsx("span", { children: "Bridge relay" }),
2759
+ /* @__PURE__ */ jsxs("span", { style: { fontFamily: '"SF Mono", "Fira Code", monospace', fontSize: "0.75rem" }, children: [
2760
+ payload.bridgeRelayAddress.slice(0, 6),
2761
+ "...",
2762
+ payload.bridgeRelayAddress.slice(-4)
2763
+ ] })
2764
+ ] }),
2765
+ payload.estimatedFeeUsd && /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2766
+ /* @__PURE__ */ jsx("span", { children: "Est. fee" }),
2767
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600 }, children: [
2768
+ "$",
2769
+ payload.estimatedFeeUsd
2770
+ ] })
2771
+ ] })
2772
+ ] }),
2773
+ /* @__PURE__ */ jsx(Spinner, { label: "Waiting for passkey..." })
2774
+ ] }) });
2775
+ }
2776
+ const currentActionType = authExecutor.currentAction?.type;
2777
+ const getRegistrationMessage = () => {
2778
+ switch (currentActionType) {
2779
+ case "REGISTER_PASSKEY":
2780
+ return {
2781
+ label: "Creating your passkey...",
2782
+ description: "Set up a passkey for secure, one-touch payments."
2783
+ };
2784
+ case "UPGRADE_SMART_ACCOUNT":
2785
+ return {
2786
+ label: "Upgrading your account...",
2787
+ description: "Approve the prompts in MetaMask to upgrade your account to a smart account and register your passkey. You may see two prompts."
2788
+ };
2789
+ case "GRANT_PERMISSIONS":
2790
+ return {
2791
+ label: "Setting up permissions...",
2792
+ description: "Signing delegation permissions. Transfer routing and gas sponsorship are handled by Swype backend."
2793
+ };
2794
+ default:
2795
+ return { label: "", description: "" };
2796
+ }
2797
+ };
2798
+ const regMsg = getRegistrationMessage();
2799
+ const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing && regMsg.label ? regMsg.label : authExecutor.executing ? "Authorizing..." : transferSigning.signing ? "Preparing transfer..." : polling.isPolling ? "Processing payment..." : "Please wait...";
2800
+ const statusDescription = creatingTransfer ? "Setting up your transfer..." : mobileFlow ? "Complete the authorization in your wallet app, then return here." : authExecutor.executing && regMsg.description ? regMsg.description : authExecutor.executing ? "Complete the wallet prompts to authorize this payment." : transferSigning.signing ? "Waiting for backend to prepare your transfer payload..." : polling.isPolling ? "Your payment is being processed. This usually takes a few moments." : "Hang tight...";
1659
2801
  return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
1660
2802
  /* @__PURE__ */ jsx(Spinner, { size: 48 }),
1661
2803
  /* @__PURE__ */ jsx(
@@ -1699,11 +2841,11 @@ function SwypePayment({
1699
2841
  },
1700
2842
  r.actionId
1701
2843
  )) }),
1702
- (error || authExecutor.error || polling.error) && /* @__PURE__ */ jsx(
2844
+ (error || authExecutor.error || transferSigning.error || polling.error) && /* @__PURE__ */ jsx(
1703
2845
  "div",
1704
2846
  {
1705
2847
  style: { ...errorStyle, marginTop: "16px", textAlign: "left" },
1706
- children: error || authExecutor.error || polling.error
2848
+ children: error || authExecutor.error || transferSigning.error || polling.error
1707
2849
  }
1708
2850
  )
1709
2851
  ] }) });
@@ -1830,6 +2972,6 @@ function SwypePayment({
1830
2972
  return null;
1831
2973
  }
1832
2974
 
1833
- export { SwypePayment, SwypeProvider, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useTransferPolling };
2975
+ export { SwypePayment, SwypeProvider, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling };
1834
2976
  //# sourceMappingURL=index.js.map
1835
2977
  //# sourceMappingURL=index.js.map