@swype-org/react-sdk 0.1.3 → 0.1.5

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
@@ -4,7 +4,7 @@ import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChai
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';
7
+ import { recoverTypedDataAddress, walletActions, createClient, custom } from 'viem';
8
8
  import { parseAccount, getAddress } from 'viem/utils';
9
9
 
10
10
  var __defProp = Object.defineProperty;
@@ -118,6 +118,9 @@ function useSwypeConfig() {
118
118
  }
119
119
  return ctx;
120
120
  }
121
+ function useOptionalSwypeConfig() {
122
+ return useContext(SwypeContext);
123
+ }
121
124
  function useSwypeDepositAmount() {
122
125
  const ctx = useContext(SwypeContext);
123
126
  if (!ctx) {
@@ -142,6 +145,8 @@ __export(api_exports, {
142
145
  fetchChains: () => fetchChains,
143
146
  fetchProviders: () => fetchProviders,
144
147
  fetchTransfer: () => fetchTransfer,
148
+ fetchUserConfig: () => fetchUserConfig,
149
+ registerPasskey: () => registerPasskey,
145
150
  reportActionCompletion: () => reportActionCompletion,
146
151
  signTransfer: () => signTransfer,
147
152
  updateUserConfig: () => updateUserConfig,
@@ -205,19 +210,29 @@ async function createTransfer(apiBaseUrl, token, params) {
205
210
  if (!res.ok) await throwApiError(res);
206
211
  return await res.json();
207
212
  }
208
- async function fetchTransfer(apiBaseUrl, token, transferId) {
213
+ async function fetchTransfer(apiBaseUrl, token, transferId, authorizationSessionToken) {
214
+ if (!token && !authorizationSessionToken) {
215
+ throw new Error("Missing auth credentials for transfer fetch.");
216
+ }
209
217
  const res = await fetch(`${apiBaseUrl}/v1/transfers/${transferId}`, {
210
- headers: { Authorization: `Bearer ${token}` }
218
+ headers: {
219
+ ...token ? { Authorization: `Bearer ${token}` } : {},
220
+ ...authorizationSessionToken ? { "x-authorization-session-token": authorizationSessionToken } : {}
221
+ }
211
222
  });
212
223
  if (!res.ok) await throwApiError(res);
213
224
  return await res.json();
214
225
  }
215
- async function signTransfer(apiBaseUrl, token, transferId, signedUserOp) {
226
+ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp, authorizationSessionToken) {
227
+ if (!token && !authorizationSessionToken) {
228
+ throw new Error("Missing auth credentials for transfer signing.");
229
+ }
216
230
  const res = await fetch(`${apiBaseUrl}/v1/transfers/${transferId}`, {
217
231
  method: "PATCH",
218
232
  headers: {
219
233
  "Content-Type": "application/json",
220
- Authorization: `Bearer ${token}`
234
+ ...token ? { Authorization: `Bearer ${token}` } : {},
235
+ ...authorizationSessionToken ? { "x-authorization-session-token": authorizationSessionToken } : {}
221
236
  },
222
237
  body: JSON.stringify({ signedUserOp })
223
238
  });
@@ -231,6 +246,24 @@ async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
231
246
  if (!res.ok) await throwApiError(res);
232
247
  return await res.json();
233
248
  }
249
+ async function registerPasskey(apiBaseUrl, token, credentialId, publicKey) {
250
+ const res = await fetch(`${apiBaseUrl}/v1/users/config/passkey`, {
251
+ method: "POST",
252
+ headers: {
253
+ "Content-Type": "application/json",
254
+ Authorization: `Bearer ${token}`
255
+ },
256
+ body: JSON.stringify({ credentialId, publicKey })
257
+ });
258
+ if (!res.ok) await throwApiError(res);
259
+ }
260
+ async function fetchUserConfig(apiBaseUrl, token) {
261
+ const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
262
+ headers: { Authorization: `Bearer ${token}` }
263
+ });
264
+ if (!res.ok) await throwApiError(res);
265
+ return await res.json();
266
+ }
234
267
  async function updateUserConfig(apiBaseUrl, token, config) {
235
268
  const res = await fetch(`${apiBaseUrl}/v1/users`, {
236
269
  method: "PATCH",
@@ -525,182 +558,136 @@ async function getWalletClient(config, parameters = {}) {
525
558
  const client = await getConnectorClient(config, parameters);
526
559
  return client.extend(walletActions);
527
560
  }
528
-
529
- // src/authorization/upgrade.ts
530
- function isNonEmptyString(value) {
531
- return typeof value === "string" && value.trim().length > 0;
561
+ var WALLET_CLIENT_MAX_ATTEMPTS = 15;
562
+ var WALLET_CLIENT_POLL_MS = 200;
563
+ var ACTION_POLL_INTERVAL_MS = 500;
564
+ var ACTION_POLL_MAX_RETRIES = 20;
565
+ var SIGN_PERMIT2_POLL_MS = 1e3;
566
+ var SIGN_PERMIT2_MAX_POLLS = 15;
567
+ var TRANSFER_SIGN_MAX_POLLS = 60;
568
+ function actionSuccess(action, message, data) {
569
+ return { actionId: action.id, type: action.type, status: "success", message, data };
532
570
  }
533
- function isValidChainId(value) {
534
- return typeof value === "number" && Number.isInteger(value) && value > 0;
571
+ function actionError(action, message) {
572
+ return { actionId: action.id, type: action.type, status: "error", message };
535
573
  }
536
- function isRpcClient(value) {
537
- return typeof value === "object" && value !== null && "request" in value && typeof value.request === "function";
574
+ function isUserRejection(msg) {
575
+ const lower = msg.toLowerCase();
576
+ return lower.includes("rejected") || lower.includes("denied");
538
577
  }
539
- function sleep(ms) {
540
- return new Promise((resolve) => setTimeout(resolve, ms));
578
+ function hexToBytes(hex) {
579
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
580
+ const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
581
+ return new Uint8Array(bytes);
541
582
  }
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.`);
583
+ function toBase64(buffer) {
584
+ return btoa(String.fromCharCode(...new Uint8Array(buffer)));
572
585
  }
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}`);
586
+ function base64ToBytes(value) {
587
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
588
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
589
+ const raw = atob(padded);
590
+ const bytes = new Uint8Array(raw.length);
591
+ for (let i = 0; i < raw.length; i++) {
592
+ bytes[i] = raw.charCodeAt(i);
592
593
  }
594
+ return bytes;
593
595
  }
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.");
596
+ function readEnvValue(name) {
597
+ const meta = import.meta;
598
+ const metaValue = meta.env?.[name];
599
+ if (typeof metaValue === "string" && metaValue.trim().length > 0) {
600
+ return metaValue.trim();
599
601
  }
600
- const resolvedSmartAccountAddress = metadata?.smartAccountAddress ?? sender;
601
- if (!isNonEmptyString(resolvedSmartAccountAddress)) {
602
- throw new Error("No connected account address found for smart account upgrade.");
602
+ const processValue = globalThis.process?.env?.[name];
603
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
604
+ return processValue.trim();
603
605
  }
604
- const chainId = metadata?.upgradePayload?.authorization?.chainId;
605
- if (!isValidChainId(chainId)) {
606
- throw new Error("Invalid or missing chainId in upgrade metadata.");
606
+ return void 0;
607
+ }
608
+ function resolvePasskeyRpId() {
609
+ const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("SWYPE_DOMAIN");
610
+ if (configuredDomain) {
611
+ return configuredDomain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
607
612
  }
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
- );
613
+ if (typeof window !== "undefined") {
614
+ return window.location.hostname;
612
615
  }
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
- };
616
+ return "localhost";
675
617
  }
676
-
677
- // src/hooks.ts
678
- async function waitForWalletClient(wagmiConfig2, maxAttempts = 15, intervalMs = 200) {
679
- for (let i = 0; i < maxAttempts; i++) {
618
+ async function waitForWalletClient(wagmiConfig2, params = {}) {
619
+ for (let i = 0; i < WALLET_CLIENT_MAX_ATTEMPTS; i++) {
680
620
  try {
681
- return await getWalletClient(wagmiConfig2);
621
+ return await getWalletClient(wagmiConfig2, params);
682
622
  } catch {
683
- if (i === maxAttempts - 1) {
623
+ if (i === WALLET_CLIENT_MAX_ATTEMPTS - 1) {
684
624
  throw new Error("Wallet not ready. Please try again.");
685
625
  }
686
- await new Promise((r) => setTimeout(r, intervalMs));
626
+ await new Promise((r) => setTimeout(r, WALLET_CLIENT_POLL_MS));
687
627
  }
688
628
  }
689
629
  throw new Error("Wallet not ready. Please try again.");
690
630
  }
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;
631
+ function parseSignTypedDataPayload(typedData) {
632
+ const { domain, types, primaryType, message } = typedData;
633
+ if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
634
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
635
+ }
636
+ if (!types || typeof types !== "object" || Array.isArray(types)) {
637
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid types object.");
638
+ }
639
+ if (typeof primaryType !== "string") {
640
+ throw new Error("SIGN_PERMIT2 typedData is missing primaryType.");
641
+ }
642
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
643
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid message object.");
697
644
  }
698
- return "Failed to upgrade account";
645
+ return {
646
+ domain,
647
+ types,
648
+ primaryType,
649
+ message
650
+ };
699
651
  }
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");
652
+ function getPendingActions(session, completedIds) {
653
+ return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
654
+ }
655
+ async function createPasskeyCredential(userIdentifier) {
656
+ const challenge = new Uint8Array(32);
657
+ crypto.getRandomValues(challenge);
658
+ const rpId = resolvePasskeyRpId();
659
+ const credential = await navigator.credentials.create({
660
+ publicKey: {
661
+ challenge,
662
+ rp: { name: "Swype", id: rpId },
663
+ user: {
664
+ id: new TextEncoder().encode(userIdentifier),
665
+ name: userIdentifier,
666
+ displayName: "Swype User"
667
+ },
668
+ pubKeyCredParams: [
669
+ { alg: -7, type: "public-key" },
670
+ // ES256 (P-256)
671
+ { alg: -257, type: "public-key" }
672
+ // RS256
673
+ ],
674
+ authenticatorSelection: {
675
+ authenticatorAttachment: "platform",
676
+ residentKey: "preferred",
677
+ userVerification: "required"
678
+ },
679
+ timeout: 6e4
680
+ }
681
+ });
682
+ if (!credential) {
683
+ throw new Error("Passkey creation was cancelled.");
684
+ }
685
+ const response = credential.response;
686
+ const publicKeyBytes = response.getPublicKey?.();
687
+ return {
688
+ credentialId: toBase64(credential.rawId),
689
+ publicKey: publicKeyBytes ? toBase64(publicKeyBytes) : ""
690
+ };
704
691
  }
705
692
  function useTransferPolling(intervalMs = 3e3) {
706
693
  const { apiBaseUrl } = useSwypeConfig();
@@ -750,8 +737,247 @@ function useTransferPolling(intervalMs = 3e3) {
750
737
  useEffect(() => () => stopPolling(), [stopPolling]);
751
738
  return { transfer, error, isPolling, startPolling, stopPolling };
752
739
  }
753
- function useAuthorizationExecutor() {
754
- const { apiBaseUrl } = useSwypeConfig();
740
+ async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
741
+ try {
742
+ const account = getAccount(wagmiConfig2);
743
+ if (account.isConnected && account.address) {
744
+ const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
745
+ return actionSuccess(
746
+ action,
747
+ `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
748
+ { accounts: [account.address], chainId: hexChainId2 }
749
+ );
750
+ }
751
+ const targetId = action.metadata?.wagmiConnectorId;
752
+ const metaMaskConnector = connectors.find((c) => {
753
+ const id = c.id.toLowerCase();
754
+ const name = c.name.toLowerCase();
755
+ return id.includes("metamask") || name.includes("metamask");
756
+ });
757
+ const connector = targetId ? connectors.find((c) => c.id === targetId) ?? metaMaskConnector ?? connectors[0] : metaMaskConnector ?? connectors[0];
758
+ if (!connector) {
759
+ return actionError(action, "No wallet connector found. Please install a supported wallet.");
760
+ }
761
+ const result = await connectAsync({ connector });
762
+ const hexChainId = `0x${result.chainId.toString(16)}`;
763
+ return actionSuccess(
764
+ action,
765
+ `Connected to ${connector.name}. Account: ${result.accounts[0]}, Chain: ${hexChainId}`,
766
+ { accounts: [...result.accounts], chainId: hexChainId }
767
+ );
768
+ } catch (err) {
769
+ return actionError(
770
+ action,
771
+ err instanceof Error ? err.message : "Failed to connect wallet"
772
+ );
773
+ }
774
+ }
775
+ async function executeSelectSource(action, waitForSelection) {
776
+ try {
777
+ const options = action.metadata?.options;
778
+ const recommended = action.metadata?.recommended;
779
+ if (!options || options.length === 0) {
780
+ return actionError(
781
+ action,
782
+ "No selectable source options returned by backend."
783
+ );
784
+ }
785
+ const selection = await waitForSelection(action);
786
+ const isValidSelection = options.some(
787
+ (option) => option.chainName === selection.chainName && option.tokenSymbol === selection.tokenSymbol
788
+ );
789
+ if (!isValidSelection) {
790
+ return actionError(
791
+ action,
792
+ "Invalid source selection. Please choose one of the provided options."
793
+ );
794
+ }
795
+ return actionSuccess(
796
+ action,
797
+ `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
798
+ { selectedChainName: selection.chainName, selectedTokenSymbol: selection.tokenSymbol }
799
+ );
800
+ } catch (err) {
801
+ return actionError(
802
+ action,
803
+ err instanceof Error ? err.message : "Failed to select source"
804
+ );
805
+ }
806
+ }
807
+ async function executeSwitchChain(action, wagmiConfig2, switchChainAsync) {
808
+ try {
809
+ const account = getAccount(wagmiConfig2);
810
+ const targetChainIdHex = action.metadata?.targetChainId;
811
+ if (!targetChainIdHex) {
812
+ return actionError(action, "No targetChainId in action metadata.");
813
+ }
814
+ if (!/^0x[0-9a-fA-F]+$/.test(targetChainIdHex)) {
815
+ return actionError(
816
+ action,
817
+ `Invalid targetChainId in action metadata: ${targetChainIdHex}`
818
+ );
819
+ }
820
+ const targetChainIdNum = parseInt(targetChainIdHex, 16);
821
+ if (Number.isNaN(targetChainIdNum)) {
822
+ return actionError(
823
+ action,
824
+ `Invalid targetChainId in action metadata: ${targetChainIdHex}`
825
+ );
826
+ }
827
+ const hexChainId = `0x${targetChainIdNum.toString(16)}`;
828
+ if (account.chainId === targetChainIdNum) {
829
+ return actionSuccess(
830
+ action,
831
+ `Already on chain ${hexChainId}. Skipped.`,
832
+ { chainId: hexChainId, switched: false }
833
+ );
834
+ }
835
+ await switchChainAsync({ chainId: targetChainIdNum });
836
+ return actionSuccess(
837
+ action,
838
+ `Switched to chain ${hexChainId}.`,
839
+ { chainId: hexChainId, switched: true }
840
+ );
841
+ } catch (err) {
842
+ return actionError(
843
+ action,
844
+ err instanceof Error ? err.message : "Failed to switch chain"
845
+ );
846
+ }
847
+ }
848
+ async function executeApprovePermit2(action, wagmiConfig2) {
849
+ try {
850
+ const walletClient = await waitForWalletClient(wagmiConfig2);
851
+ const account = getAccount(wagmiConfig2);
852
+ const sender = account.address ?? walletClient.account?.address;
853
+ const expectedWallet = action.metadata?.walletAddress;
854
+ if (!sender) {
855
+ throw new Error("Wallet account not available. Please connect your wallet.");
856
+ }
857
+ if (expectedWallet && sender.toLowerCase() !== expectedWallet.toLowerCase()) {
858
+ return actionError(
859
+ action,
860
+ `Connected wallet ${sender} does not match the required source wallet ${expectedWallet}. Please switch accounts in your wallet and retry.`
861
+ );
862
+ }
863
+ const to = action.metadata?.to;
864
+ const data = action.metadata?.data;
865
+ const tokenSymbol = action.metadata?.tokenSymbol;
866
+ if (!to || !data) {
867
+ return actionError(
868
+ action,
869
+ "APPROVE_PERMIT2 metadata is missing transaction parameters (to, data)."
870
+ );
871
+ }
872
+ const txHash = await walletClient.request({
873
+ method: "eth_sendTransaction",
874
+ params: [{
875
+ from: sender,
876
+ to,
877
+ data,
878
+ value: "0x0"
879
+ }]
880
+ });
881
+ console.info(
882
+ `[swype-sdk][approve-permit2] ERC-20 approve tx sent. token=${tokenSymbol ?? to}, txHash=${txHash}`
883
+ );
884
+ return actionSuccess(
885
+ action,
886
+ `Approved Permit2 to spend ${tokenSymbol ?? "tokens"}.`,
887
+ { txHash }
888
+ );
889
+ } catch (err) {
890
+ const msg = err instanceof Error ? err.message : "Failed to approve Permit2";
891
+ return actionError(
892
+ action,
893
+ isUserRejection(msg) ? "You rejected the approval transaction. Please approve the Permit2 spending allowance in your wallet to continue." : msg
894
+ );
895
+ }
896
+ }
897
+ async function executeSignPermit2(action, wagmiConfig2, apiBaseUrl, sessionId) {
898
+ try {
899
+ const expectedWallet = action.metadata?.walletAddress;
900
+ const walletClient = await waitForWalletClient(
901
+ wagmiConfig2,
902
+ expectedWallet ? { account: expectedWallet } : {}
903
+ );
904
+ const account = getAccount(wagmiConfig2);
905
+ const connectedAddress = account.address ?? walletClient.account?.address;
906
+ const sender = expectedWallet ?? connectedAddress;
907
+ if (!sender) {
908
+ throw new Error("Wallet account not available. Please connect your wallet.");
909
+ }
910
+ if (expectedWallet && connectedAddress && connectedAddress.toLowerCase() !== expectedWallet.toLowerCase()) {
911
+ return actionError(
912
+ action,
913
+ `Connected wallet ${connectedAddress} does not match the required source wallet ${expectedWallet}. Please switch accounts in your wallet and retry.`
914
+ );
915
+ }
916
+ let typedData = action.metadata?.typedData;
917
+ const tokenSymbol = action.metadata?.tokenSymbol;
918
+ if (!typedData && sessionId) {
919
+ for (let i = 0; i < SIGN_PERMIT2_MAX_POLLS; i++) {
920
+ await new Promise((r) => setTimeout(r, SIGN_PERMIT2_POLL_MS));
921
+ const session = await fetchAuthorizationSession(apiBaseUrl, sessionId);
922
+ const updated = session.actions.find((a) => a.id === action.id);
923
+ typedData = updated?.metadata?.typedData;
924
+ if (typedData) break;
925
+ }
926
+ }
927
+ if (!typedData) {
928
+ return actionError(
929
+ action,
930
+ "SIGN_PERMIT2 metadata is missing typedData. The server may still be preparing the signing payload."
931
+ );
932
+ }
933
+ const parsed = parseSignTypedDataPayload(typedData);
934
+ console.info(
935
+ `[swype-sdk][sign-permit2] Signing typed data. expectedOwner=${expectedWallet ?? "N/A"}, senderParam=${sender}, connectedAddress=${connectedAddress ?? "N/A"}, primaryType=${parsed.primaryType}, domainChainId=${String(parsed.domain.chainId ?? "N/A")}, verifyingContract=${String(parsed.domain.verifyingContract ?? "N/A")}`
936
+ );
937
+ const signature = await walletClient.signTypedData({
938
+ account: sender,
939
+ domain: parsed.domain,
940
+ types: parsed.types,
941
+ primaryType: parsed.primaryType,
942
+ message: parsed.message
943
+ });
944
+ const recoverInput = {
945
+ domain: parsed.domain,
946
+ types: parsed.types,
947
+ primaryType: parsed.primaryType,
948
+ message: parsed.message,
949
+ signature
950
+ };
951
+ const recoveredSigner = await recoverTypedDataAddress(recoverInput);
952
+ const expectedSigner = (expectedWallet ?? sender).toLowerCase();
953
+ console.info(
954
+ `[swype-sdk][sign-permit2] Signature recovered. recoveredSigner=${recoveredSigner}, expectedSigner=${expectedSigner}`
955
+ );
956
+ if (recoveredSigner.toLowerCase() !== expectedSigner) {
957
+ return actionError(
958
+ action,
959
+ `Wallet signed with ${recoveredSigner}, but source wallet is ${expectedWallet ?? sender}. Please switch to the source wallet in MetaMask and retry.`
960
+ );
961
+ }
962
+ console.info(
963
+ `[swype-sdk][sign-permit2] Permit2 EIP-712 signature obtained. token=${tokenSymbol ?? "unknown"}`
964
+ );
965
+ return actionSuccess(
966
+ action,
967
+ `Permit2 allowance signed for ${tokenSymbol ?? "tokens"}.`,
968
+ { signature }
969
+ );
970
+ } catch (err) {
971
+ const msg = err instanceof Error ? err.message : "Failed to sign Permit2 allowance";
972
+ return actionError(
973
+ action,
974
+ isUserRejection(msg) ? "You rejected the Permit2 signature request. Please approve the signature in your wallet to allow fund transfers." : msg
975
+ );
976
+ }
977
+ }
978
+ function useAuthorizationExecutor(options) {
979
+ const swypeConfig = useOptionalSwypeConfig();
980
+ const apiBaseUrl = options?.apiBaseUrl ?? swypeConfig?.apiBaseUrl;
755
981
  const wagmiConfig2 = useConfig();
756
982
  const { connectAsync, connectors } = useConnect();
757
983
  const { switchChainAsync } = useSwitchChain();
@@ -762,6 +988,7 @@ function useAuthorizationExecutor() {
762
988
  const executingRef = useRef(false);
763
989
  const [pendingSelectSource, setPendingSelectSource] = useState(null);
764
990
  const selectSourceResolverRef = useRef(null);
991
+ const sessionIdRef = useRef(null);
765
992
  const resolveSelectSource = useCallback((selection) => {
766
993
  if (selectSourceResolverRef.current) {
767
994
  selectSourceResolverRef.current(selection);
@@ -769,393 +996,82 @@ function useAuthorizationExecutor() {
769
996
  setPendingSelectSource(null);
770
997
  }
771
998
  }, []);
772
- const sessionIdRef = useRef(null);
773
- const executeOpenProvider = useCallback(
774
- async (action) => {
775
- try {
776
- const account = getAccount(wagmiConfig2);
777
- if (account.isConnected && account.address) {
778
- const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
779
- return {
780
- actionId: action.id,
781
- type: action.type,
782
- status: "success",
783
- message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
784
- data: { accounts: [account.address], chainId: hexChainId2 }
785
- };
786
- }
787
- const targetId = action.metadata?.wagmiConnectorId;
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];
794
- if (!connector) {
795
- return {
796
- actionId: action.id,
797
- type: action.type,
798
- status: "error",
799
- message: "No wallet connector found. Please install a supported wallet."
800
- };
801
- }
802
- const result = await connectAsync({ connector });
803
- const hexChainId = `0x${result.chainId.toString(16)}`;
804
- return {
805
- actionId: action.id,
806
- type: action.type,
807
- status: "success",
808
- message: `Connected to ${connector.name}. Account: ${result.accounts[0]}, Chain: ${hexChainId}`,
809
- data: { accounts: [...result.accounts], chainId: hexChainId }
810
- };
811
- } catch (err) {
812
- return {
813
- actionId: action.id,
814
- type: action.type,
815
- status: "error",
816
- message: err instanceof Error ? err.message : "Failed to connect wallet"
817
- };
818
- }
819
- },
820
- [wagmiConfig2, connectors, connectAsync]
821
- );
822
- const executeSelectSource = useCallback(
823
- async (action) => {
824
- try {
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" };
829
- return {
830
- actionId: action.id,
831
- type: action.type,
832
- status: "success",
833
- message: `Auto-selected ${selection2.tokenSymbol} on ${selection2.chainName}.`,
834
- data: {
835
- selectedChainName: selection2.chainName,
836
- selectedTokenSymbol: selection2.tokenSymbol
837
- }
838
- };
839
- }
840
- const selection = await new Promise((resolve) => {
841
- selectSourceResolverRef.current = resolve;
842
- setPendingSelectSource(action);
843
- });
844
- return {
845
- actionId: action.id,
846
- type: action.type,
847
- status: "success",
848
- message: `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
849
- data: {
850
- selectedChainName: selection.chainName,
851
- selectedTokenSymbol: selection.tokenSymbol
852
- }
853
- };
854
- } catch (err) {
855
- return {
856
- actionId: action.id,
857
- type: action.type,
858
- status: "error",
859
- message: err instanceof Error ? err.message : "Failed to select source"
860
- };
861
- }
862
- },
999
+ const waitForSelection = useCallback(
1000
+ (action) => new Promise((resolve) => {
1001
+ selectSourceResolverRef.current = resolve;
1002
+ setPendingSelectSource(action);
1003
+ }),
863
1004
  []
864
1005
  );
865
- const executeSwitchChain = useCallback(
866
- async (action) => {
867
- try {
868
- const account = getAccount(wagmiConfig2);
869
- const targetChainIdHex = action.metadata?.targetChainId;
870
- if (!targetChainIdHex) {
871
- return {
872
- actionId: action.id,
873
- type: action.type,
874
- status: "error",
875
- message: "No targetChainId in action metadata."
876
- };
877
- }
878
- const targetChainIdNum = parseInt(targetChainIdHex, 16);
879
- const hexChainId = `0x${targetChainIdNum.toString(16)}`;
880
- if (account.chainId === targetChainIdNum) {
881
- return {
882
- actionId: action.id,
883
- type: action.type,
884
- status: "success",
885
- message: `Already on chain ${hexChainId}. Skipped.`,
886
- data: { chainId: hexChainId, switched: false }
887
- };
888
- }
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
931
- ],
932
- authenticatorSelection: {
933
- authenticatorAttachment: "platform",
934
- residentKey: "preferred",
935
- userVerification: "required"
936
- },
937
- timeout: 6e4
938
- }
939
- });
940
- if (!credential) {
941
- return {
942
- actionId: action.id,
943
- type: action.type,
944
- status: "error",
945
- message: "Passkey creation was cancelled."
946
- };
947
- }
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))) : "";
954
- return {
955
- actionId: action.id,
956
- type: action.type,
957
- status: "success",
958
- message: "Passkey created successfully.",
959
- data: {
960
- credentialId,
961
- publicKey
962
- }
963
- };
964
- } catch (err) {
965
- return {
966
- actionId: action.id,
967
- type: action.type,
968
- status: "error",
969
- message: err instanceof Error ? err.message : "Failed to create passkey"
970
- };
971
- }
972
- },
973
- [wagmiConfig2]
974
- );
975
- const executeUpgradeSmartAccount = useCallback(
976
- async (action) => {
977
- try {
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
1003
- }
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) {
1026
- return {
1027
- actionId: action.id,
1028
- type: action.type,
1029
- status: "error",
1030
- message: "No signing payload in action metadata."
1031
- };
1032
- }
1033
- const signature = await walletClient.signMessage({
1034
- message: JSON.stringify(signingPayload)
1035
- });
1036
- const tokenSummary = tokens?.map((t) => `${t.symbol} on ${t.chainName}`).join(", ") ?? "all tokens";
1037
- return {
1038
- actionId: action.id,
1039
- type: action.type,
1040
- status: "success",
1041
- message: `Permissions granted for ${tokenSummary}.`,
1042
- data: {
1043
- signedDelegation: signature,
1044
- tokens
1045
- }
1046
- };
1047
- } catch (err) {
1048
- return {
1049
- actionId: action.id,
1050
- type: action.type,
1051
- status: "error",
1052
- message: err instanceof Error ? err.message : "Failed to grant permissions"
1053
- };
1054
- }
1055
- },
1056
- [wagmiConfig2]
1057
- );
1058
- const executeAction = useCallback(
1006
+ const dispatchAction = useCallback(
1059
1007
  async (action) => {
1060
1008
  setCurrentAction(action);
1061
1009
  switch (action.type) {
1062
1010
  case "OPEN_PROVIDER":
1063
- return executeOpenProvider(action);
1011
+ return executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
1064
1012
  case "SELECT_SOURCE":
1065
- return executeSelectSource(action);
1013
+ return executeSelectSource(action, waitForSelection);
1066
1014
  case "SWITCH_CHAIN":
1067
- return executeSwitchChain(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);
1015
+ return executeSwitchChain(action, wagmiConfig2, switchChainAsync);
1016
+ case "APPROVE_PERMIT2":
1017
+ return executeApprovePermit2(action, wagmiConfig2);
1018
+ case "SIGN_PERMIT2":
1019
+ return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
1074
1020
  default:
1075
- return {
1076
- actionId: action.id,
1077
- type: action.type,
1078
- status: "error",
1079
- message: `Unsupported action type: ${action.type}`
1080
- };
1021
+ return actionError(action, `Unsupported action type: ${action.type}`);
1081
1022
  }
1082
1023
  },
1083
- [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeUpgradeSmartAccount, executeGrantPermissions]
1024
+ [wagmiConfig2, connectors, connectAsync, switchChainAsync, apiBaseUrl, waitForSelection]
1084
1025
  );
1085
- const executeSession = useCallback(
1086
- async (transfer) => {
1026
+ const executeSessionById = useCallback(
1027
+ async (sessionId) => {
1087
1028
  if (executingRef.current) return;
1088
1029
  executingRef.current = true;
1089
- if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
1030
+ if (!sessionId) {
1090
1031
  executingRef.current = false;
1091
- throw new Error("No authorization sessions available.");
1032
+ throw new Error("No authorization session id provided.");
1033
+ }
1034
+ if (!apiBaseUrl) {
1035
+ executingRef.current = false;
1036
+ throw new Error("Missing apiBaseUrl. Provide useAuthorizationExecutor({ apiBaseUrl }) or wrap in <SwypeProvider>.");
1092
1037
  }
1093
- const sessionId = transfer.authorizationSessions[0].id;
1094
1038
  sessionIdRef.current = sessionId;
1095
1039
  setExecuting(true);
1096
1040
  setError(null);
1097
1041
  setResults([]);
1098
1042
  try {
1099
- let currentSession = await fetchAuthorizationSession(apiBaseUrl, sessionId);
1043
+ let session = await fetchAuthorizationSession(apiBaseUrl, sessionId);
1100
1044
  const allResults = [];
1101
- const completedActionIds = /* @__PURE__ */ new Set();
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) {
1045
+ const completedIds = /* @__PURE__ */ new Set();
1046
+ let pending = getPendingActions(session, completedIds);
1047
+ let retries = 0;
1048
+ while (pending.length === 0 && session.status !== "AUTHORIZED" && retries < ACTION_POLL_MAX_RETRIES) {
1107
1049
  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++;
1050
+ session = await fetchAuthorizationSession(apiBaseUrl, sessionId);
1051
+ pending = getPendingActions(session, completedIds);
1052
+ retries++;
1111
1053
  }
1112
- if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
1054
+ if (pending.length === 0 && session.status !== "AUTHORIZED") {
1113
1055
  throw new Error("Authorization actions were not created in time. Please try again.");
1114
1056
  }
1115
- while (pendingActions.length > 0) {
1116
- const action = pendingActions[0];
1117
- if (completedActionIds.has(action.id)) break;
1118
- const result = await executeAction(action);
1057
+ while (pending.length > 0) {
1058
+ const action = pending[0];
1059
+ if (completedIds.has(action.id)) break;
1060
+ const result = await dispatchAction(action);
1119
1061
  if (result.status === "error") {
1120
1062
  allResults.push(result);
1121
1063
  setResults([...allResults]);
1122
1064
  throw new Error(result.message);
1123
1065
  }
1124
- completedActionIds.add(action.id);
1125
- const updatedSession = await reportActionCompletion(
1066
+ completedIds.add(action.id);
1067
+ allResults.push(result);
1068
+ session = await reportActionCompletion(
1126
1069
  apiBaseUrl,
1127
1070
  action.id,
1128
1071
  result.data ?? {}
1129
1072
  );
1130
- currentSession = updatedSession;
1131
- pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
1132
- if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
1133
- const chainResults = [result];
1134
- while (pendingActions.length > 0) {
1135
- const nextAction = pendingActions[0];
1136
- const nextResult = await executeAction(nextAction);
1137
- if (nextResult.status === "error") {
1138
- chainResults.push(nextResult);
1139
- allResults.push(...chainResults);
1140
- setResults([...allResults]);
1141
- throw new Error(nextResult.message);
1142
- }
1143
- completedActionIds.add(nextAction.id);
1144
- const nextSession = await reportActionCompletion(
1145
- apiBaseUrl,
1146
- nextAction.id,
1147
- nextResult.data ?? {}
1148
- );
1149
- currentSession = nextSession;
1150
- chainResults.push(nextResult);
1151
- pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
1152
- }
1153
- allResults.push(...chainResults);
1154
- setResults([...allResults]);
1155
- continue;
1156
- }
1157
- allResults.push(result);
1158
1073
  setResults([...allResults]);
1074
+ pending = getPendingActions(session, completedIds);
1159
1075
  }
1160
1076
  } catch (err) {
1161
1077
  const msg = err instanceof Error ? err.message : "Authorization failed";
@@ -1167,7 +1083,16 @@ function useAuthorizationExecutor() {
1167
1083
  executingRef.current = false;
1168
1084
  }
1169
1085
  },
1170
- [apiBaseUrl, executeAction]
1086
+ [apiBaseUrl, dispatchAction]
1087
+ );
1088
+ const executeSession = useCallback(
1089
+ async (transfer) => {
1090
+ if (!transfer.authorizationSessions?.length) {
1091
+ throw new Error("No authorization sessions available.");
1092
+ }
1093
+ await executeSessionById(transfer.authorizationSessions[0].id);
1094
+ },
1095
+ [executeSessionById]
1171
1096
  );
1172
1097
  return {
1173
1098
  executing,
@@ -1176,12 +1101,21 @@ function useAuthorizationExecutor() {
1176
1101
  currentAction,
1177
1102
  pendingSelectSource,
1178
1103
  resolveSelectSource,
1104
+ executeSessionById,
1179
1105
  executeSession
1180
1106
  };
1181
1107
  }
1182
- function useTransferSigning(pollIntervalMs = 2e3) {
1183
- const { apiBaseUrl } = useSwypeConfig();
1184
- const { getAccessToken } = usePrivy();
1108
+ function useTransferSigning(pollIntervalMs = 2e3, options) {
1109
+ const swypeConfig = useOptionalSwypeConfig();
1110
+ const apiBaseUrl = options?.apiBaseUrl ?? swypeConfig?.apiBaseUrl;
1111
+ const authorizationSessionToken = options?.authorizationSessionToken;
1112
+ let privyGetAccessToken;
1113
+ try {
1114
+ ({ getAccessToken: privyGetAccessToken } = usePrivy());
1115
+ } catch {
1116
+ privyGetAccessToken = void 0;
1117
+ }
1118
+ const getAccessToken = options?.getAccessToken ?? privyGetAccessToken;
1185
1119
  const [signing, setSigning] = useState(false);
1186
1120
  const [signPayload, setSignPayload] = useState(null);
1187
1121
  const [error, setError] = useState(null);
@@ -1191,14 +1125,24 @@ function useTransferSigning(pollIntervalMs = 2e3) {
1191
1125
  setError(null);
1192
1126
  setSignPayload(null);
1193
1127
  try {
1194
- const token = await getAccessToken();
1195
- if (!token) {
1128
+ if (!apiBaseUrl) {
1129
+ throw new Error("Missing apiBaseUrl. Provide useTransferSigning(_, { apiBaseUrl }) or wrap in <SwypeProvider>.");
1130
+ }
1131
+ if (!getAccessToken && !authorizationSessionToken) {
1132
+ throw new Error("Missing getAccessToken provider. Provide useTransferSigning(_, { getAccessToken }).");
1133
+ }
1134
+ const token = getAccessToken ? await getAccessToken() : null;
1135
+ if (!token && !authorizationSessionToken) {
1196
1136
  throw new Error("Could not get access token");
1197
1137
  }
1198
- const MAX_POLLS = 60;
1199
1138
  let payload = null;
1200
- for (let i = 0; i < MAX_POLLS; i++) {
1201
- const transfer = await fetchTransfer(apiBaseUrl, token, transferId);
1139
+ for (let i = 0; i < TRANSFER_SIGN_MAX_POLLS; i++) {
1140
+ const transfer = await fetchTransfer(
1141
+ apiBaseUrl,
1142
+ token ?? "",
1143
+ transferId,
1144
+ authorizationSessionToken
1145
+ );
1202
1146
  if (transfer.signPayload) {
1203
1147
  payload = transfer.signPayload;
1204
1148
  setSignPayload(payload);
@@ -1212,14 +1156,16 @@ function useTransferSigning(pollIntervalMs = 2e3) {
1212
1156
  if (!payload) {
1213
1157
  throw new Error("Timed out waiting for sign payload. Please try again.");
1214
1158
  }
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
- );
1159
+ const hashBytes = hexToBytes(payload.userOpHash);
1160
+ const allowCredentials = payload.passkeyCredentialId ? [{
1161
+ type: "public-key",
1162
+ id: base64ToBytes(payload.passkeyCredentialId)
1163
+ }] : void 0;
1219
1164
  const assertion = await navigator.credentials.get({
1220
1165
  publicKey: {
1221
1166
  challenge: hashBytes,
1222
- rpId: window.location.hostname,
1167
+ rpId: resolvePasskeyRpId(),
1168
+ allowCredentials,
1223
1169
  userVerification: "required",
1224
1170
  timeout: 6e4
1225
1171
  }
@@ -1228,32 +1174,20 @@ function useTransferSigning(pollIntervalMs = 2e3) {
1228
1174
  throw new Error("Passkey authentication was cancelled.");
1229
1175
  }
1230
1176
  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
1177
  const signedUserOp = {
1245
1178
  ...payload.userOp,
1246
- signature,
1247
- authenticatorData,
1248
- clientDataJSON
1179
+ credentialId: toBase64(assertion.rawId),
1180
+ signature: toBase64(response.signature),
1181
+ authenticatorData: toBase64(response.authenticatorData),
1182
+ clientDataJSON: toBase64(response.clientDataJSON)
1249
1183
  };
1250
- const updatedTransfer = await signTransfer(
1184
+ return await signTransfer(
1251
1185
  apiBaseUrl,
1252
- token,
1186
+ token ?? "",
1253
1187
  transferId,
1254
- signedUserOp
1188
+ signedUserOp,
1189
+ authorizationSessionToken
1255
1190
  );
1256
- return updatedTransfer;
1257
1191
  } catch (err) {
1258
1192
  const msg = err instanceof Error ? err.message : "Failed to sign transfer";
1259
1193
  setError(msg);
@@ -1262,7 +1196,7 @@ function useTransferSigning(pollIntervalMs = 2e3) {
1262
1196
  setSigning(false);
1263
1197
  }
1264
1198
  },
1265
- [apiBaseUrl, getAccessToken, pollIntervalMs]
1199
+ [apiBaseUrl, getAccessToken, pollIntervalMs, authorizationSessionToken]
1266
1200
  );
1267
1201
  return { signing, signPayload, error, signTransfer: signTransfer2 };
1268
1202
  }
@@ -2038,6 +1972,37 @@ function computeSmartDefaults(accts, transferAmount) {
2038
1972
  }
2039
1973
  return { accountId: accts[0].id, walletId: null };
2040
1974
  }
1975
+ function parseRawBalance(rawBalance, decimals) {
1976
+ const parsed = Number(rawBalance);
1977
+ if (!Number.isFinite(parsed)) return 0;
1978
+ return parsed / 10 ** decimals;
1979
+ }
1980
+ function buildSelectSourceChoices(options) {
1981
+ const chainChoices = [];
1982
+ const chainIndexByName = /* @__PURE__ */ new Map();
1983
+ for (const option of options) {
1984
+ const chainName = option.chainName;
1985
+ const tokenSymbol = option.tokenSymbol;
1986
+ const balance = parseRawBalance(option.rawBalance, option.decimals);
1987
+ let chainChoice;
1988
+ const existingChainIdx = chainIndexByName.get(chainName);
1989
+ if (existingChainIdx === void 0) {
1990
+ chainChoice = { chainName, balance: 0, tokens: [] };
1991
+ chainIndexByName.set(chainName, chainChoices.length);
1992
+ chainChoices.push(chainChoice);
1993
+ } else {
1994
+ chainChoice = chainChoices[existingChainIdx];
1995
+ }
1996
+ chainChoice.balance += balance;
1997
+ const existingToken = chainChoice.tokens.find((token) => token.tokenSymbol === tokenSymbol);
1998
+ if (existingToken) {
1999
+ existingToken.balance += balance;
2000
+ } else {
2001
+ chainChoice.tokens.push({ tokenSymbol, balance });
2002
+ }
2003
+ }
2004
+ return chainChoices;
2005
+ }
2041
2006
  function SwypePayment({
2042
2007
  destination,
2043
2008
  onComplete,
@@ -2064,8 +2029,12 @@ function SwypePayment({
2064
2029
  });
2065
2030
  const [transfer, setTransfer] = useState(null);
2066
2031
  const [creatingTransfer, setCreatingTransfer] = useState(false);
2032
+ const [registeringPasskey, setRegisteringPasskey] = useState(false);
2067
2033
  const [mobileFlow, setMobileFlow] = useState(false);
2068
2034
  const pollingTransferIdRef = useRef(null);
2035
+ const [selectSourceChainName, setSelectSourceChainName] = useState("");
2036
+ const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
2037
+ const initializedSelectSourceActionRef = useRef(null);
2069
2038
  const authExecutor = useAuthorizationExecutor();
2070
2039
  const polling = useTransferPolling();
2071
2040
  const transferSigning = useTransferSigning();
@@ -2077,14 +2046,36 @@ function SwypePayment({
2077
2046
  }
2078
2047
  }, [depositAmount]);
2079
2048
  useEffect(() => {
2080
- if (ready && authenticated && step === "login") {
2081
- if (depositAmount != null && depositAmount > 0) {
2082
- setStep("ready");
2083
- } else {
2084
- setStep("enter-amount");
2049
+ if (!ready || !authenticated || step !== "login") return;
2050
+ let cancelled = false;
2051
+ const checkPasskey = async () => {
2052
+ try {
2053
+ const token = await getAccessToken();
2054
+ if (!token || cancelled) return;
2055
+ const { config } = await fetchUserConfig(apiBaseUrl, token);
2056
+ if (cancelled) return;
2057
+ if (!config.passkey) {
2058
+ setStep("register-passkey");
2059
+ } else if (depositAmount != null && depositAmount > 0) {
2060
+ setStep("ready");
2061
+ } else {
2062
+ setStep("enter-amount");
2063
+ }
2064
+ } catch {
2065
+ if (!cancelled) {
2066
+ if (depositAmount != null && depositAmount > 0) {
2067
+ setStep("ready");
2068
+ } else {
2069
+ setStep("enter-amount");
2070
+ }
2071
+ }
2085
2072
  }
2086
- }
2087
- }, [ready, authenticated, step, depositAmount]);
2073
+ };
2074
+ checkPasskey();
2075
+ return () => {
2076
+ cancelled = true;
2077
+ };
2078
+ }, [ready, authenticated, step, depositAmount, apiBaseUrl, getAccessToken]);
2088
2079
  const loadingDataRef = useRef(false);
2089
2080
  useEffect(() => {
2090
2081
  if (!authenticated) return;
@@ -2158,61 +2149,43 @@ function SwypePayment({
2158
2149
  document.removeEventListener("visibilitychange", handleVisibility);
2159
2150
  };
2160
2151
  }, [mobileFlow, polling]);
2152
+ const pendingSelectSourceAction = authExecutor.pendingSelectSource;
2153
+ const selectSourceChoices = useMemo(() => {
2154
+ if (!pendingSelectSourceAction) return [];
2155
+ const options = pendingSelectSourceAction.metadata?.options ?? [];
2156
+ return buildSelectSourceChoices(options);
2157
+ }, [pendingSelectSourceAction]);
2158
+ const selectSourceRecommended = useMemo(() => {
2159
+ if (!pendingSelectSourceAction) return null;
2160
+ return pendingSelectSourceAction.metadata?.recommended ?? null;
2161
+ }, [pendingSelectSourceAction]);
2161
2162
  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
- }
2163
+ if (!pendingSelectSourceAction) {
2164
+ initializedSelectSourceActionRef.current = null;
2165
+ setSelectSourceChainName("");
2166
+ setSelectSourceTokenSymbol("");
2167
+ return;
2168
+ }
2169
+ if (initializedSelectSourceActionRef.current === pendingSelectSourceAction.id) {
2170
+ return;
2171
+ }
2172
+ const hasRecommendedOption = !!selectSourceRecommended && selectSourceChoices.some(
2173
+ (chain) => chain.chainName === selectSourceRecommended.chainName && chain.tokens.some(
2174
+ (token) => token.tokenSymbol === selectSourceRecommended.tokenSymbol
2175
+ )
2176
+ );
2177
+ if (hasRecommendedOption && selectSourceRecommended) {
2178
+ setSelectSourceChainName(selectSourceRecommended.chainName);
2179
+ setSelectSourceTokenSymbol(selectSourceRecommended.tokenSymbol);
2180
+ } else if (selectSourceChoices.length > 0 && selectSourceChoices[0].tokens.length > 0) {
2181
+ setSelectSourceChainName(selectSourceChoices[0].chainName);
2182
+ setSelectSourceTokenSymbol(selectSourceChoices[0].tokens[0].tokenSymbol);
2182
2183
  } 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
- }
2184
+ setSelectSourceChainName("Base");
2185
+ setSelectSourceTokenSymbol("USDC");
2214
2186
  }
2215
- }, [authExecutor, authExecutor.pendingSelectSource, advancedSettings, selectedWalletId, selectedAccountId, accounts]);
2187
+ initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
2188
+ }, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
2216
2189
  const handlePay = useCallback(async () => {
2217
2190
  const parsedAmount = parseFloat(amount);
2218
2191
  if (isNaN(parsedAmount) || parsedAmount <= 0) {
@@ -2375,6 +2348,37 @@ function SwypePayment({
2375
2348
  ]
2376
2349
  }
2377
2350
  );
2351
+ const displayedSelectSourceChoices = selectSourceChoices.length > 0 ? selectSourceChoices : [
2352
+ {
2353
+ chainName: "Base",
2354
+ balance: 0,
2355
+ tokens: [{ tokenSymbol: "USDC", balance: 0 }]
2356
+ }
2357
+ ];
2358
+ const selectedChainChoice = displayedSelectSourceChoices.find(
2359
+ (choice) => choice.chainName === selectSourceChainName
2360
+ ) ?? displayedSelectSourceChoices[0];
2361
+ const selectSourceTokenChoices = selectedChainChoice?.tokens ?? [];
2362
+ const resolvedSelectSourceChainName = selectedChainChoice?.chainName ?? selectSourceChainName;
2363
+ const resolvedSelectSourceTokenSymbol = selectSourceTokenChoices.find(
2364
+ (token) => token.tokenSymbol === selectSourceTokenSymbol
2365
+ )?.tokenSymbol ?? selectSourceTokenChoices[0]?.tokenSymbol ?? "";
2366
+ const canConfirmSelectSource = !!resolvedSelectSourceChainName && !!resolvedSelectSourceTokenSymbol;
2367
+ const handleSelectSourceChainChange = (chainName) => {
2368
+ setSelectSourceChainName(chainName);
2369
+ const nextChain = displayedSelectSourceChoices.find(
2370
+ (choice) => choice.chainName === chainName
2371
+ );
2372
+ if (!nextChain || nextChain.tokens.length === 0) {
2373
+ setSelectSourceTokenSymbol("");
2374
+ return;
2375
+ }
2376
+ const recommendedTokenForChain = selectSourceRecommended?.chainName === chainName ? selectSourceRecommended.tokenSymbol : null;
2377
+ const hasRecommendedToken = !!recommendedTokenForChain && nextChain.tokens.some((token) => token.tokenSymbol === recommendedTokenForChain);
2378
+ setSelectSourceTokenSymbol(
2379
+ hasRecommendedToken && recommendedTokenForChain ? recommendedTokenForChain : nextChain.tokens[0].tokenSymbol
2380
+ );
2381
+ };
2378
2382
  if (!ready) {
2379
2383
  return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "24px 0" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Initializing..." }) }) });
2380
2384
  }
@@ -2418,6 +2422,73 @@ function SwypePayment({
2418
2422
  /* @__PURE__ */ jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
2419
2423
  ] }) });
2420
2424
  }
2425
+ if (step === "register-passkey") {
2426
+ const handleRegisterPasskey = async () => {
2427
+ setRegisteringPasskey(true);
2428
+ setError(null);
2429
+ try {
2430
+ const token = await getAccessToken();
2431
+ if (!token) throw new Error("Not authenticated");
2432
+ const { credentialId, publicKey } = await createPasskeyCredential("Swype User");
2433
+ await registerPasskey(apiBaseUrl, token, credentialId, publicKey);
2434
+ if (depositAmount != null && depositAmount > 0) {
2435
+ setStep("ready");
2436
+ } else {
2437
+ setStep("enter-amount");
2438
+ }
2439
+ } catch (err) {
2440
+ const msg = err instanceof Error ? err.message : "Failed to register passkey";
2441
+ setError(msg);
2442
+ } finally {
2443
+ setRegisteringPasskey(false);
2444
+ }
2445
+ };
2446
+ return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
2447
+ /* @__PURE__ */ jsxs(
2448
+ "svg",
2449
+ {
2450
+ width: "48",
2451
+ height: "48",
2452
+ viewBox: "0 0 48 48",
2453
+ fill: "none",
2454
+ style: { margin: "0 auto 16px" },
2455
+ children: [
2456
+ /* @__PURE__ */ jsx("rect", { width: "48", height: "48", rx: "12", fill: tokens.accent + "20" }),
2457
+ /* @__PURE__ */ jsx(
2458
+ "path",
2459
+ {
2460
+ d: "M24 16c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 10c-4.42 0-8 1.79-8 4v2h16v-2c0-2.21-3.58-4-8-4z",
2461
+ fill: tokens.accent
2462
+ }
2463
+ )
2464
+ ]
2465
+ }
2466
+ ),
2467
+ /* @__PURE__ */ jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Set Up Passkey" }),
2468
+ /* @__PURE__ */ jsx(
2469
+ "p",
2470
+ {
2471
+ style: {
2472
+ fontSize: "0.875rem",
2473
+ color: tokens.textSecondary,
2474
+ margin: "0 0 24px 0",
2475
+ lineHeight: 1.5
2476
+ },
2477
+ children: "Create a passkey for secure, one-touch payments. This only needs to be done once."
2478
+ }
2479
+ ),
2480
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle, children: error }),
2481
+ /* @__PURE__ */ jsx(
2482
+ "button",
2483
+ {
2484
+ style: registeringPasskey ? btnDisabled : btnPrimary,
2485
+ disabled: registeringPasskey,
2486
+ onClick: handleRegisterPasskey,
2487
+ children: registeringPasskey ? "Creating passkey..." : "Create Passkey"
2488
+ }
2489
+ )
2490
+ ] }) });
2491
+ }
2421
2492
  if (step === "enter-amount") {
2422
2493
  const parsedAmount = parseFloat(amount);
2423
2494
  const canContinue = !isNaN(parsedAmount) && parsedAmount > 0;
@@ -2736,6 +2807,142 @@ function SwypePayment({
2736
2807
  ] });
2737
2808
  }
2738
2809
  if (step === "processing") {
2810
+ if (pendingSelectSourceAction) {
2811
+ const chainValue = resolvedSelectSourceChainName;
2812
+ const tokenValue = resolvedSelectSourceTokenSymbol;
2813
+ return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
2814
+ stepBadge("Select source"),
2815
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
2816
+ /* @__PURE__ */ jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Select payment source" }),
2817
+ /* @__PURE__ */ jsx(
2818
+ "p",
2819
+ {
2820
+ style: {
2821
+ fontSize: "0.85rem",
2822
+ color: tokens.textSecondary,
2823
+ margin: 0,
2824
+ lineHeight: 1.5
2825
+ },
2826
+ children: "Confirm the chain and token to use for this transfer."
2827
+ }
2828
+ )
2829
+ ] }),
2830
+ /* @__PURE__ */ jsxs(
2831
+ "div",
2832
+ {
2833
+ style: {
2834
+ fontSize: "0.825rem",
2835
+ color: tokens.textSecondary,
2836
+ marginBottom: "16px",
2837
+ padding: "14px",
2838
+ background: tokens.bgInput,
2839
+ borderRadius: tokens.radius,
2840
+ border: `1px solid ${tokens.border}`
2841
+ },
2842
+ children: [
2843
+ /* @__PURE__ */ jsx(
2844
+ "label",
2845
+ {
2846
+ htmlFor: "swype-select-source-chain",
2847
+ style: {
2848
+ display: "block",
2849
+ fontSize: "0.75rem",
2850
+ fontWeight: 600,
2851
+ marginBottom: "6px",
2852
+ color: tokens.textMuted,
2853
+ textTransform: "uppercase",
2854
+ letterSpacing: "0.04em"
2855
+ },
2856
+ children: "Chain"
2857
+ }
2858
+ ),
2859
+ /* @__PURE__ */ jsx(
2860
+ "select",
2861
+ {
2862
+ id: "swype-select-source-chain",
2863
+ value: chainValue,
2864
+ onChange: (event) => handleSelectSourceChainChange(event.target.value),
2865
+ style: {
2866
+ width: "100%",
2867
+ marginBottom: "12px",
2868
+ padding: "10px 12px",
2869
+ borderRadius: tokens.radius,
2870
+ border: `1px solid ${tokens.border}`,
2871
+ background: tokens.bgCard,
2872
+ color: tokens.text,
2873
+ fontFamily: "inherit",
2874
+ fontSize: "0.875rem",
2875
+ outline: "none"
2876
+ },
2877
+ children: displayedSelectSourceChoices.map((chainChoice) => /* @__PURE__ */ jsxs("option", { value: chainChoice.chainName, children: [
2878
+ chainChoice.chainName,
2879
+ " ($",
2880
+ chainChoice.balance.toFixed(2),
2881
+ ")"
2882
+ ] }, chainChoice.chainName))
2883
+ }
2884
+ ),
2885
+ /* @__PURE__ */ jsx(
2886
+ "label",
2887
+ {
2888
+ htmlFor: "swype-select-source-token",
2889
+ style: {
2890
+ display: "block",
2891
+ fontSize: "0.75rem",
2892
+ fontWeight: 600,
2893
+ marginBottom: "6px",
2894
+ color: tokens.textMuted,
2895
+ textTransform: "uppercase",
2896
+ letterSpacing: "0.04em"
2897
+ },
2898
+ children: "Token"
2899
+ }
2900
+ ),
2901
+ /* @__PURE__ */ jsx(
2902
+ "select",
2903
+ {
2904
+ id: "swype-select-source-token",
2905
+ value: tokenValue,
2906
+ onChange: (event) => setSelectSourceTokenSymbol(event.target.value),
2907
+ style: {
2908
+ width: "100%",
2909
+ padding: "10px 12px",
2910
+ borderRadius: tokens.radius,
2911
+ border: `1px solid ${tokens.border}`,
2912
+ background: tokens.bgCard,
2913
+ color: tokens.text,
2914
+ fontFamily: "inherit",
2915
+ fontSize: "0.875rem",
2916
+ outline: "none"
2917
+ },
2918
+ children: selectSourceTokenChoices.map((tokenChoice) => /* @__PURE__ */ jsxs("option", { value: tokenChoice.tokenSymbol, children: [
2919
+ tokenChoice.tokenSymbol,
2920
+ " ($",
2921
+ tokenChoice.balance.toFixed(2),
2922
+ ")"
2923
+ ] }, tokenChoice.tokenSymbol))
2924
+ }
2925
+ )
2926
+ ]
2927
+ }
2928
+ ),
2929
+ /* @__PURE__ */ jsx(
2930
+ "button",
2931
+ {
2932
+ style: canConfirmSelectSource ? btnPrimary : btnDisabled,
2933
+ disabled: !canConfirmSelectSource,
2934
+ onClick: () => {
2935
+ if (!canConfirmSelectSource) return;
2936
+ authExecutor.resolveSelectSource({
2937
+ chainName: resolvedSelectSourceChainName,
2938
+ tokenSymbol: resolvedSelectSourceTokenSymbol
2939
+ });
2940
+ },
2941
+ children: "Confirm source"
2942
+ }
2943
+ )
2944
+ ] });
2945
+ }
2739
2946
  if (transferSigning.signing && transferSigning.signPayload) {
2740
2947
  const payload = transferSigning.signPayload;
2741
2948
  return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
@@ -2776,20 +2983,20 @@ function SwypePayment({
2776
2983
  const currentActionType = authExecutor.currentAction?.type;
2777
2984
  const getRegistrationMessage = () => {
2778
2985
  switch (currentActionType) {
2779
- case "REGISTER_PASSKEY":
2986
+ case "CREATE_SMART_ACCOUNT":
2780
2987
  return {
2781
- label: "Creating your passkey...",
2782
- description: "Set up a passkey for secure, one-touch payments."
2988
+ label: "Creating your smart account...",
2989
+ description: "Setting up your smart account for gasless payments."
2783
2990
  };
2784
- case "UPGRADE_SMART_ACCOUNT":
2991
+ case "APPROVE_PERMIT2":
2785
2992
  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."
2993
+ label: "Approving token access...",
2994
+ description: "Approve the prompt in your wallet to allow secure token transfers."
2788
2995
  };
2789
- case "GRANT_PERMISSIONS":
2996
+ case "SIGN_PERMIT2":
2790
2997
  return {
2791
- label: "Setting up permissions...",
2792
- description: "Signing delegation permissions. Transfer routing and gas sponsorship are handled by Swype backend."
2998
+ label: "Signing transfer permission...",
2999
+ description: "Sign the permit to allow your smart account to transfer tokens on your behalf."
2793
3000
  };
2794
3001
  default:
2795
3002
  return { label: "", description: "" };
@@ -2972,6 +3179,6 @@ function SwypePayment({
2972
3179
  return null;
2973
3180
  }
2974
3181
 
2975
- export { SwypePayment, SwypeProvider, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling };
3182
+ export { SwypePayment, SwypeProvider, createPasskeyCredential, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling, useTransferSigning };
2976
3183
  //# sourceMappingURL=index.js.map
2977
3184
  //# sourceMappingURL=index.js.map