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