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