@swype-org/react-sdk 0.1.4 → 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 +808 -557
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -11
- package/dist/index.d.ts +84 -11
- package/dist/index.js +806 -558
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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: {
|
|
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,24 +558,78 @@ async function getWalletClient(config, parameters = {}) {
|
|
|
525
558
|
const client = await getConnectorClient(config, parameters);
|
|
526
559
|
return client.extend(walletActions);
|
|
527
560
|
}
|
|
528
|
-
|
|
529
|
-
|
|
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 };
|
|
570
|
+
}
|
|
571
|
+
function actionError(action, message) {
|
|
572
|
+
return { actionId: action.id, type: action.type, status: "error", message };
|
|
573
|
+
}
|
|
574
|
+
function isUserRejection(msg) {
|
|
575
|
+
const lower = msg.toLowerCase();
|
|
576
|
+
return lower.includes("rejected") || lower.includes("denied");
|
|
577
|
+
}
|
|
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);
|
|
582
|
+
}
|
|
583
|
+
function toBase64(buffer) {
|
|
584
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
585
|
+
}
|
|
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);
|
|
593
|
+
}
|
|
594
|
+
return bytes;
|
|
595
|
+
}
|
|
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();
|
|
601
|
+
}
|
|
602
|
+
const processValue = globalThis.process?.env?.[name];
|
|
603
|
+
if (typeof processValue === "string" && processValue.trim().length > 0) {
|
|
604
|
+
return processValue.trim();
|
|
605
|
+
}
|
|
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();
|
|
612
|
+
}
|
|
613
|
+
if (typeof window !== "undefined") {
|
|
614
|
+
return window.location.hostname;
|
|
615
|
+
}
|
|
616
|
+
return "localhost";
|
|
617
|
+
}
|
|
618
|
+
async function waitForWalletClient(wagmiConfig2, params = {}) {
|
|
619
|
+
for (let i = 0; i < WALLET_CLIENT_MAX_ATTEMPTS; i++) {
|
|
530
620
|
try {
|
|
531
|
-
return await getWalletClient(wagmiConfig2,
|
|
621
|
+
return await getWalletClient(wagmiConfig2, params);
|
|
532
622
|
} catch {
|
|
533
|
-
if (i ===
|
|
623
|
+
if (i === WALLET_CLIENT_MAX_ATTEMPTS - 1) {
|
|
534
624
|
throw new Error("Wallet not ready. Please try again.");
|
|
535
625
|
}
|
|
536
|
-
await new Promise((r) => setTimeout(r,
|
|
626
|
+
await new Promise((r) => setTimeout(r, WALLET_CLIENT_POLL_MS));
|
|
537
627
|
}
|
|
538
628
|
}
|
|
539
629
|
throw new Error("Wallet not ready. Please try again.");
|
|
540
630
|
}
|
|
541
631
|
function parseSignTypedDataPayload(typedData) {
|
|
542
|
-
const domain = typedData
|
|
543
|
-
const types = typedData.types;
|
|
544
|
-
const primaryType = typedData.primaryType;
|
|
545
|
-
const message = typedData.message;
|
|
632
|
+
const { domain, types, primaryType, message } = typedData;
|
|
546
633
|
if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
|
|
547
634
|
throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
|
|
548
635
|
}
|
|
@@ -562,6 +649,46 @@ function parseSignTypedDataPayload(typedData) {
|
|
|
562
649
|
message
|
|
563
650
|
};
|
|
564
651
|
}
|
|
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
|
+
};
|
|
691
|
+
}
|
|
565
692
|
function useTransferPolling(intervalMs = 3e3) {
|
|
566
693
|
const { apiBaseUrl } = useSwypeConfig();
|
|
567
694
|
const { getAccessToken } = usePrivy();
|
|
@@ -610,8 +737,247 @@ function useTransferPolling(intervalMs = 3e3) {
|
|
|
610
737
|
useEffect(() => () => stopPolling(), [stopPolling]);
|
|
611
738
|
return { transfer, error, isPolling, startPolling, stopPolling };
|
|
612
739
|
}
|
|
613
|
-
function
|
|
614
|
-
|
|
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;
|
|
615
981
|
const wagmiConfig2 = useConfig();
|
|
616
982
|
const { connectAsync, connectors } = useConnect();
|
|
617
983
|
const { switchChainAsync } = useSwitchChain();
|
|
@@ -622,6 +988,7 @@ function useAuthorizationExecutor() {
|
|
|
622
988
|
const executingRef = useRef(false);
|
|
623
989
|
const [pendingSelectSource, setPendingSelectSource] = useState(null);
|
|
624
990
|
const selectSourceResolverRef = useRef(null);
|
|
991
|
+
const sessionIdRef = useRef(null);
|
|
625
992
|
const resolveSelectSource = useCallback((selection) => {
|
|
626
993
|
if (selectSourceResolverRef.current) {
|
|
627
994
|
selectSourceResolverRef.current(selection);
|
|
@@ -629,487 +996,82 @@ function useAuthorizationExecutor() {
|
|
|
629
996
|
setPendingSelectSource(null);
|
|
630
997
|
}
|
|
631
998
|
}, []);
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (account.isConnected && account.address) {
|
|
638
|
-
const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
|
|
639
|
-
return {
|
|
640
|
-
actionId: action.id,
|
|
641
|
-
type: action.type,
|
|
642
|
-
status: "success",
|
|
643
|
-
message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
|
|
644
|
-
data: { accounts: [account.address], chainId: hexChainId2 }
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const targetId = action.metadata?.wagmiConnectorId;
|
|
648
|
-
const metaMaskConnector = connectors.find((c) => {
|
|
649
|
-
const id = c.id.toLowerCase();
|
|
650
|
-
const name = c.name.toLowerCase();
|
|
651
|
-
return id.includes("metamask") || name.includes("metamask");
|
|
652
|
-
});
|
|
653
|
-
const connector = targetId ? connectors.find((c) => c.id === targetId) ?? metaMaskConnector ?? connectors[0] : metaMaskConnector ?? connectors[0];
|
|
654
|
-
if (!connector) {
|
|
655
|
-
return {
|
|
656
|
-
actionId: action.id,
|
|
657
|
-
type: action.type,
|
|
658
|
-
status: "error",
|
|
659
|
-
message: "No wallet connector found. Please install a supported wallet."
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
const result = await connectAsync({ connector });
|
|
663
|
-
const hexChainId = `0x${result.chainId.toString(16)}`;
|
|
664
|
-
return {
|
|
665
|
-
actionId: action.id,
|
|
666
|
-
type: action.type,
|
|
667
|
-
status: "success",
|
|
668
|
-
message: `Connected to ${connector.name}. Account: ${result.accounts[0]}, Chain: ${hexChainId}`,
|
|
669
|
-
data: { accounts: [...result.accounts], chainId: hexChainId }
|
|
670
|
-
};
|
|
671
|
-
} catch (err) {
|
|
672
|
-
return {
|
|
673
|
-
actionId: action.id,
|
|
674
|
-
type: action.type,
|
|
675
|
-
status: "error",
|
|
676
|
-
message: err instanceof Error ? err.message : "Failed to connect wallet"
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
},
|
|
680
|
-
[wagmiConfig2, connectors, connectAsync]
|
|
681
|
-
);
|
|
682
|
-
const executeSelectSource = useCallback(
|
|
683
|
-
async (action) => {
|
|
684
|
-
try {
|
|
685
|
-
const options = action.metadata?.options;
|
|
686
|
-
const recommended = action.metadata?.recommended;
|
|
687
|
-
if (!options || options.length <= 1) {
|
|
688
|
-
const selection2 = recommended ?? { chainName: "Base", tokenSymbol: "USDC" };
|
|
689
|
-
return {
|
|
690
|
-
actionId: action.id,
|
|
691
|
-
type: action.type,
|
|
692
|
-
status: "success",
|
|
693
|
-
message: `Auto-selected ${selection2.tokenSymbol} on ${selection2.chainName}.`,
|
|
694
|
-
data: {
|
|
695
|
-
selectedChainName: selection2.chainName,
|
|
696
|
-
selectedTokenSymbol: selection2.tokenSymbol
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
const selection = await new Promise((resolve) => {
|
|
701
|
-
selectSourceResolverRef.current = resolve;
|
|
702
|
-
setPendingSelectSource(action);
|
|
703
|
-
});
|
|
704
|
-
return {
|
|
705
|
-
actionId: action.id,
|
|
706
|
-
type: action.type,
|
|
707
|
-
status: "success",
|
|
708
|
-
message: `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
|
|
709
|
-
data: {
|
|
710
|
-
selectedChainName: selection.chainName,
|
|
711
|
-
selectedTokenSymbol: selection.tokenSymbol
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
} catch (err) {
|
|
715
|
-
return {
|
|
716
|
-
actionId: action.id,
|
|
717
|
-
type: action.type,
|
|
718
|
-
status: "error",
|
|
719
|
-
message: err instanceof Error ? err.message : "Failed to select source"
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
},
|
|
723
|
-
[]
|
|
724
|
-
);
|
|
725
|
-
const executeSwitchChain = useCallback(
|
|
726
|
-
async (action) => {
|
|
727
|
-
try {
|
|
728
|
-
const account = getAccount(wagmiConfig2);
|
|
729
|
-
const targetChainIdHex = action.metadata?.targetChainId;
|
|
730
|
-
if (!targetChainIdHex) {
|
|
731
|
-
return {
|
|
732
|
-
actionId: action.id,
|
|
733
|
-
type: action.type,
|
|
734
|
-
status: "error",
|
|
735
|
-
message: "No targetChainId in action metadata."
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
const targetChainIdNum = parseInt(targetChainIdHex, 16);
|
|
739
|
-
const hexChainId = `0x${targetChainIdNum.toString(16)}`;
|
|
740
|
-
if (account.chainId === targetChainIdNum) {
|
|
741
|
-
return {
|
|
742
|
-
actionId: action.id,
|
|
743
|
-
type: action.type,
|
|
744
|
-
status: "success",
|
|
745
|
-
message: `Already on chain ${hexChainId}. Skipped.`,
|
|
746
|
-
data: { chainId: hexChainId, switched: false }
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
await switchChainAsync({ chainId: targetChainIdNum });
|
|
750
|
-
return {
|
|
751
|
-
actionId: action.id,
|
|
752
|
-
type: action.type,
|
|
753
|
-
status: "success",
|
|
754
|
-
message: `Switched to chain ${hexChainId}.`,
|
|
755
|
-
data: { chainId: hexChainId, switched: true }
|
|
756
|
-
};
|
|
757
|
-
} catch (err) {
|
|
758
|
-
return {
|
|
759
|
-
actionId: action.id,
|
|
760
|
-
type: action.type,
|
|
761
|
-
status: "error",
|
|
762
|
-
message: err instanceof Error ? err.message : "Failed to switch chain"
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
},
|
|
766
|
-
[wagmiConfig2, switchChainAsync]
|
|
767
|
-
);
|
|
768
|
-
const executeRegisterPasskey = useCallback(
|
|
769
|
-
async (action) => {
|
|
770
|
-
try {
|
|
771
|
-
const account = getAccount(wagmiConfig2);
|
|
772
|
-
const challenge = new Uint8Array(32);
|
|
773
|
-
crypto.getRandomValues(challenge);
|
|
774
|
-
const credential = await navigator.credentials.create({
|
|
775
|
-
publicKey: {
|
|
776
|
-
challenge,
|
|
777
|
-
rp: {
|
|
778
|
-
name: "Swype",
|
|
779
|
-
id: window.location.hostname
|
|
780
|
-
},
|
|
781
|
-
user: {
|
|
782
|
-
id: new TextEncoder().encode(account.address ?? "user"),
|
|
783
|
-
name: account.address ?? "Swype User",
|
|
784
|
-
displayName: "Swype User"
|
|
785
|
-
},
|
|
786
|
-
pubKeyCredParams: [
|
|
787
|
-
{ alg: -7, type: "public-key" },
|
|
788
|
-
// ES256 (P-256)
|
|
789
|
-
{ alg: -257, type: "public-key" }
|
|
790
|
-
// RS256
|
|
791
|
-
],
|
|
792
|
-
authenticatorSelection: {
|
|
793
|
-
authenticatorAttachment: "platform",
|
|
794
|
-
residentKey: "preferred",
|
|
795
|
-
userVerification: "required"
|
|
796
|
-
},
|
|
797
|
-
timeout: 6e4
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
if (!credential) {
|
|
801
|
-
return {
|
|
802
|
-
actionId: action.id,
|
|
803
|
-
type: action.type,
|
|
804
|
-
status: "error",
|
|
805
|
-
message: "Passkey creation was cancelled."
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
const response = credential.response;
|
|
809
|
-
const publicKeyBytes = response.getPublicKey?.();
|
|
810
|
-
const credentialId = btoa(
|
|
811
|
-
String.fromCharCode(...new Uint8Array(credential.rawId))
|
|
812
|
-
);
|
|
813
|
-
const publicKey = publicKeyBytes ? btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))) : "";
|
|
814
|
-
return {
|
|
815
|
-
actionId: action.id,
|
|
816
|
-
type: action.type,
|
|
817
|
-
status: "success",
|
|
818
|
-
message: "Passkey created successfully.",
|
|
819
|
-
data: {
|
|
820
|
-
credentialId,
|
|
821
|
-
publicKey
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
|
-
} catch (err) {
|
|
825
|
-
return {
|
|
826
|
-
actionId: action.id,
|
|
827
|
-
type: action.type,
|
|
828
|
-
status: "error",
|
|
829
|
-
message: err instanceof Error ? err.message : "Failed to create passkey"
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
},
|
|
833
|
-
[wagmiConfig2]
|
|
834
|
-
);
|
|
835
|
-
const executeCreateSmartAccount = useCallback(
|
|
836
|
-
async (action) => {
|
|
837
|
-
return {
|
|
838
|
-
actionId: action.id,
|
|
839
|
-
type: action.type,
|
|
840
|
-
status: "success",
|
|
841
|
-
message: "Smart account creation acknowledged. Server is deploying.",
|
|
842
|
-
data: {}
|
|
843
|
-
};
|
|
844
|
-
},
|
|
999
|
+
const waitForSelection = useCallback(
|
|
1000
|
+
(action) => new Promise((resolve) => {
|
|
1001
|
+
selectSourceResolverRef.current = resolve;
|
|
1002
|
+
setPendingSelectSource(action);
|
|
1003
|
+
}),
|
|
845
1004
|
[]
|
|
846
1005
|
);
|
|
847
|
-
const
|
|
848
|
-
async (action) => {
|
|
849
|
-
try {
|
|
850
|
-
const walletClient = await waitForWalletClient(wagmiConfig2);
|
|
851
|
-
const account = getAccount(wagmiConfig2);
|
|
852
|
-
const sender = account.address ?? walletClient.account?.address;
|
|
853
|
-
const expectedWalletAddress = action.metadata?.walletAddress;
|
|
854
|
-
if (!sender) {
|
|
855
|
-
throw new Error("Wallet account not available. Please connect your wallet.");
|
|
856
|
-
}
|
|
857
|
-
if (expectedWalletAddress && sender.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
|
|
858
|
-
return {
|
|
859
|
-
actionId: action.id,
|
|
860
|
-
type: action.type,
|
|
861
|
-
status: "error",
|
|
862
|
-
message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
const to = action.metadata?.to;
|
|
866
|
-
const data = action.metadata?.data;
|
|
867
|
-
const tokenSymbol = action.metadata?.tokenSymbol;
|
|
868
|
-
if (!to || !data) {
|
|
869
|
-
return {
|
|
870
|
-
actionId: action.id,
|
|
871
|
-
type: action.type,
|
|
872
|
-
status: "error",
|
|
873
|
-
message: "APPROVE_PERMIT2 metadata is missing transaction parameters (to, data)."
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
const txHash = await walletClient.request({
|
|
877
|
-
method: "eth_sendTransaction",
|
|
878
|
-
params: [
|
|
879
|
-
{
|
|
880
|
-
from: sender,
|
|
881
|
-
to,
|
|
882
|
-
data,
|
|
883
|
-
value: "0x0"
|
|
884
|
-
}
|
|
885
|
-
]
|
|
886
|
-
});
|
|
887
|
-
console.info(
|
|
888
|
-
`[swype-sdk][approve-permit2] ERC-20 approve tx sent. token=${tokenSymbol ?? to}, txHash=${txHash}`
|
|
889
|
-
);
|
|
890
|
-
return {
|
|
891
|
-
actionId: action.id,
|
|
892
|
-
type: action.type,
|
|
893
|
-
status: "success",
|
|
894
|
-
message: `Approved Permit2 to spend ${tokenSymbol ?? "tokens"}.`,
|
|
895
|
-
data: { txHash }
|
|
896
|
-
};
|
|
897
|
-
} catch (err) {
|
|
898
|
-
const message = err instanceof Error ? err.message : "Failed to approve Permit2";
|
|
899
|
-
const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
|
|
900
|
-
return {
|
|
901
|
-
actionId: action.id,
|
|
902
|
-
type: action.type,
|
|
903
|
-
status: "error",
|
|
904
|
-
message: isRejected ? "You rejected the approval transaction. Please approve the Permit2 spending allowance in your wallet to continue." : message
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
},
|
|
908
|
-
[wagmiConfig2]
|
|
909
|
-
);
|
|
910
|
-
const executeSignPermit2 = useCallback(
|
|
911
|
-
async (action) => {
|
|
912
|
-
try {
|
|
913
|
-
const expectedWalletAddress = action.metadata?.walletAddress;
|
|
914
|
-
const walletClient = await waitForWalletClient(
|
|
915
|
-
wagmiConfig2,
|
|
916
|
-
expectedWalletAddress ? { account: expectedWalletAddress } : {}
|
|
917
|
-
);
|
|
918
|
-
const account = getAccount(wagmiConfig2);
|
|
919
|
-
const connectedAddress = account.address ?? walletClient.account?.address;
|
|
920
|
-
const sender = expectedWalletAddress ?? connectedAddress;
|
|
921
|
-
if (!sender) {
|
|
922
|
-
throw new Error("Wallet account not available. Please connect your wallet.");
|
|
923
|
-
}
|
|
924
|
-
if (expectedWalletAddress && connectedAddress && connectedAddress.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
|
|
925
|
-
return {
|
|
926
|
-
actionId: action.id,
|
|
927
|
-
type: action.type,
|
|
928
|
-
status: "error",
|
|
929
|
-
message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
let typedData = action.metadata?.typedData;
|
|
933
|
-
const tokenSymbol = action.metadata?.tokenSymbol;
|
|
934
|
-
if (!typedData && sessionIdRef.current) {
|
|
935
|
-
const POLL_INTERVAL_MS = 1e3;
|
|
936
|
-
const MAX_POLLS = 15;
|
|
937
|
-
for (let i = 0; i < MAX_POLLS; i++) {
|
|
938
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
939
|
-
const session = await fetchAuthorizationSession(
|
|
940
|
-
apiBaseUrl,
|
|
941
|
-
sessionIdRef.current
|
|
942
|
-
);
|
|
943
|
-
const updatedAction = session.actions.find((a) => a.id === action.id);
|
|
944
|
-
typedData = updatedAction?.metadata?.typedData;
|
|
945
|
-
if (typedData) break;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
if (!typedData) {
|
|
949
|
-
return {
|
|
950
|
-
actionId: action.id,
|
|
951
|
-
type: action.type,
|
|
952
|
-
status: "error",
|
|
953
|
-
message: "SIGN_PERMIT2 metadata is missing typedData. The server may still be preparing the signing payload."
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
const parsedTypedData = parseSignTypedDataPayload(typedData);
|
|
957
|
-
console.info(
|
|
958
|
-
`[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")}`
|
|
959
|
-
);
|
|
960
|
-
const signature = await walletClient.signTypedData({
|
|
961
|
-
account: sender,
|
|
962
|
-
domain: parsedTypedData.domain,
|
|
963
|
-
types: parsedTypedData.types,
|
|
964
|
-
primaryType: parsedTypedData.primaryType,
|
|
965
|
-
message: parsedTypedData.message
|
|
966
|
-
});
|
|
967
|
-
const recoverInput = {
|
|
968
|
-
domain: parsedTypedData.domain,
|
|
969
|
-
types: parsedTypedData.types,
|
|
970
|
-
primaryType: parsedTypedData.primaryType,
|
|
971
|
-
message: parsedTypedData.message,
|
|
972
|
-
signature
|
|
973
|
-
};
|
|
974
|
-
const recoveredSigner = await recoverTypedDataAddress(recoverInput);
|
|
975
|
-
const expectedSigner = (expectedWalletAddress ?? sender).toLowerCase();
|
|
976
|
-
console.info(
|
|
977
|
-
`[swype-sdk][sign-permit2] Signature recovered. recoveredSigner=${recoveredSigner}, expectedSigner=${expectedSigner}`
|
|
978
|
-
);
|
|
979
|
-
if (recoveredSigner.toLowerCase() !== expectedSigner) {
|
|
980
|
-
return {
|
|
981
|
-
actionId: action.id,
|
|
982
|
-
type: action.type,
|
|
983
|
-
status: "error",
|
|
984
|
-
message: `Wallet signed with ${recoveredSigner}, but source wallet is ${expectedWalletAddress ?? sender}. Please switch to the source wallet in MetaMask and retry.`
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
console.info(
|
|
988
|
-
`[swype-sdk][sign-permit2] Permit2 EIP-712 signature obtained. token=${tokenSymbol ?? "unknown"}`
|
|
989
|
-
);
|
|
990
|
-
return {
|
|
991
|
-
actionId: action.id,
|
|
992
|
-
type: action.type,
|
|
993
|
-
status: "success",
|
|
994
|
-
message: `Permit2 allowance signed for ${tokenSymbol ?? "tokens"}.`,
|
|
995
|
-
data: { signature }
|
|
996
|
-
};
|
|
997
|
-
} catch (err) {
|
|
998
|
-
const message = err instanceof Error ? err.message : "Failed to sign Permit2 allowance";
|
|
999
|
-
const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
|
|
1000
|
-
return {
|
|
1001
|
-
actionId: action.id,
|
|
1002
|
-
type: action.type,
|
|
1003
|
-
status: "error",
|
|
1004
|
-
message: isRejected ? "You rejected the Permit2 signature request. Please approve the signature in your wallet to allow fund transfers." : message
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
},
|
|
1008
|
-
[wagmiConfig2, apiBaseUrl]
|
|
1009
|
-
);
|
|
1010
|
-
const executeAction = useCallback(
|
|
1006
|
+
const dispatchAction = useCallback(
|
|
1011
1007
|
async (action) => {
|
|
1012
1008
|
setCurrentAction(action);
|
|
1013
1009
|
switch (action.type) {
|
|
1014
1010
|
case "OPEN_PROVIDER":
|
|
1015
|
-
return executeOpenProvider(action);
|
|
1011
|
+
return executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
|
|
1016
1012
|
case "SELECT_SOURCE":
|
|
1017
|
-
return executeSelectSource(action);
|
|
1013
|
+
return executeSelectSource(action, waitForSelection);
|
|
1018
1014
|
case "SWITCH_CHAIN":
|
|
1019
|
-
return executeSwitchChain(action);
|
|
1020
|
-
case "REGISTER_PASSKEY":
|
|
1021
|
-
return executeRegisterPasskey(action);
|
|
1022
|
-
case "CREATE_SMART_ACCOUNT":
|
|
1023
|
-
return executeCreateSmartAccount(action);
|
|
1015
|
+
return executeSwitchChain(action, wagmiConfig2, switchChainAsync);
|
|
1024
1016
|
case "APPROVE_PERMIT2":
|
|
1025
|
-
return executeApprovePermit2(action);
|
|
1017
|
+
return executeApprovePermit2(action, wagmiConfig2);
|
|
1026
1018
|
case "SIGN_PERMIT2":
|
|
1027
|
-
return executeSignPermit2(action);
|
|
1019
|
+
return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
|
|
1028
1020
|
default:
|
|
1029
|
-
return {
|
|
1030
|
-
actionId: action.id,
|
|
1031
|
-
type: action.type,
|
|
1032
|
-
status: "error",
|
|
1033
|
-
message: `Unsupported action type: ${action.type}`
|
|
1034
|
-
};
|
|
1021
|
+
return actionError(action, `Unsupported action type: ${action.type}`);
|
|
1035
1022
|
}
|
|
1036
1023
|
},
|
|
1037
|
-
[
|
|
1024
|
+
[wagmiConfig2, connectors, connectAsync, switchChainAsync, apiBaseUrl, waitForSelection]
|
|
1038
1025
|
);
|
|
1039
|
-
const
|
|
1040
|
-
async (
|
|
1026
|
+
const executeSessionById = useCallback(
|
|
1027
|
+
async (sessionId) => {
|
|
1041
1028
|
if (executingRef.current) return;
|
|
1042
1029
|
executingRef.current = true;
|
|
1043
|
-
if (!
|
|
1030
|
+
if (!sessionId) {
|
|
1044
1031
|
executingRef.current = false;
|
|
1045
|
-
throw new Error("No authorization
|
|
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>.");
|
|
1046
1037
|
}
|
|
1047
|
-
const sessionId = transfer.authorizationSessions[0].id;
|
|
1048
1038
|
sessionIdRef.current = sessionId;
|
|
1049
1039
|
setExecuting(true);
|
|
1050
1040
|
setError(null);
|
|
1051
1041
|
setResults([]);
|
|
1052
1042
|
try {
|
|
1053
|
-
let
|
|
1043
|
+
let session = await fetchAuthorizationSession(apiBaseUrl, sessionId);
|
|
1054
1044
|
const allResults = [];
|
|
1055
|
-
const
|
|
1056
|
-
let
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
let actionPollRetries = 0;
|
|
1060
|
-
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) {
|
|
1061
1049
|
await new Promise((r) => setTimeout(r, ACTION_POLL_INTERVAL_MS));
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1050
|
+
session = await fetchAuthorizationSession(apiBaseUrl, sessionId);
|
|
1051
|
+
pending = getPendingActions(session, completedIds);
|
|
1052
|
+
retries++;
|
|
1065
1053
|
}
|
|
1066
|
-
if (
|
|
1054
|
+
if (pending.length === 0 && session.status !== "AUTHORIZED") {
|
|
1067
1055
|
throw new Error("Authorization actions were not created in time. Please try again.");
|
|
1068
1056
|
}
|
|
1069
|
-
while (
|
|
1070
|
-
const action =
|
|
1071
|
-
if (
|
|
1072
|
-
const result = await
|
|
1057
|
+
while (pending.length > 0) {
|
|
1058
|
+
const action = pending[0];
|
|
1059
|
+
if (completedIds.has(action.id)) break;
|
|
1060
|
+
const result = await dispatchAction(action);
|
|
1073
1061
|
if (result.status === "error") {
|
|
1074
1062
|
allResults.push(result);
|
|
1075
1063
|
setResults([...allResults]);
|
|
1076
1064
|
throw new Error(result.message);
|
|
1077
1065
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1066
|
+
completedIds.add(action.id);
|
|
1067
|
+
allResults.push(result);
|
|
1068
|
+
session = await reportActionCompletion(
|
|
1080
1069
|
apiBaseUrl,
|
|
1081
1070
|
action.id,
|
|
1082
1071
|
result.data ?? {}
|
|
1083
1072
|
);
|
|
1084
|
-
currentSession = updatedSession;
|
|
1085
|
-
pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
|
|
1086
|
-
if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
|
|
1087
|
-
const chainResults = [result];
|
|
1088
|
-
while (pendingActions.length > 0) {
|
|
1089
|
-
const nextAction = pendingActions[0];
|
|
1090
|
-
const nextResult = await executeAction(nextAction);
|
|
1091
|
-
if (nextResult.status === "error") {
|
|
1092
|
-
chainResults.push(nextResult);
|
|
1093
|
-
allResults.push(...chainResults);
|
|
1094
|
-
setResults([...allResults]);
|
|
1095
|
-
throw new Error(nextResult.message);
|
|
1096
|
-
}
|
|
1097
|
-
completedActionIds.add(nextAction.id);
|
|
1098
|
-
const nextSession = await reportActionCompletion(
|
|
1099
|
-
apiBaseUrl,
|
|
1100
|
-
nextAction.id,
|
|
1101
|
-
nextResult.data ?? {}
|
|
1102
|
-
);
|
|
1103
|
-
currentSession = nextSession;
|
|
1104
|
-
chainResults.push(nextResult);
|
|
1105
|
-
pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
|
|
1106
|
-
}
|
|
1107
|
-
allResults.push(...chainResults);
|
|
1108
|
-
setResults([...allResults]);
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
allResults.push(result);
|
|
1112
1073
|
setResults([...allResults]);
|
|
1074
|
+
pending = getPendingActions(session, completedIds);
|
|
1113
1075
|
}
|
|
1114
1076
|
} catch (err) {
|
|
1115
1077
|
const msg = err instanceof Error ? err.message : "Authorization failed";
|
|
@@ -1121,7 +1083,16 @@ function useAuthorizationExecutor() {
|
|
|
1121
1083
|
executingRef.current = false;
|
|
1122
1084
|
}
|
|
1123
1085
|
},
|
|
1124
|
-
[apiBaseUrl,
|
|
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]
|
|
1125
1096
|
);
|
|
1126
1097
|
return {
|
|
1127
1098
|
executing,
|
|
@@ -1130,12 +1101,21 @@ function useAuthorizationExecutor() {
|
|
|
1130
1101
|
currentAction,
|
|
1131
1102
|
pendingSelectSource,
|
|
1132
1103
|
resolveSelectSource,
|
|
1104
|
+
executeSessionById,
|
|
1133
1105
|
executeSession
|
|
1134
1106
|
};
|
|
1135
1107
|
}
|
|
1136
|
-
function useTransferSigning(pollIntervalMs = 2e3) {
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
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;
|
|
1139
1119
|
const [signing, setSigning] = useState(false);
|
|
1140
1120
|
const [signPayload, setSignPayload] = useState(null);
|
|
1141
1121
|
const [error, setError] = useState(null);
|
|
@@ -1145,14 +1125,24 @@ function useTransferSigning(pollIntervalMs = 2e3) {
|
|
|
1145
1125
|
setError(null);
|
|
1146
1126
|
setSignPayload(null);
|
|
1147
1127
|
try {
|
|
1148
|
-
|
|
1149
|
-
|
|
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) {
|
|
1150
1136
|
throw new Error("Could not get access token");
|
|
1151
1137
|
}
|
|
1152
|
-
const MAX_POLLS = 60;
|
|
1153
1138
|
let payload = null;
|
|
1154
|
-
for (let i = 0; i <
|
|
1155
|
-
const transfer = await fetchTransfer(
|
|
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
|
+
);
|
|
1156
1146
|
if (transfer.signPayload) {
|
|
1157
1147
|
payload = transfer.signPayload;
|
|
1158
1148
|
setSignPayload(payload);
|
|
@@ -1166,14 +1156,16 @@ function useTransferSigning(pollIntervalMs = 2e3) {
|
|
|
1166
1156
|
if (!payload) {
|
|
1167
1157
|
throw new Error("Timed out waiting for sign payload. Please try again.");
|
|
1168
1158
|
}
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1159
|
+
const hashBytes = hexToBytes(payload.userOpHash);
|
|
1160
|
+
const allowCredentials = payload.passkeyCredentialId ? [{
|
|
1161
|
+
type: "public-key",
|
|
1162
|
+
id: base64ToBytes(payload.passkeyCredentialId)
|
|
1163
|
+
}] : void 0;
|
|
1173
1164
|
const assertion = await navigator.credentials.get({
|
|
1174
1165
|
publicKey: {
|
|
1175
1166
|
challenge: hashBytes,
|
|
1176
|
-
rpId:
|
|
1167
|
+
rpId: resolvePasskeyRpId(),
|
|
1168
|
+
allowCredentials,
|
|
1177
1169
|
userVerification: "required",
|
|
1178
1170
|
timeout: 6e4
|
|
1179
1171
|
}
|
|
@@ -1182,32 +1174,20 @@ function useTransferSigning(pollIntervalMs = 2e3) {
|
|
|
1182
1174
|
throw new Error("Passkey authentication was cancelled.");
|
|
1183
1175
|
}
|
|
1184
1176
|
const response = assertion.response;
|
|
1185
|
-
const signature = btoa(
|
|
1186
|
-
String.fromCharCode(...new Uint8Array(response.signature))
|
|
1187
|
-
);
|
|
1188
|
-
const authenticatorData = btoa(
|
|
1189
|
-
String.fromCharCode(
|
|
1190
|
-
...new Uint8Array(response.authenticatorData)
|
|
1191
|
-
)
|
|
1192
|
-
);
|
|
1193
|
-
const clientDataJSON = btoa(
|
|
1194
|
-
String.fromCharCode(
|
|
1195
|
-
...new Uint8Array(response.clientDataJSON)
|
|
1196
|
-
)
|
|
1197
|
-
);
|
|
1198
1177
|
const signedUserOp = {
|
|
1199
1178
|
...payload.userOp,
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1179
|
+
credentialId: toBase64(assertion.rawId),
|
|
1180
|
+
signature: toBase64(response.signature),
|
|
1181
|
+
authenticatorData: toBase64(response.authenticatorData),
|
|
1182
|
+
clientDataJSON: toBase64(response.clientDataJSON)
|
|
1203
1183
|
};
|
|
1204
|
-
|
|
1184
|
+
return await signTransfer(
|
|
1205
1185
|
apiBaseUrl,
|
|
1206
|
-
token,
|
|
1186
|
+
token ?? "",
|
|
1207
1187
|
transferId,
|
|
1208
|
-
signedUserOp
|
|
1188
|
+
signedUserOp,
|
|
1189
|
+
authorizationSessionToken
|
|
1209
1190
|
);
|
|
1210
|
-
return updatedTransfer;
|
|
1211
1191
|
} catch (err) {
|
|
1212
1192
|
const msg = err instanceof Error ? err.message : "Failed to sign transfer";
|
|
1213
1193
|
setError(msg);
|
|
@@ -1216,7 +1196,7 @@ function useTransferSigning(pollIntervalMs = 2e3) {
|
|
|
1216
1196
|
setSigning(false);
|
|
1217
1197
|
}
|
|
1218
1198
|
},
|
|
1219
|
-
[apiBaseUrl, getAccessToken, pollIntervalMs]
|
|
1199
|
+
[apiBaseUrl, getAccessToken, pollIntervalMs, authorizationSessionToken]
|
|
1220
1200
|
);
|
|
1221
1201
|
return { signing, signPayload, error, signTransfer: signTransfer2 };
|
|
1222
1202
|
}
|
|
@@ -1992,6 +1972,37 @@ function computeSmartDefaults(accts, transferAmount) {
|
|
|
1992
1972
|
}
|
|
1993
1973
|
return { accountId: accts[0].id, walletId: null };
|
|
1994
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
|
+
}
|
|
1995
2006
|
function SwypePayment({
|
|
1996
2007
|
destination,
|
|
1997
2008
|
onComplete,
|
|
@@ -2018,8 +2029,12 @@ function SwypePayment({
|
|
|
2018
2029
|
});
|
|
2019
2030
|
const [transfer, setTransfer] = useState(null);
|
|
2020
2031
|
const [creatingTransfer, setCreatingTransfer] = useState(false);
|
|
2032
|
+
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
|
2021
2033
|
const [mobileFlow, setMobileFlow] = useState(false);
|
|
2022
2034
|
const pollingTransferIdRef = useRef(null);
|
|
2035
|
+
const [selectSourceChainName, setSelectSourceChainName] = useState("");
|
|
2036
|
+
const [selectSourceTokenSymbol, setSelectSourceTokenSymbol] = useState("");
|
|
2037
|
+
const initializedSelectSourceActionRef = useRef(null);
|
|
2023
2038
|
const authExecutor = useAuthorizationExecutor();
|
|
2024
2039
|
const polling = useTransferPolling();
|
|
2025
2040
|
const transferSigning = useTransferSigning();
|
|
@@ -2031,14 +2046,36 @@ function SwypePayment({
|
|
|
2031
2046
|
}
|
|
2032
2047
|
}, [depositAmount]);
|
|
2033
2048
|
useEffect(() => {
|
|
2034
|
-
if (ready
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
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
|
+
}
|
|
2039
2072
|
}
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2073
|
+
};
|
|
2074
|
+
checkPasskey();
|
|
2075
|
+
return () => {
|
|
2076
|
+
cancelled = true;
|
|
2077
|
+
};
|
|
2078
|
+
}, [ready, authenticated, step, depositAmount, apiBaseUrl, getAccessToken]);
|
|
2042
2079
|
const loadingDataRef = useRef(false);
|
|
2043
2080
|
useEffect(() => {
|
|
2044
2081
|
if (!authenticated) return;
|
|
@@ -2112,61 +2149,43 @@ function SwypePayment({
|
|
|
2112
2149
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
2113
2150
|
};
|
|
2114
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]);
|
|
2115
2162
|
useEffect(() => {
|
|
2116
|
-
if (!
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
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);
|
|
2136
2183
|
} else {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
const selWallet = selectedWalletId ? accounts.find((a) => a.id === selectedAccountId)?.wallets.find((w) => w.id === selectedWalletId) : null;
|
|
2140
|
-
if (selWallet) {
|
|
2141
|
-
const walletMatch = options.find(
|
|
2142
|
-
(opt) => opt.chainName === selWallet.chain.name
|
|
2143
|
-
);
|
|
2144
|
-
if (walletMatch) {
|
|
2145
|
-
authExecutor.resolveSelectSource({
|
|
2146
|
-
chainName: walletMatch.chainName,
|
|
2147
|
-
tokenSymbol: walletMatch.tokenSymbol
|
|
2148
|
-
});
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
if (recommended) {
|
|
2153
|
-
authExecutor.resolveSelectSource({
|
|
2154
|
-
chainName: recommended.chainName,
|
|
2155
|
-
tokenSymbol: recommended.tokenSymbol
|
|
2156
|
-
});
|
|
2157
|
-
} else if (options.length > 0) {
|
|
2158
|
-
authExecutor.resolveSelectSource({
|
|
2159
|
-
chainName: options[0].chainName,
|
|
2160
|
-
tokenSymbol: options[0].tokenSymbol
|
|
2161
|
-
});
|
|
2162
|
-
} else {
|
|
2163
|
-
authExecutor.resolveSelectSource({
|
|
2164
|
-
chainName: "Base",
|
|
2165
|
-
tokenSymbol: "USDC"
|
|
2166
|
-
});
|
|
2167
|
-
}
|
|
2184
|
+
setSelectSourceChainName("Base");
|
|
2185
|
+
setSelectSourceTokenSymbol("USDC");
|
|
2168
2186
|
}
|
|
2169
|
-
|
|
2187
|
+
initializedSelectSourceActionRef.current = pendingSelectSourceAction.id;
|
|
2188
|
+
}, [pendingSelectSourceAction, selectSourceChoices, selectSourceRecommended]);
|
|
2170
2189
|
const handlePay = useCallback(async () => {
|
|
2171
2190
|
const parsedAmount = parseFloat(amount);
|
|
2172
2191
|
if (isNaN(parsedAmount) || parsedAmount <= 0) {
|
|
@@ -2329,6 +2348,37 @@ function SwypePayment({
|
|
|
2329
2348
|
]
|
|
2330
2349
|
}
|
|
2331
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
|
+
};
|
|
2332
2382
|
if (!ready) {
|
|
2333
2383
|
return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "24px 0" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Initializing..." }) }) });
|
|
2334
2384
|
}
|
|
@@ -2372,6 +2422,73 @@ function SwypePayment({
|
|
|
2372
2422
|
/* @__PURE__ */ jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
|
|
2373
2423
|
] }) });
|
|
2374
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
|
+
}
|
|
2375
2492
|
if (step === "enter-amount") {
|
|
2376
2493
|
const parsedAmount = parseFloat(amount);
|
|
2377
2494
|
const canContinue = !isNaN(parsedAmount) && parsedAmount > 0;
|
|
@@ -2690,6 +2807,142 @@ function SwypePayment({
|
|
|
2690
2807
|
] });
|
|
2691
2808
|
}
|
|
2692
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
|
+
}
|
|
2693
2946
|
if (transferSigning.signing && transferSigning.signPayload) {
|
|
2694
2947
|
const payload = transferSigning.signPayload;
|
|
2695
2948
|
return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
|
|
@@ -2730,11 +2983,6 @@ function SwypePayment({
|
|
|
2730
2983
|
const currentActionType = authExecutor.currentAction?.type;
|
|
2731
2984
|
const getRegistrationMessage = () => {
|
|
2732
2985
|
switch (currentActionType) {
|
|
2733
|
-
case "REGISTER_PASSKEY":
|
|
2734
|
-
return {
|
|
2735
|
-
label: "Creating your passkey...",
|
|
2736
|
-
description: "Set up a passkey for secure, one-touch payments."
|
|
2737
|
-
};
|
|
2738
2986
|
case "CREATE_SMART_ACCOUNT":
|
|
2739
2987
|
return {
|
|
2740
2988
|
label: "Creating your smart account...",
|
|
@@ -2931,6 +3179,6 @@ function SwypePayment({
|
|
|
2931
3179
|
return null;
|
|
2932
3180
|
}
|
|
2933
3181
|
|
|
2934
|
-
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 };
|
|
2935
3183
|
//# sourceMappingURL=index.js.map
|
|
2936
3184
|
//# sourceMappingURL=index.js.map
|