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