@swype-org/react-sdk 0.1.3 → 0.1.4

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
@@ -527,160 +527,10 @@ async function getWalletClient(config, parameters = {}) {
527
527
  const client = await getConnectorClient(config, parameters);
528
528
  return client.extend(viem.walletActions);
529
529
  }
530
-
531
- // src/authorization/upgrade.ts
532
- function isNonEmptyString(value) {
533
- return typeof value === "string" && value.trim().length > 0;
534
- }
535
- function isValidChainId(value) {
536
- return typeof value === "number" && Number.isInteger(value) && value > 0;
537
- }
538
- function isRpcClient(value) {
539
- return typeof value === "object" && value !== null && "request" in value && typeof value.request === "function";
540
- }
541
- function sleep(ms) {
542
- return new Promise((resolve) => setTimeout(resolve, ms));
543
- }
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.`);
574
- }
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}`);
594
- }
595
- }
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.");
601
- }
602
- const resolvedSmartAccountAddress = metadata?.smartAccountAddress ?? sender;
603
- if (!isNonEmptyString(resolvedSmartAccountAddress)) {
604
- throw new Error("No connected account address found for smart account upgrade.");
605
- }
606
- const chainId = metadata?.upgradePayload?.authorization?.chainId;
607
- if (!isValidChainId(chainId)) {
608
- throw new Error("Invalid or missing chainId in upgrade metadata.");
609
- }
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
- );
614
- }
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
- };
677
- }
678
-
679
- // src/hooks.ts
680
- async function waitForWalletClient(wagmiConfig2, maxAttempts = 15, intervalMs = 200) {
530
+ async function waitForWalletClient(wagmiConfig2, walletClientParams = {}, maxAttempts = 15, intervalMs = 200) {
681
531
  for (let i = 0; i < maxAttempts; i++) {
682
532
  try {
683
- return await getWalletClient(wagmiConfig2);
533
+ return await getWalletClient(wagmiConfig2, walletClientParams);
684
534
  } catch {
685
535
  if (i === maxAttempts - 1) {
686
536
  throw new Error("Wallet not ready. Please try again.");
@@ -690,19 +540,29 @@ async function waitForWalletClient(wagmiConfig2, maxAttempts = 15, intervalMs =
690
540
  }
691
541
  throw new Error("Wallet not ready. Please try again.");
692
542
  }
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;
543
+ function parseSignTypedDataPayload(typedData) {
544
+ const domain = typedData.domain;
545
+ const types = typedData.types;
546
+ const primaryType = typedData.primaryType;
547
+ const message = typedData.message;
548
+ if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
549
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
699
550
  }
700
- return "Failed to upgrade account";
701
- }
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");
551
+ if (!types || typeof types !== "object" || Array.isArray(types)) {
552
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid types object.");
553
+ }
554
+ if (typeof primaryType !== "string") {
555
+ throw new Error("SIGN_PERMIT2 typedData is missing primaryType.");
556
+ }
557
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
558
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid message object.");
559
+ }
560
+ return {
561
+ domain,
562
+ types,
563
+ primaryType,
564
+ message
565
+ };
706
566
  }
707
567
  function useTransferPolling(intervalMs = 3e3) {
708
568
  const { apiBaseUrl } = useSwypeConfig();
@@ -974,88 +834,180 @@ function useAuthorizationExecutor() {
974
834
  },
975
835
  [wagmiConfig2]
976
836
  );
977
- const executeUpgradeSmartAccount = react.useCallback(
837
+ const executeCreateSmartAccount = react.useCallback(
838
+ async (action) => {
839
+ return {
840
+ actionId: action.id,
841
+ type: action.type,
842
+ status: "success",
843
+ message: "Smart account creation acknowledged. Server is deploying.",
844
+ data: {}
845
+ };
846
+ },
847
+ []
848
+ );
849
+ const executeApprovePermit2 = react.useCallback(
978
850
  async (action) => {
979
851
  try {
980
- const account = getAccount(wagmiConfig2);
981
852
  const walletClient = await waitForWalletClient(wagmiConfig2);
853
+ const account = getAccount(wagmiConfig2);
982
854
  const sender = account.address ?? walletClient.account?.address;
855
+ const expectedWalletAddress = action.metadata?.walletAddress;
983
856
  if (!sender) {
984
857
  throw new Error("Wallet account not available. Please connect your wallet.");
985
858
  }
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
- );
859
+ if (expectedWalletAddress && sender.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
860
+ return {
861
+ actionId: action.id,
862
+ type: action.type,
863
+ status: "error",
864
+ message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
865
+ };
990
866
  }
991
- const metadata = action.metadata;
992
- const { txHash, smartAccountAddress } = await submitUpgradeTransaction({
993
- walletClient,
994
- sender,
995
- metadata
867
+ const to = action.metadata?.to;
868
+ const data = action.metadata?.data;
869
+ const tokenSymbol = action.metadata?.tokenSymbol;
870
+ if (!to || !data) {
871
+ return {
872
+ actionId: action.id,
873
+ type: action.type,
874
+ status: "error",
875
+ message: "APPROVE_PERMIT2 metadata is missing transaction parameters (to, data)."
876
+ };
877
+ }
878
+ const txHash = await walletClient.request({
879
+ method: "eth_sendTransaction",
880
+ params: [
881
+ {
882
+ from: sender,
883
+ to,
884
+ data,
885
+ value: "0x0"
886
+ }
887
+ ]
996
888
  });
889
+ console.info(
890
+ `[swype-sdk][approve-permit2] ERC-20 approve tx sent. token=${tokenSymbol ?? to}, txHash=${txHash}`
891
+ );
997
892
  return {
998
893
  actionId: action.id,
999
894
  type: action.type,
1000
895
  status: "success",
1001
- message: "Account upgrade transaction submitted.",
1002
- data: {
1003
- txHash,
1004
- smartAccountAddress
1005
- }
896
+ message: `Approved Permit2 to spend ${tokenSymbol ?? "tokens"}.`,
897
+ data: { txHash }
1006
898
  };
1007
899
  } catch (err) {
1008
- console.error("Failed to upgrade account", err);
1009
- const message = extractErrorMessage(err);
900
+ const message = err instanceof Error ? err.message : "Failed to approve Permit2";
1010
901
  const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
1011
902
  return {
1012
903
  actionId: action.id,
1013
904
  type: action.type,
1014
905
  status: "error",
1015
- message: isRejected ? "You rejected the upgrade transaction. Please approve the transaction prompt in your wallet to continue." : message
906
+ message: isRejected ? "You rejected the approval transaction. Please approve the Permit2 spending allowance in your wallet to continue." : message
1016
907
  };
1017
908
  }
1018
909
  },
1019
910
  [wagmiConfig2]
1020
911
  );
1021
- const executeGrantPermissions = react.useCallback(
912
+ const executeSignPermit2 = react.useCallback(
1022
913
  async (action) => {
1023
914
  try {
1024
- const walletClient = await waitForWalletClient(wagmiConfig2);
1025
- const signingPayload = action.metadata?.signingPayload;
1026
- const tokens = action.metadata?.tokens;
1027
- if (!signingPayload) {
915
+ const expectedWalletAddress = action.metadata?.walletAddress;
916
+ const walletClient = await waitForWalletClient(
917
+ wagmiConfig2,
918
+ expectedWalletAddress ? { account: expectedWalletAddress } : {}
919
+ );
920
+ const account = getAccount(wagmiConfig2);
921
+ const connectedAddress = account.address ?? walletClient.account?.address;
922
+ const sender = expectedWalletAddress ?? connectedAddress;
923
+ if (!sender) {
924
+ throw new Error("Wallet account not available. Please connect your wallet.");
925
+ }
926
+ if (expectedWalletAddress && connectedAddress && connectedAddress.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
927
+ return {
928
+ actionId: action.id,
929
+ type: action.type,
930
+ status: "error",
931
+ message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
932
+ };
933
+ }
934
+ let typedData = action.metadata?.typedData;
935
+ const tokenSymbol = action.metadata?.tokenSymbol;
936
+ if (!typedData && sessionIdRef.current) {
937
+ const POLL_INTERVAL_MS = 1e3;
938
+ const MAX_POLLS = 15;
939
+ for (let i = 0; i < MAX_POLLS; i++) {
940
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
941
+ const session = await fetchAuthorizationSession(
942
+ apiBaseUrl,
943
+ sessionIdRef.current
944
+ );
945
+ const updatedAction = session.actions.find((a) => a.id === action.id);
946
+ typedData = updatedAction?.metadata?.typedData;
947
+ if (typedData) break;
948
+ }
949
+ }
950
+ if (!typedData) {
1028
951
  return {
1029
952
  actionId: action.id,
1030
953
  type: action.type,
1031
954
  status: "error",
1032
- message: "No signing payload in action metadata."
955
+ message: "SIGN_PERMIT2 metadata is missing typedData. The server may still be preparing the signing payload."
1033
956
  };
1034
957
  }
1035
- const signature = await walletClient.signMessage({
1036
- message: JSON.stringify(signingPayload)
958
+ const parsedTypedData = parseSignTypedDataPayload(typedData);
959
+ console.info(
960
+ `[swype-sdk][sign-permit2] Signing typed data. expectedOwner=${expectedWalletAddress ?? "N/A"}, senderParam=${sender}, connectedAddress=${connectedAddress ?? "N/A"}, primaryType=${parsedTypedData.primaryType}, domainChainId=${String(parsedTypedData.domain.chainId ?? "N/A")}, verifyingContract=${String(parsedTypedData.domain.verifyingContract ?? "N/A")}`
961
+ );
962
+ const signature = await walletClient.signTypedData({
963
+ account: sender,
964
+ domain: parsedTypedData.domain,
965
+ types: parsedTypedData.types,
966
+ primaryType: parsedTypedData.primaryType,
967
+ message: parsedTypedData.message
1037
968
  });
1038
- const tokenSummary = tokens?.map((t) => `${t.symbol} on ${t.chainName}`).join(", ") ?? "all tokens";
969
+ const recoverInput = {
970
+ domain: parsedTypedData.domain,
971
+ types: parsedTypedData.types,
972
+ primaryType: parsedTypedData.primaryType,
973
+ message: parsedTypedData.message,
974
+ signature
975
+ };
976
+ const recoveredSigner = await viem.recoverTypedDataAddress(recoverInput);
977
+ const expectedSigner = (expectedWalletAddress ?? sender).toLowerCase();
978
+ console.info(
979
+ `[swype-sdk][sign-permit2] Signature recovered. recoveredSigner=${recoveredSigner}, expectedSigner=${expectedSigner}`
980
+ );
981
+ if (recoveredSigner.toLowerCase() !== expectedSigner) {
982
+ return {
983
+ actionId: action.id,
984
+ type: action.type,
985
+ status: "error",
986
+ message: `Wallet signed with ${recoveredSigner}, but source wallet is ${expectedWalletAddress ?? sender}. Please switch to the source wallet in MetaMask and retry.`
987
+ };
988
+ }
989
+ console.info(
990
+ `[swype-sdk][sign-permit2] Permit2 EIP-712 signature obtained. token=${tokenSymbol ?? "unknown"}`
991
+ );
1039
992
  return {
1040
993
  actionId: action.id,
1041
994
  type: action.type,
1042
995
  status: "success",
1043
- message: `Permissions granted for ${tokenSummary}.`,
1044
- data: {
1045
- signedDelegation: signature,
1046
- tokens
1047
- }
996
+ message: `Permit2 allowance signed for ${tokenSymbol ?? "tokens"}.`,
997
+ data: { signature }
1048
998
  };
1049
999
  } catch (err) {
1000
+ const message = err instanceof Error ? err.message : "Failed to sign Permit2 allowance";
1001
+ const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
1050
1002
  return {
1051
1003
  actionId: action.id,
1052
1004
  type: action.type,
1053
1005
  status: "error",
1054
- message: err instanceof Error ? err.message : "Failed to grant permissions"
1006
+ message: isRejected ? "You rejected the Permit2 signature request. Please approve the signature in your wallet to allow fund transfers." : message
1055
1007
  };
1056
1008
  }
1057
1009
  },
1058
- [wagmiConfig2]
1010
+ [wagmiConfig2, apiBaseUrl]
1059
1011
  );
1060
1012
  const executeAction = react.useCallback(
1061
1013
  async (action) => {
@@ -1069,10 +1021,12 @@ function useAuthorizationExecutor() {
1069
1021
  return executeSwitchChain(action);
1070
1022
  case "REGISTER_PASSKEY":
1071
1023
  return executeRegisterPasskey(action);
1072
- case "UPGRADE_SMART_ACCOUNT":
1073
- return executeUpgradeSmartAccount(action);
1074
- case "GRANT_PERMISSIONS":
1075
- return executeGrantPermissions(action);
1024
+ case "CREATE_SMART_ACCOUNT":
1025
+ return executeCreateSmartAccount(action);
1026
+ case "APPROVE_PERMIT2":
1027
+ return executeApprovePermit2(action);
1028
+ case "SIGN_PERMIT2":
1029
+ return executeSignPermit2(action);
1076
1030
  default:
1077
1031
  return {
1078
1032
  actionId: action.id,
@@ -1082,7 +1036,7 @@ function useAuthorizationExecutor() {
1082
1036
  };
1083
1037
  }
1084
1038
  },
1085
- [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeUpgradeSmartAccount, executeGrantPermissions]
1039
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeCreateSmartAccount, executeApprovePermit2, executeSignPermit2]
1086
1040
  );
1087
1041
  const executeSession = react.useCallback(
1088
1042
  async (transfer) => {
@@ -2783,15 +2737,20 @@ function SwypePayment({
2783
2737
  label: "Creating your passkey...",
2784
2738
  description: "Set up a passkey for secure, one-touch payments."
2785
2739
  };
2786
- case "UPGRADE_SMART_ACCOUNT":
2740
+ case "CREATE_SMART_ACCOUNT":
2741
+ return {
2742
+ label: "Creating your smart account...",
2743
+ description: "Setting up your smart account for gasless payments."
2744
+ };
2745
+ case "APPROVE_PERMIT2":
2787
2746
  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."
2747
+ label: "Approving token access...",
2748
+ description: "Approve the prompt in your wallet to allow secure token transfers."
2790
2749
  };
2791
- case "GRANT_PERMISSIONS":
2750
+ case "SIGN_PERMIT2":
2792
2751
  return {
2793
- label: "Setting up permissions...",
2794
- description: "Signing delegation permissions. Transfer routing and gas sponsorship are handled by Swype backend."
2752
+ label: "Signing transfer permission...",
2753
+ description: "Sign the permit to allow your smart account to transfer tokens on your behalf."
2795
2754
  };
2796
2755
  default:
2797
2756
  return { label: "", description: "" };