@swype-org/react-sdk 0.2.174 → 0.2.185
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/README.md +36 -0
- package/dist/index.cjs +1200 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -20
- package/dist/index.d.ts +130 -20
- package/dist/index.js +1200 -115
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var buffer = require('buffer');
|
|
3
4
|
var react = require('react');
|
|
4
5
|
var reactAuth = require('@privy-io/react-auth');
|
|
5
6
|
var wagmi = require('wagmi');
|
|
@@ -17,6 +18,10 @@ var __export = (target, all) => {
|
|
|
17
18
|
for (var name in all)
|
|
18
19
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
20
|
};
|
|
21
|
+
var g = globalThis;
|
|
22
|
+
if (typeof g.Buffer === "undefined") {
|
|
23
|
+
g.Buffer = buffer.Buffer;
|
|
24
|
+
}
|
|
20
25
|
|
|
21
26
|
// src/theme.ts
|
|
22
27
|
var darkTheme = {
|
|
@@ -1201,7 +1206,7 @@ async function fetchTransfer(apiBaseUrl, token, transferId, authorizationSession
|
|
|
1201
1206
|
if (!res.ok) await throwApiError(res);
|
|
1202
1207
|
return await res.json();
|
|
1203
1208
|
}
|
|
1204
|
-
async function signTransfer(apiBaseUrl, token, transferId,
|
|
1209
|
+
async function signTransfer(apiBaseUrl, token, transferId, signedTransfer, authorizationSessionToken) {
|
|
1205
1210
|
if (!token && !authorizationSessionToken) {
|
|
1206
1211
|
throw new Error("Missing auth credentials for transfer signing.");
|
|
1207
1212
|
}
|
|
@@ -1212,7 +1217,9 @@ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp, authori
|
|
|
1212
1217
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
1213
1218
|
...authorizationSessionToken ? { "x-authorization-session-token": authorizationSessionToken } : {}
|
|
1214
1219
|
},
|
|
1215
|
-
body: JSON.stringify(
|
|
1220
|
+
body: JSON.stringify(
|
|
1221
|
+
signedTransfer.chainFamily === "svm" ? { signedTransfer } : { signedUserOp: signedTransfer }
|
|
1222
|
+
)
|
|
1216
1223
|
});
|
|
1217
1224
|
if (!res.ok) await throwApiError(res);
|
|
1218
1225
|
return await res.json();
|
|
@@ -1326,7 +1333,14 @@ async function probeActionCompletion(apiBaseUrl, actionId) {
|
|
|
1326
1333
|
if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
|
|
1327
1334
|
return { detected: false, reason: "not-found", status: res.status, code, message };
|
|
1328
1335
|
}
|
|
1329
|
-
|
|
1336
|
+
const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
|
|
1337
|
+
"APPROVE_NOT_DETECTED",
|
|
1338
|
+
"APPROVE_SPL_NOT_DETECTED",
|
|
1339
|
+
"SPL_DELEGATE_MISSING",
|
|
1340
|
+
"SPL_DELEGATE_INSUFFICIENT",
|
|
1341
|
+
"SPL_DELEGATE_WRONG_OWNER"
|
|
1342
|
+
]);
|
|
1343
|
+
if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
|
|
1330
1344
|
return { detected: false, reason: "not-found", status: res.status, code, message };
|
|
1331
1345
|
}
|
|
1332
1346
|
if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
|
|
@@ -1354,6 +1368,8 @@ async function pollTransferTick(params) {
|
|
|
1354
1368
|
}
|
|
1355
1369
|
|
|
1356
1370
|
// src/hooks/useTransferPolling.ts
|
|
1371
|
+
var FAST_POLL_COUNT = 8;
|
|
1372
|
+
var FAST_POLL_INTERVAL_MS = 1e3;
|
|
1357
1373
|
function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
1358
1374
|
const { apiBaseUrl } = useBlinkConfig();
|
|
1359
1375
|
const { getAccessToken: privyGetAccessToken } = reactAuth.usePrivy();
|
|
@@ -1361,12 +1377,15 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
|
1361
1377
|
const [transfer, setTransfer] = react.useState(null);
|
|
1362
1378
|
const [error, setError] = react.useState(null);
|
|
1363
1379
|
const [isPolling, setIsPolling] = react.useState(false);
|
|
1364
|
-
const
|
|
1380
|
+
const timeoutRef = react.useRef(null);
|
|
1365
1381
|
const transferIdRef = react.useRef(null);
|
|
1382
|
+
const tickCountRef = react.useRef(0);
|
|
1383
|
+
const runningRef = react.useRef(false);
|
|
1366
1384
|
const stopPolling = react.useCallback(() => {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1385
|
+
runningRef.current = false;
|
|
1386
|
+
if (timeoutRef.current) {
|
|
1387
|
+
clearTimeout(timeoutRef.current);
|
|
1388
|
+
timeoutRef.current = null;
|
|
1370
1389
|
}
|
|
1371
1390
|
setIsPolling(false);
|
|
1372
1391
|
}, []);
|
|
@@ -1391,16 +1410,27 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
|
1391
1410
|
stopPolling();
|
|
1392
1411
|
}
|
|
1393
1412
|
}, [apiBaseUrl, getAccessToken, stopPolling]);
|
|
1413
|
+
const scheduleNext = react.useCallback(() => {
|
|
1414
|
+
if (!runningRef.current) return;
|
|
1415
|
+
const next = tickCountRef.current < FAST_POLL_COUNT ? Math.min(FAST_POLL_INTERVAL_MS, intervalMs) : intervalMs;
|
|
1416
|
+
timeoutRef.current = setTimeout(async () => {
|
|
1417
|
+
tickCountRef.current += 1;
|
|
1418
|
+
await poll();
|
|
1419
|
+
scheduleNext();
|
|
1420
|
+
}, next);
|
|
1421
|
+
}, [intervalMs, poll]);
|
|
1394
1422
|
const startPolling = react.useCallback(
|
|
1395
1423
|
(transferId) => {
|
|
1396
1424
|
stopPolling();
|
|
1397
1425
|
transferIdRef.current = transferId;
|
|
1426
|
+
tickCountRef.current = 0;
|
|
1427
|
+
runningRef.current = true;
|
|
1398
1428
|
setIsPolling(true);
|
|
1399
1429
|
setError(null);
|
|
1400
1430
|
poll();
|
|
1401
|
-
|
|
1431
|
+
scheduleNext();
|
|
1402
1432
|
},
|
|
1403
|
-
[poll,
|
|
1433
|
+
[poll, scheduleNext, stopPolling]
|
|
1404
1434
|
);
|
|
1405
1435
|
react.useEffect(() => () => stopPolling(), [stopPolling]);
|
|
1406
1436
|
return { transfer, error, isPolling, startPolling, stopPolling };
|
|
@@ -1653,7 +1683,7 @@ async function resolvePermit2BatchMetadata(options) {
|
|
|
1653
1683
|
approveAction = null,
|
|
1654
1684
|
signAction,
|
|
1655
1685
|
fetchAuthorizationSession: fetchAuthorizationSession2 = fetchAuthorizationSession,
|
|
1656
|
-
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1686
|
+
sleep: sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1657
1687
|
logPrefix = "[blink-sdk][permit2-batch]",
|
|
1658
1688
|
waitForAwaitingLimit = false
|
|
1659
1689
|
} = options;
|
|
@@ -1693,7 +1723,7 @@ async function resolvePermit2BatchMetadata(options) {
|
|
|
1693
1723
|
`SIGN_PERMIT2 metadata is incomplete for batch execution. Missing: ${missingFields.join(", ")}.`
|
|
1694
1724
|
);
|
|
1695
1725
|
}
|
|
1696
|
-
await
|
|
1726
|
+
await sleep2(BATCH_SIGN_PERMIT2_POLL_MS);
|
|
1697
1727
|
const refreshedSession = await fetchAuthorizationSession2(apiBaseUrl, sessionId);
|
|
1698
1728
|
const refreshedSignAction = refreshedSession.actions.find((action) => action.id === signAction.id);
|
|
1699
1729
|
if (!refreshedSignAction) {
|
|
@@ -1747,7 +1777,7 @@ function isTxSettlementTimeoutError(err) {
|
|
|
1747
1777
|
async function waitForTransactionReceipt(walletClient, txHash, logContext, options = {}) {
|
|
1748
1778
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
|
|
1749
1779
|
const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
|
|
1750
|
-
const
|
|
1780
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1751
1781
|
const info = options.logger?.info ?? ((message) => console.info(message));
|
|
1752
1782
|
const warn = options.logger?.warn ?? ((message) => console.warn(message));
|
|
1753
1783
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
@@ -1767,7 +1797,7 @@ async function waitForTransactionReceipt(walletClient, txHash, logContext, optio
|
|
|
1767
1797
|
`[blink-sdk][tx-receipt] eth_getTransactionReceipt errored; will retry. context=${logContext}, txHash=${txHash}, error=${err instanceof Error ? err.message : String(err)}`
|
|
1768
1798
|
);
|
|
1769
1799
|
}
|
|
1770
|
-
await
|
|
1800
|
+
await sleep2(pollIntervalMs);
|
|
1771
1801
|
}
|
|
1772
1802
|
const waitedMs = pollIntervalMs * maxAttempts;
|
|
1773
1803
|
if (options.strict) {
|
|
@@ -1810,7 +1840,7 @@ function parseNonce(raw) {
|
|
|
1810
1840
|
async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logContext, options = {}) {
|
|
1811
1841
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
|
|
1812
1842
|
const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
|
|
1813
|
-
const
|
|
1843
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1814
1844
|
const info = options.logger?.info ?? ((message) => console.info(message));
|
|
1815
1845
|
const warn = options.logger?.warn ?? ((message) => console.warn(message));
|
|
1816
1846
|
let lastObservedNonce = null;
|
|
@@ -1829,7 +1859,7 @@ async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logCo
|
|
|
1829
1859
|
`[blink-sdk][tx-nonce] eth_getTransactionCount errored; will retry. context=${logContext}, sender=${sender}, error=${err instanceof Error ? err.message : String(err)}`
|
|
1830
1860
|
);
|
|
1831
1861
|
}
|
|
1832
|
-
await
|
|
1862
|
+
await sleep2(pollIntervalMs);
|
|
1833
1863
|
}
|
|
1834
1864
|
const waitedMs = pollIntervalMs * maxAttempts;
|
|
1835
1865
|
throw new TxSettlementTimeoutError({
|
|
@@ -1852,7 +1882,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1852
1882
|
logContext,
|
|
1853
1883
|
pollIntervalMs,
|
|
1854
1884
|
maxAttempts,
|
|
1855
|
-
sleep,
|
|
1885
|
+
sleep: sleep2,
|
|
1856
1886
|
logger
|
|
1857
1887
|
} = params;
|
|
1858
1888
|
const expectedMinNonce = preSendNonce + 1;
|
|
@@ -1867,7 +1897,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1867
1897
|
await waitForTransactionReceipt(walletClient, txHash, logContext, {
|
|
1868
1898
|
pollIntervalMs: resolvedPollIntervalMs,
|
|
1869
1899
|
maxAttempts: resolvedMaxAttempts,
|
|
1870
|
-
sleep,
|
|
1900
|
+
sleep: sleep2,
|
|
1871
1901
|
logger: sharedLogger,
|
|
1872
1902
|
strict: true
|
|
1873
1903
|
});
|
|
@@ -1889,7 +1919,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1889
1919
|
{
|
|
1890
1920
|
pollIntervalMs: resolvedPollIntervalMs,
|
|
1891
1921
|
maxAttempts: resolvedMaxAttempts,
|
|
1892
|
-
sleep,
|
|
1922
|
+
sleep: sleep2,
|
|
1893
1923
|
logger: sharedLogger
|
|
1894
1924
|
}
|
|
1895
1925
|
);
|
|
@@ -1927,13 +1957,13 @@ async function waitForWalletNonceAgreement(params) {
|
|
|
1927
1957
|
pollIntervalMs,
|
|
1928
1958
|
maxAttempts,
|
|
1929
1959
|
minWaitMs,
|
|
1930
|
-
sleep,
|
|
1960
|
+
sleep: sleep2,
|
|
1931
1961
|
logger
|
|
1932
1962
|
} = params;
|
|
1933
1963
|
const resolvedPollIntervalMs = pollIntervalMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_POLL_INTERVAL_MS;
|
|
1934
1964
|
const resolvedMaxAttempts = maxAttempts ?? DEFAULT_WALLET_NONCE_AGREEMENT_MAX_ATTEMPTS;
|
|
1935
1965
|
const resolvedMinWaitMs = minWaitMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_MIN_WAIT_MS;
|
|
1936
|
-
const resolvedSleep =
|
|
1966
|
+
const resolvedSleep = sleep2 ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1937
1967
|
const info = logger?.info ?? ((message) => console.info(message));
|
|
1938
1968
|
const warn = logger?.warn ?? ((message) => console.warn(message));
|
|
1939
1969
|
const startedAt = Date.now();
|
|
@@ -2019,7 +2049,7 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2019
2049
|
const maxConsecutiveErrors = options.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS;
|
|
2020
2050
|
const errorGracePeriodMs = options.errorGracePeriodMs ?? DEFAULT_ERROR_GRACE_PERIOD_MS;
|
|
2021
2051
|
const logEveryN = Math.max(1, options.logEveryNAttempts ?? DEFAULT_LOG_EVERY_N_ATTEMPTS);
|
|
2022
|
-
const
|
|
2052
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2023
2053
|
const now = options.now ?? (() => Date.now());
|
|
2024
2054
|
const info = options.logger?.info ?? ((m) => console.info(m));
|
|
2025
2055
|
const warn = options.logger?.warn ?? ((m) => console.warn(m));
|
|
@@ -2028,7 +2058,7 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2028
2058
|
let consecutiveErrors = 0;
|
|
2029
2059
|
let lastStatus;
|
|
2030
2060
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2031
|
-
await
|
|
2061
|
+
await sleep2(pollIntervalMs);
|
|
2032
2062
|
let status;
|
|
2033
2063
|
try {
|
|
2034
2064
|
status = await withTimeout(
|
|
@@ -2210,6 +2240,10 @@ function buildTargetMatchers(target) {
|
|
|
2210
2240
|
aliases.add("rabby");
|
|
2211
2241
|
aliases.add("io.rabby");
|
|
2212
2242
|
}
|
|
2243
|
+
if (value.includes("phantom")) {
|
|
2244
|
+
aliases.add("phantom");
|
|
2245
|
+
aliases.add("app.phantom");
|
|
2246
|
+
}
|
|
2213
2247
|
if (value.includes("injected")) {
|
|
2214
2248
|
aliases.add("injected");
|
|
2215
2249
|
}
|
|
@@ -2247,6 +2281,252 @@ function resolveWalletConnector(connectors, target) {
|
|
|
2247
2281
|
return metaMaskConnector ?? connectors[0];
|
|
2248
2282
|
}
|
|
2249
2283
|
|
|
2284
|
+
// src/solanaWalletRuntime.ts
|
|
2285
|
+
function asSigner(adapter) {
|
|
2286
|
+
return adapter;
|
|
2287
|
+
}
|
|
2288
|
+
var PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE = "PHANTOM_SOLANA_CONNECT_TIMEOUT";
|
|
2289
|
+
var APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE = "APPROVE_SPL_CONFIRMATION_TIMEOUT";
|
|
2290
|
+
var APPROVE_SPL_ONCHAIN_FAILURE_PREFIX = "Solana transaction failed:";
|
|
2291
|
+
var DEFAULT_CONFIRM_TIMEOUT_MS = 45e3;
|
|
2292
|
+
var DEFAULT_POLL_INTERVAL_MS2 = 1e3;
|
|
2293
|
+
var DEFAULT_COMMITMENT = "confirmed";
|
|
2294
|
+
var cachedAdapter = null;
|
|
2295
|
+
var cachedProviderKey = null;
|
|
2296
|
+
function providerKey(selection) {
|
|
2297
|
+
const providerName = selection.providerName?.trim();
|
|
2298
|
+
if (!providerName) {
|
|
2299
|
+
throw new Error("Solana wallet metadata is missing providerName.");
|
|
2300
|
+
}
|
|
2301
|
+
return `${selection.providerId ?? ""}:${providerName.toLowerCase()}`;
|
|
2302
|
+
}
|
|
2303
|
+
function assertSupportedProvider(selection) {
|
|
2304
|
+
const name = selection.providerName?.trim().toLowerCase();
|
|
2305
|
+
if (name !== "phantom") {
|
|
2306
|
+
throw new Error(`Unsupported Solana wallet provider: ${selection.providerName ?? "unknown"}.`);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
async function createAdapter(selection) {
|
|
2310
|
+
assertSupportedProvider(selection);
|
|
2311
|
+
const { PhantomWalletAdapter } = await import('@solana/wallet-adapter-phantom');
|
|
2312
|
+
return new PhantomWalletAdapter();
|
|
2313
|
+
}
|
|
2314
|
+
function readPublicKey(adapter) {
|
|
2315
|
+
const publicKey = adapter.publicKey?.toBase58();
|
|
2316
|
+
if (!publicKey) {
|
|
2317
|
+
throw new Error("Solana wallet did not return a public key.");
|
|
2318
|
+
}
|
|
2319
|
+
return publicKey;
|
|
2320
|
+
}
|
|
2321
|
+
async function connectSolanaWallet(selection, options) {
|
|
2322
|
+
const key = providerKey(selection);
|
|
2323
|
+
if (!cachedAdapter || cachedProviderKey !== key) {
|
|
2324
|
+
cachedAdapter = await createAdapter(selection);
|
|
2325
|
+
cachedProviderKey = key;
|
|
2326
|
+
}
|
|
2327
|
+
if (!cachedAdapter.connected) {
|
|
2328
|
+
const adapter = cachedAdapter;
|
|
2329
|
+
const connectPromise = adapter.connect();
|
|
2330
|
+
const timeoutMs = options?.timeoutMs;
|
|
2331
|
+
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
2332
|
+
let timeoutHandle = null;
|
|
2333
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2334
|
+
timeoutHandle = setTimeout(() => {
|
|
2335
|
+
reject(new Error(PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE));
|
|
2336
|
+
}, timeoutMs);
|
|
2337
|
+
});
|
|
2338
|
+
try {
|
|
2339
|
+
await Promise.race([connectPromise, timeoutPromise]);
|
|
2340
|
+
} finally {
|
|
2341
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2342
|
+
}
|
|
2343
|
+
} else {
|
|
2344
|
+
await connectPromise;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return {
|
|
2348
|
+
adapter: cachedAdapter,
|
|
2349
|
+
publicKey: readPublicKey(cachedAdapter)
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
async function signSolanaTransaction(selection, unsignedTxBase64, expectedOwnerPubkey) {
|
|
2353
|
+
const { adapter, publicKey } = await connectSolanaWallet(selection);
|
|
2354
|
+
if (publicKey !== expectedOwnerPubkey) {
|
|
2355
|
+
throw new Error(
|
|
2356
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
const signer = asSigner(adapter);
|
|
2360
|
+
if (typeof signer.signTransaction !== "function") {
|
|
2361
|
+
throw new Error("Selected Solana wallet does not support transaction signing.");
|
|
2362
|
+
}
|
|
2363
|
+
const transaction = await deserializeTransaction(unsignedTxBase64);
|
|
2364
|
+
const signedTransaction = await signer.signTransaction(transaction);
|
|
2365
|
+
return {
|
|
2366
|
+
ownerPubkey: publicKey,
|
|
2367
|
+
signedTxBase64: bytesToBase64(serializeTransaction(signedTransaction))
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
async function supportsSignAllSolanaTransactions(selection) {
|
|
2371
|
+
const { adapter } = await connectSolanaWallet(selection);
|
|
2372
|
+
return typeof asSigner(adapter).signAllTransactions === "function";
|
|
2373
|
+
}
|
|
2374
|
+
async function signAllSolanaTransactions(selection, unsignedTxsBase64, expectedOwnerPubkey) {
|
|
2375
|
+
if (!Array.isArray(unsignedTxsBase64) || unsignedTxsBase64.length === 0) {
|
|
2376
|
+
throw new Error("signAllSolanaTransactions requires at least one unsigned transaction.");
|
|
2377
|
+
}
|
|
2378
|
+
const { adapter, publicKey } = await connectSolanaWallet(selection);
|
|
2379
|
+
if (publicKey !== expectedOwnerPubkey) {
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
const signer = asSigner(adapter);
|
|
2385
|
+
if (typeof signer.signAllTransactions !== "function") {
|
|
2386
|
+
throw new Error("Selected Solana wallet does not support signAllTransactions.");
|
|
2387
|
+
}
|
|
2388
|
+
const transactions = [];
|
|
2389
|
+
for (const txBase64 of unsignedTxsBase64) {
|
|
2390
|
+
transactions.push(await deserializeTransaction(txBase64));
|
|
2391
|
+
}
|
|
2392
|
+
const signed = await signer.signAllTransactions(transactions);
|
|
2393
|
+
if (!Array.isArray(signed) || signed.length !== transactions.length) {
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
`signAllTransactions returned ${Array.isArray(signed) ? signed.length : "non-array"} transactions; expected ${transactions.length}.`
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
return {
|
|
2399
|
+
ownerPubkey: publicKey,
|
|
2400
|
+
signedTxsBase64: signed.map((tx) => bytesToBase64(serializeTransaction(tx)))
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
async function deserializeTransaction(unsignedTxBase64) {
|
|
2404
|
+
const { Transaction, VersionedTransaction } = await import('@solana/web3.js');
|
|
2405
|
+
const bytes = base64ToBytes(unsignedTxBase64);
|
|
2406
|
+
try {
|
|
2407
|
+
return Transaction.from(bytes);
|
|
2408
|
+
} catch {
|
|
2409
|
+
return VersionedTransaction.deserialize(bytes);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
function serializeTransaction(transaction) {
|
|
2413
|
+
if ("version" in transaction) {
|
|
2414
|
+
return transaction.serialize();
|
|
2415
|
+
}
|
|
2416
|
+
return transaction.serialize({
|
|
2417
|
+
requireAllSignatures: false,
|
|
2418
|
+
verifySignatures: false
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
function base64ToBytes(value) {
|
|
2422
|
+
if (typeof globalThis.atob === "function") {
|
|
2423
|
+
const binary = globalThis.atob(value);
|
|
2424
|
+
const bytes = new Uint8Array(binary.length);
|
|
2425
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2426
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2427
|
+
}
|
|
2428
|
+
return bytes;
|
|
2429
|
+
}
|
|
2430
|
+
const bufferCtor = globalThis.Buffer;
|
|
2431
|
+
if (bufferCtor) {
|
|
2432
|
+
return bufferCtor.from(value, "base64");
|
|
2433
|
+
}
|
|
2434
|
+
throw new Error("Base64 decoding is not available in this environment.");
|
|
2435
|
+
}
|
|
2436
|
+
function bytesToBase64(bytes) {
|
|
2437
|
+
if (typeof globalThis.btoa === "function") {
|
|
2438
|
+
let binary = "";
|
|
2439
|
+
for (const byte of bytes) {
|
|
2440
|
+
binary += String.fromCharCode(byte);
|
|
2441
|
+
}
|
|
2442
|
+
return globalThis.btoa(binary);
|
|
2443
|
+
}
|
|
2444
|
+
const bufferCtor = globalThis.Buffer;
|
|
2445
|
+
if (bufferCtor) {
|
|
2446
|
+
return bufferCtor.from(bytes).toString("base64");
|
|
2447
|
+
}
|
|
2448
|
+
throw new Error("Base64 encoding is not available in this environment.");
|
|
2449
|
+
}
|
|
2450
|
+
async function signAndSendApproveSplViaWallet(params) {
|
|
2451
|
+
const rpcUrl = params.rpcUrl?.trim();
|
|
2452
|
+
if (!rpcUrl) {
|
|
2453
|
+
throw new Error("signAndSendApproveSplViaWallet requires an rpcUrl.");
|
|
2454
|
+
}
|
|
2455
|
+
const unsignedTxBase64 = params.unsignedTxBase64?.trim();
|
|
2456
|
+
if (!unsignedTxBase64) {
|
|
2457
|
+
throw new Error("signAndSendApproveSplViaWallet requires unsignedTxBase64.");
|
|
2458
|
+
}
|
|
2459
|
+
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
2460
|
+
const { adapter, publicKey } = await connectSolanaWallet(params.selection);
|
|
2461
|
+
if (publicKey !== params.expectedOwnerPubkey) {
|
|
2462
|
+
throw new Error(
|
|
2463
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${params.expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
if (!adapter.sendTransaction) {
|
|
2467
|
+
throw new Error("Selected Solana wallet does not support sendTransaction.");
|
|
2468
|
+
}
|
|
2469
|
+
const transaction = await deserializeTransaction(unsignedTxBase64);
|
|
2470
|
+
const connection = await defaultConnectionFactory(rpcUrl, commitment);
|
|
2471
|
+
const signature = await adapter.sendTransaction(
|
|
2472
|
+
transaction,
|
|
2473
|
+
connection,
|
|
2474
|
+
{
|
|
2475
|
+
preflightCommitment: commitment,
|
|
2476
|
+
maxRetries: 3
|
|
2477
|
+
}
|
|
2478
|
+
);
|
|
2479
|
+
return { ownerPubkey: publicKey, signature };
|
|
2480
|
+
}
|
|
2481
|
+
async function confirmApproveSplViaPolling(params) {
|
|
2482
|
+
const rpcUrl = params.rpcUrl?.trim();
|
|
2483
|
+
if (!rpcUrl) {
|
|
2484
|
+
throw new Error("confirmApproveSplViaPolling requires an rpcUrl.");
|
|
2485
|
+
}
|
|
2486
|
+
const signature = params.signature?.trim();
|
|
2487
|
+
if (!signature) {
|
|
2488
|
+
throw new Error("confirmApproveSplViaPolling requires a signature.");
|
|
2489
|
+
}
|
|
2490
|
+
const confirmTimeoutMs = params.confirmTimeoutMs ?? DEFAULT_CONFIRM_TIMEOUT_MS;
|
|
2491
|
+
const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
2492
|
+
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
2493
|
+
const connection = await defaultConnectionFactory(rpcUrl, commitment);
|
|
2494
|
+
const slot = await pollForConfirmation({
|
|
2495
|
+
connection,
|
|
2496
|
+
signature,
|
|
2497
|
+
confirmTimeoutMs,
|
|
2498
|
+
pollIntervalMs
|
|
2499
|
+
});
|
|
2500
|
+
return { signature, slot };
|
|
2501
|
+
}
|
|
2502
|
+
async function defaultConnectionFactory(rpcUrl, commitment) {
|
|
2503
|
+
const { Connection } = await import('@solana/web3.js');
|
|
2504
|
+
return new Connection(rpcUrl, commitment);
|
|
2505
|
+
}
|
|
2506
|
+
async function pollForConfirmation(args) {
|
|
2507
|
+
const { connection, signature, confirmTimeoutMs, pollIntervalMs } = args;
|
|
2508
|
+
const expiresAt = Date.now() + confirmTimeoutMs;
|
|
2509
|
+
while (Date.now() <= expiresAt) {
|
|
2510
|
+
const result = await connection.getSignatureStatuses(
|
|
2511
|
+
[signature],
|
|
2512
|
+
{ searchTransactionHistory: true }
|
|
2513
|
+
);
|
|
2514
|
+
const status = result.value[0];
|
|
2515
|
+
if (status?.err) {
|
|
2516
|
+
throw new Error(`${APPROVE_SPL_ONCHAIN_FAILURE_PREFIX} ${JSON.stringify(status.err)}`);
|
|
2517
|
+
}
|
|
2518
|
+
const confirmationStatus = status?.confirmationStatus;
|
|
2519
|
+
if (confirmationStatus === "confirmed" || confirmationStatus === "finalized") {
|
|
2520
|
+
return status?.slot;
|
|
2521
|
+
}
|
|
2522
|
+
await sleep(pollIntervalMs);
|
|
2523
|
+
}
|
|
2524
|
+
throw new Error(APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE);
|
|
2525
|
+
}
|
|
2526
|
+
function sleep(ms) {
|
|
2527
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2250
2530
|
// src/hooks/authorizationExecutor.ts
|
|
2251
2531
|
var WALLET_CLIENT_MAX_ATTEMPTS = 25;
|
|
2252
2532
|
var WALLET_CLIENT_POLL_MS = 400;
|
|
@@ -2258,6 +2538,7 @@ var EXECUTE_BRIDGE_TX_TIMEOUT_MS = 12e4;
|
|
|
2258
2538
|
var EXECUTE_BRIDGE_TX_TIMEOUT_MESSAGE = "EXECUTE_BRIDGE_TX_TIMEOUT";
|
|
2259
2539
|
var WALLET_PROMPT_TIMEOUT_MS = 9e4;
|
|
2260
2540
|
var WALLET_PROMPT_TIMEOUT_MESSAGE = "WALLET_PROMPT_TIMEOUT";
|
|
2541
|
+
var PHANTOM_SOLANA_CONNECT_TIMEOUT_MS = 8e3;
|
|
2261
2542
|
var BATCH_RECEIPT_POLL_INTERVAL_MS = 1500;
|
|
2262
2543
|
var BATCH_RECEIPT_MAX_ATTEMPTS = 20;
|
|
2263
2544
|
var BATCHABLE_ACTION_TYPES = /* @__PURE__ */ new Set(["APPROVE_PERMIT2", "SIGN_PERMIT2", "EXECUTE_BRIDGE"]);
|
|
@@ -2371,13 +2652,110 @@ function parseSignTypedDataPayload(typedData) {
|
|
|
2371
2652
|
};
|
|
2372
2653
|
}
|
|
2373
2654
|
function isBatchableAction(action) {
|
|
2655
|
+
if (action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm") {
|
|
2656
|
+
return false;
|
|
2657
|
+
}
|
|
2374
2658
|
return BATCHABLE_ACTION_TYPES.has(action.type);
|
|
2375
2659
|
}
|
|
2660
|
+
function isSvmCombinedFirstTransferPair(pending) {
|
|
2661
|
+
if (pending.length < 2) return false;
|
|
2662
|
+
const [first, second] = pending;
|
|
2663
|
+
if (first.type !== "APPROVE_SPL") return false;
|
|
2664
|
+
if (second.type !== "EXECUTE_BRIDGE") return false;
|
|
2665
|
+
if (second.metadata?.chainFamily !== "svm") return false;
|
|
2666
|
+
if (first.metadata?.awaitingLimit) return false;
|
|
2667
|
+
const approveTx = first.metadata?.unsignedApproveTxBase64;
|
|
2668
|
+
const bridgeTx = second.metadata?.unsignedTxBase64;
|
|
2669
|
+
if (typeof approveTx !== "string" || approveTx.trim() === "") return false;
|
|
2670
|
+
if (typeof bridgeTx !== "string" || bridgeTx.trim() === "") return false;
|
|
2671
|
+
const approveOwner = first.metadata?.ownerPubkey;
|
|
2672
|
+
const bridgeOwner = second.metadata?.ownerPubkey ?? second.metadata?.senderAddress;
|
|
2673
|
+
if (typeof approveOwner !== "string" || approveOwner.trim() === "") return false;
|
|
2674
|
+
if (typeof bridgeOwner !== "string" || bridgeOwner.trim() === "") return false;
|
|
2675
|
+
if (approveOwner !== bridgeOwner) return false;
|
|
2676
|
+
return true;
|
|
2677
|
+
}
|
|
2678
|
+
function getSolanaWalletSelection(action) {
|
|
2679
|
+
const providerName = action.metadata?.providerName;
|
|
2680
|
+
if (typeof providerName !== "string" || providerName.trim() === "") {
|
|
2681
|
+
throw new Error(`${action.type} metadata is missing providerName.`);
|
|
2682
|
+
}
|
|
2683
|
+
const providerId = action.metadata?.providerId;
|
|
2684
|
+
return {
|
|
2685
|
+
...typeof providerId === "string" && providerId.trim() !== "" ? { providerId } : {},
|
|
2686
|
+
providerName
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
function isPhantomEvmProviderAvailable() {
|
|
2690
|
+
if (typeof window === "undefined") return false;
|
|
2691
|
+
const phantom = window.phantom;
|
|
2692
|
+
return Boolean(phantom?.ethereum?.isPhantom);
|
|
2693
|
+
}
|
|
2694
|
+
async function phantomEvmHasAuthorizedAccount() {
|
|
2695
|
+
if (typeof window === "undefined") return false;
|
|
2696
|
+
const provider = window.phantom?.ethereum;
|
|
2697
|
+
if (!provider?.isPhantom) return false;
|
|
2698
|
+
try {
|
|
2699
|
+
const accounts = await provider.request({ method: "eth_accounts" });
|
|
2700
|
+
return Array.isArray(accounts) && accounts.length > 0;
|
|
2701
|
+
} catch {
|
|
2702
|
+
return false;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
async function attemptPhantomEvmConnect(connectors, connectAsync) {
|
|
2706
|
+
let connector = resolveWalletConnector(connectors, { providerName: "Phantom" });
|
|
2707
|
+
if (!connector) {
|
|
2708
|
+
const ethereum = typeof window !== "undefined" ? window.ethereum : void 0;
|
|
2709
|
+
if (ethereum?.isPhantom) {
|
|
2710
|
+
connector = resolveWalletConnector(connectors, { wagmiConnectorId: "injected" });
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
if (!connector) return null;
|
|
2714
|
+
const result = await connectAsync({ connector });
|
|
2715
|
+
const address = result.accounts[0];
|
|
2716
|
+
if (!address) return null;
|
|
2717
|
+
return { address, chainId: result.chainId };
|
|
2718
|
+
}
|
|
2376
2719
|
function getPendingActions(session, completedIds) {
|
|
2377
2720
|
return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
|
|
2378
2721
|
}
|
|
2379
2722
|
async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
|
|
2380
2723
|
try {
|
|
2724
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
2725
|
+
const selection = getSolanaWalletSelection(action);
|
|
2726
|
+
const evmProbed = isPhantomEvmProviderAvailable() && await phantomEvmHasAuthorizedAccount();
|
|
2727
|
+
const svmPromise = connectSolanaWallet(selection, {
|
|
2728
|
+
timeoutMs: evmProbed ? PHANTOM_SOLANA_CONNECT_TIMEOUT_MS : void 0
|
|
2729
|
+
});
|
|
2730
|
+
const evmPromise = evmProbed ? attemptPhantomEvmConnect(connectors, connectAsync) : Promise.resolve(null);
|
|
2731
|
+
const [svmSettled, evmSettled] = await Promise.allSettled([svmPromise, evmPromise]);
|
|
2732
|
+
const svmPublicKey = svmSettled.status === "fulfilled" ? svmSettled.value.publicKey : null;
|
|
2733
|
+
const evmAddress = evmSettled.status === "fulfilled" && evmSettled.value ? evmSettled.value.address : null;
|
|
2734
|
+
if (!svmPublicKey && !evmAddress) {
|
|
2735
|
+
const svmReason = svmSettled.status === "rejected" ? svmSettled.reason instanceof Error ? svmSettled.reason.message : String(svmSettled.reason) : "Failed to connect Phantom.";
|
|
2736
|
+
const friendlyMsg = svmReason === PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE ? "Phantom did not respond. Please try again." : svmReason;
|
|
2737
|
+
return actionError(
|
|
2738
|
+
action,
|
|
2739
|
+
isUserRejection(friendlyMsg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : friendlyMsg
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
if (evmAddress) {
|
|
2743
|
+
await refreshWalletCapabilities(wagmiConfig2, "open-provider:phantom-dual");
|
|
2744
|
+
}
|
|
2745
|
+
const messageParts = [];
|
|
2746
|
+
if (svmPublicKey) messageParts.push(`SVM: ${svmPublicKey}`);
|
|
2747
|
+
if (evmAddress) messageParts.push(`EVM: ${evmAddress}`);
|
|
2748
|
+
return actionSuccess(
|
|
2749
|
+
action,
|
|
2750
|
+
`Connected to ${selection.providerName}. ${messageParts.join(", ")}`,
|
|
2751
|
+
{
|
|
2752
|
+
accounts: svmPublicKey ? [svmPublicKey, ...evmAddress ? [evmAddress] : []] : evmAddress ? [evmAddress] : [],
|
|
2753
|
+
chainFamily: "svm",
|
|
2754
|
+
...svmPublicKey ? { userSolanaWalletPubkey: svmPublicKey } : {},
|
|
2755
|
+
...evmAddress ? { evmAddress } : {}
|
|
2756
|
+
}
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2381
2759
|
const account = core.getAccount(wagmiConfig2);
|
|
2382
2760
|
const targetId = action.metadata?.wagmiConnectorId;
|
|
2383
2761
|
const connector = resolveWalletConnector(connectors, { wagmiConnectorId: targetId });
|
|
@@ -2503,9 +2881,16 @@ async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsyn
|
|
|
2503
2881
|
{ accounts: [...result.accounts], chainId: hexChainId }
|
|
2504
2882
|
);
|
|
2505
2883
|
} catch (err) {
|
|
2884
|
+
const msg = err instanceof Error ? err.message : "Failed to connect wallet";
|
|
2885
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
2886
|
+
return actionError(
|
|
2887
|
+
action,
|
|
2888
|
+
isUserRejection(msg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : msg
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2506
2891
|
return actionError(
|
|
2507
2892
|
action,
|
|
2508
|
-
|
|
2893
|
+
msg
|
|
2509
2894
|
);
|
|
2510
2895
|
}
|
|
2511
2896
|
}
|
|
@@ -2583,6 +2968,334 @@ async function executeSwitchChain(action, wagmiConfig2, switchChainAsync) {
|
|
|
2583
2968
|
);
|
|
2584
2969
|
}
|
|
2585
2970
|
}
|
|
2971
|
+
async function executeApproveSplSolana(action, options) {
|
|
2972
|
+
try {
|
|
2973
|
+
const selection = getSolanaWalletSelection(action);
|
|
2974
|
+
const ownerPubkey = action.metadata?.ownerPubkey;
|
|
2975
|
+
const unsignedApproveTxBase64 = action.metadata?.unsignedApproveTxBase64;
|
|
2976
|
+
const tokenSymbol = action.metadata?.tokenSymbol;
|
|
2977
|
+
const clientRpcUrl = action.metadata?.clientRpcUrl;
|
|
2978
|
+
if (action.metadata?.awaitingLimit) {
|
|
2979
|
+
return actionError(
|
|
2980
|
+
action,
|
|
2981
|
+
"APPROVE_SPL action is still waiting for a One-Tap allowance amount."
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
2985
|
+
return actionError(action, "APPROVE_SPL metadata is missing ownerPubkey.");
|
|
2986
|
+
}
|
|
2987
|
+
if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
|
|
2988
|
+
return actionError(
|
|
2989
|
+
action,
|
|
2990
|
+
"APPROVE_SPL metadata is missing unsignedApproveTxBase64."
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
appendDebug("info", "APPROVE_SPL: entry", {
|
|
2994
|
+
actionId: action.id,
|
|
2995
|
+
ownerPubkey,
|
|
2996
|
+
tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
|
|
2997
|
+
});
|
|
2998
|
+
if (typeof clientRpcUrl === "string" && clientRpcUrl.trim() !== "") {
|
|
2999
|
+
const sendLabel = "APPROVE_SPL signAndSendApproveSplViaWallet";
|
|
3000
|
+
const sent = await withWatchdog(
|
|
3001
|
+
withTimeout(
|
|
3002
|
+
signAndSendApproveSplViaWallet({
|
|
3003
|
+
selection,
|
|
3004
|
+
unsignedTxBase64: unsignedApproveTxBase64,
|
|
3005
|
+
expectedOwnerPubkey: ownerPubkey,
|
|
3006
|
+
rpcUrl: clientRpcUrl
|
|
3007
|
+
}),
|
|
3008
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3009
|
+
sendLabel
|
|
3010
|
+
),
|
|
3011
|
+
sendLabel
|
|
3012
|
+
);
|
|
3013
|
+
appendDebug("info", "APPROVE_SPL: tx submitted via wallet", {
|
|
3014
|
+
actionId: action.id,
|
|
3015
|
+
signature: sent.signature
|
|
3016
|
+
});
|
|
3017
|
+
try {
|
|
3018
|
+
options?.onConfirming?.(action);
|
|
3019
|
+
} catch (callbackErr) {
|
|
3020
|
+
appendDebug("warn", "APPROVE_SPL: onConfirming callback threw", {
|
|
3021
|
+
actionId: action.id,
|
|
3022
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
const confirmLabel = "APPROVE_SPL confirmApproveSplViaPolling";
|
|
3026
|
+
let slot;
|
|
3027
|
+
try {
|
|
3028
|
+
const confirmation = await withWatchdog(
|
|
3029
|
+
confirmApproveSplViaPolling({
|
|
3030
|
+
rpcUrl: clientRpcUrl,
|
|
3031
|
+
signature: sent.signature
|
|
3032
|
+
}),
|
|
3033
|
+
confirmLabel
|
|
3034
|
+
);
|
|
3035
|
+
slot = confirmation.slot;
|
|
3036
|
+
appendDebug("info", "APPROVE_SPL: settled", {
|
|
3037
|
+
actionId: action.id,
|
|
3038
|
+
signature: sent.signature,
|
|
3039
|
+
slot
|
|
3040
|
+
});
|
|
3041
|
+
} catch (pollErr) {
|
|
3042
|
+
const pollMsg = pollErr instanceof Error ? pollErr.message : String(pollErr);
|
|
3043
|
+
if (pollMsg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE) {
|
|
3044
|
+
throw pollErr;
|
|
3045
|
+
}
|
|
3046
|
+
if (pollMsg.startsWith(APPROVE_SPL_ONCHAIN_FAILURE_PREFIX)) {
|
|
3047
|
+
throw pollErr;
|
|
3048
|
+
}
|
|
3049
|
+
appendDebug("warn", "APPROVE_SPL: confirmation poll failed; relying on server verification", {
|
|
3050
|
+
actionId: action.id,
|
|
3051
|
+
signature: sent.signature,
|
|
3052
|
+
error: pollMsg
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
return actionSuccess(
|
|
3056
|
+
action,
|
|
3057
|
+
`Confirmed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3058
|
+
{
|
|
3059
|
+
approveSignature: sent.signature
|
|
3060
|
+
}
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
const signLabel = "APPROVE_SPL signSolanaTransaction";
|
|
3064
|
+
const signed = await withWatchdog(
|
|
3065
|
+
withTimeout(
|
|
3066
|
+
signSolanaTransaction(
|
|
3067
|
+
selection,
|
|
3068
|
+
unsignedApproveTxBase64,
|
|
3069
|
+
ownerPubkey
|
|
3070
|
+
),
|
|
3071
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3072
|
+
signLabel
|
|
3073
|
+
),
|
|
3074
|
+
signLabel
|
|
3075
|
+
);
|
|
3076
|
+
appendDebug("info", "APPROVE_SPL: signed (legacy path)", {
|
|
3077
|
+
actionId: action.id
|
|
3078
|
+
});
|
|
3079
|
+
return actionSuccess(
|
|
3080
|
+
action,
|
|
3081
|
+
`Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3082
|
+
{ signedTxBase64: signed.signedTxBase64 }
|
|
3083
|
+
);
|
|
3084
|
+
} catch (err) {
|
|
3085
|
+
const msg = err instanceof Error ? err.message : "Failed to sign SPL approval";
|
|
3086
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3087
|
+
const confirmTimedOut = msg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE;
|
|
3088
|
+
appendDebug(timedOut || confirmTimedOut ? "warn" : "error", "APPROVE_SPL: threw", {
|
|
3089
|
+
actionId: action.id,
|
|
3090
|
+
error: msg,
|
|
3091
|
+
userRejected: isUserRejection(msg),
|
|
3092
|
+
timedOut,
|
|
3093
|
+
confirmTimedOut
|
|
3094
|
+
});
|
|
3095
|
+
if (timedOut) {
|
|
3096
|
+
return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
|
|
3097
|
+
}
|
|
3098
|
+
if (confirmTimedOut) {
|
|
3099
|
+
return actionError(
|
|
3100
|
+
action,
|
|
3101
|
+
"Your SPL approval was submitted but did not confirm in time. Please wait a moment and retry \u2014 do not re-approve in Phantom yet, as that would create a duplicate transaction."
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
return actionError(
|
|
3105
|
+
action,
|
|
3106
|
+
isUserRejection(msg) ? "You rejected the SPL approval transaction. Please approve the transaction in Phantom to continue." : msg
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
async function executeExecuteBridgeSolana(action) {
|
|
3111
|
+
try {
|
|
3112
|
+
const selection = getSolanaWalletSelection(action);
|
|
3113
|
+
const ownerPubkey = action.metadata?.ownerPubkey ?? action.metadata?.senderAddress;
|
|
3114
|
+
const unsignedTxBase64 = action.metadata?.unsignedTxBase64;
|
|
3115
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
3116
|
+
return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing ownerPubkey.");
|
|
3117
|
+
}
|
|
3118
|
+
if (typeof unsignedTxBase64 !== "string" || unsignedTxBase64.trim() === "") {
|
|
3119
|
+
return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64.");
|
|
3120
|
+
}
|
|
3121
|
+
appendDebug("info", "EXECUTE_BRIDGE: svm entry", {
|
|
3122
|
+
actionId: action.id,
|
|
3123
|
+
ownerPubkey
|
|
3124
|
+
});
|
|
3125
|
+
const label = "EXECUTE_BRIDGE signSolanaTransaction";
|
|
3126
|
+
const signed = await withWatchdog(
|
|
3127
|
+
withTimeout(
|
|
3128
|
+
signSolanaTransaction(
|
|
3129
|
+
selection,
|
|
3130
|
+
unsignedTxBase64,
|
|
3131
|
+
ownerPubkey
|
|
3132
|
+
),
|
|
3133
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3134
|
+
label
|
|
3135
|
+
),
|
|
3136
|
+
label
|
|
3137
|
+
);
|
|
3138
|
+
appendDebug("info", "EXECUTE_BRIDGE: svm signed", {
|
|
3139
|
+
actionId: action.id
|
|
3140
|
+
});
|
|
3141
|
+
return actionSuccess(
|
|
3142
|
+
action,
|
|
3143
|
+
"Signed Solana bridge transfer in Phantom.",
|
|
3144
|
+
{ signedTxBase64: signed.signedTxBase64, ownerPubkey: signed.ownerPubkey }
|
|
3145
|
+
);
|
|
3146
|
+
} catch (err) {
|
|
3147
|
+
const msg = err instanceof Error ? err.message : "Failed to sign Solana bridge transfer";
|
|
3148
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3149
|
+
appendDebug(timedOut ? "warn" : "error", "EXECUTE_BRIDGE: svm threw", {
|
|
3150
|
+
actionId: action.id,
|
|
3151
|
+
error: msg,
|
|
3152
|
+
userRejected: isUserRejection(msg),
|
|
3153
|
+
timedOut
|
|
3154
|
+
});
|
|
3155
|
+
if (timedOut) {
|
|
3156
|
+
return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
|
|
3157
|
+
}
|
|
3158
|
+
return actionError(
|
|
3159
|
+
action,
|
|
3160
|
+
isUserRejection(msg) ? "You rejected the Solana transfer transaction. Please approve the transaction in Phantom to continue." : msg
|
|
3161
|
+
);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
async function executeSvmCombinedFirstTransfer(actions, options) {
|
|
3165
|
+
if (actions.length !== 2) {
|
|
3166
|
+
throw new Error(
|
|
3167
|
+
`executeSvmCombinedFirstTransfer requires exactly 2 actions, received ${actions.length}.`
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
const approveAction = actions[0].action;
|
|
3171
|
+
const bridgeAction = actions[1].action;
|
|
3172
|
+
if (approveAction.type !== "APPROVE_SPL") {
|
|
3173
|
+
throw new Error(
|
|
3174
|
+
`executeSvmCombinedFirstTransfer expects APPROVE_SPL first, got ${approveAction.type}.`
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
if (bridgeAction.type !== "EXECUTE_BRIDGE" || bridgeAction.metadata?.chainFamily !== "svm") {
|
|
3178
|
+
throw new Error(
|
|
3179
|
+
`executeSvmCombinedFirstTransfer expects SVM EXECUTE_BRIDGE second, got ${bridgeAction.type}/${bridgeAction.metadata?.chainFamily ?? "unknown"}.`
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
const ownerPubkey = approveAction.metadata?.ownerPubkey;
|
|
3183
|
+
const unsignedApproveTxBase64 = approveAction.metadata?.unsignedApproveTxBase64;
|
|
3184
|
+
const tokenSymbol = approveAction.metadata?.tokenSymbol;
|
|
3185
|
+
const unsignedBridgeTxBase64 = bridgeAction.metadata?.unsignedTxBase64;
|
|
3186
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
3187
|
+
return failBothActions(
|
|
3188
|
+
approveAction,
|
|
3189
|
+
bridgeAction,
|
|
3190
|
+
"APPROVE_SPL metadata is missing ownerPubkey."
|
|
3191
|
+
);
|
|
3192
|
+
}
|
|
3193
|
+
if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
|
|
3194
|
+
return failBothActions(
|
|
3195
|
+
approveAction,
|
|
3196
|
+
bridgeAction,
|
|
3197
|
+
"APPROVE_SPL metadata is missing unsignedApproveTxBase64."
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
if (typeof unsignedBridgeTxBase64 !== "string" || unsignedBridgeTxBase64.trim() === "") {
|
|
3201
|
+
return failBothActions(
|
|
3202
|
+
approveAction,
|
|
3203
|
+
bridgeAction,
|
|
3204
|
+
"SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64."
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3207
|
+
const selection = getSolanaWalletSelection(approveAction);
|
|
3208
|
+
appendDebug("info", "SVM_COMBINED: entry", {
|
|
3209
|
+
approveActionId: approveAction.id,
|
|
3210
|
+
bridgeActionId: bridgeAction.id,
|
|
3211
|
+
ownerPubkey,
|
|
3212
|
+
tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
|
|
3213
|
+
});
|
|
3214
|
+
const signLabel = "SVM_COMBINED signAllSolanaTransactions";
|
|
3215
|
+
let signed;
|
|
3216
|
+
try {
|
|
3217
|
+
signed = await withWatchdog(
|
|
3218
|
+
withTimeout(
|
|
3219
|
+
signAllSolanaTransactions(
|
|
3220
|
+
selection,
|
|
3221
|
+
[unsignedApproveTxBase64, unsignedBridgeTxBase64],
|
|
3222
|
+
ownerPubkey
|
|
3223
|
+
),
|
|
3224
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3225
|
+
signLabel
|
|
3226
|
+
),
|
|
3227
|
+
signLabel
|
|
3228
|
+
);
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
const msg = err instanceof Error ? err.message : "Failed to sign Solana transactions";
|
|
3231
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3232
|
+
appendDebug(timedOut ? "warn" : "error", "SVM_COMBINED: signAllTransactions threw", {
|
|
3233
|
+
approveActionId: approveAction.id,
|
|
3234
|
+
bridgeActionId: bridgeAction.id,
|
|
3235
|
+
error: msg,
|
|
3236
|
+
userRejected: isUserRejection(msg),
|
|
3237
|
+
timedOut
|
|
3238
|
+
});
|
|
3239
|
+
if (timedOut) {
|
|
3240
|
+
return failBothActions(
|
|
3241
|
+
approveAction,
|
|
3242
|
+
bridgeAction,
|
|
3243
|
+
WALLET_PROMPT_TIMEOUT_MESSAGE
|
|
3244
|
+
);
|
|
3245
|
+
}
|
|
3246
|
+
if (isUserRejection(msg)) {
|
|
3247
|
+
return failBothActions(
|
|
3248
|
+
approveAction,
|
|
3249
|
+
bridgeAction,
|
|
3250
|
+
"You rejected the Solana transactions. Please approve the prompt in Phantom to continue."
|
|
3251
|
+
);
|
|
3252
|
+
}
|
|
3253
|
+
return failBothActions(approveAction, bridgeAction, msg);
|
|
3254
|
+
}
|
|
3255
|
+
const [approveSignedB64, bridgeSignedB64] = signed.signedTxsBase64;
|
|
3256
|
+
appendDebug("info", "SVM_COMBINED: signAllTransactions returned", {
|
|
3257
|
+
approveActionId: approveAction.id,
|
|
3258
|
+
bridgeActionId: bridgeAction.id
|
|
3259
|
+
});
|
|
3260
|
+
try {
|
|
3261
|
+
options?.onApproveSplConfirming?.(approveAction);
|
|
3262
|
+
} catch (callbackErr) {
|
|
3263
|
+
appendDebug("warn", "SVM_COMBINED: onApproveSplConfirming callback threw", {
|
|
3264
|
+
approveActionId: approveAction.id,
|
|
3265
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
return {
|
|
3269
|
+
txHash: void 0,
|
|
3270
|
+
actionResults: [
|
|
3271
|
+
{
|
|
3272
|
+
action: approveAction,
|
|
3273
|
+
result: actionSuccess(
|
|
3274
|
+
approveAction,
|
|
3275
|
+
`Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3276
|
+
{ signedTxBase64: approveSignedB64, ownerPubkey: signed.ownerPubkey }
|
|
3277
|
+
)
|
|
3278
|
+
},
|
|
3279
|
+
{
|
|
3280
|
+
action: bridgeAction,
|
|
3281
|
+
result: actionSuccess(
|
|
3282
|
+
bridgeAction,
|
|
3283
|
+
"Signed Solana bridge transfer in Phantom.",
|
|
3284
|
+
{ signedTxBase64: bridgeSignedB64, ownerPubkey: signed.ownerPubkey }
|
|
3285
|
+
)
|
|
3286
|
+
}
|
|
3287
|
+
]
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
function failBothActions(approveAction, bridgeAction, message) {
|
|
3291
|
+
return {
|
|
3292
|
+
txHash: void 0,
|
|
3293
|
+
actionResults: [
|
|
3294
|
+
{ action: approveAction, result: actionError(approveAction, message) },
|
|
3295
|
+
{ action: bridgeAction, result: actionError(bridgeAction, message) }
|
|
3296
|
+
]
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
2586
3299
|
async function executeApprovePermit2(action, wagmiConfig2) {
|
|
2587
3300
|
let walletClient = null;
|
|
2588
3301
|
let sender = null;
|
|
@@ -3228,6 +3941,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3228
3941
|
const [results, setResults] = react.useState([]);
|
|
3229
3942
|
const [error, setError] = react.useState(null);
|
|
3230
3943
|
const [currentAction, setCurrentAction] = react.useState(null);
|
|
3944
|
+
const [approveSplConfirming, setApproveSplConfirming] = react.useState(null);
|
|
3231
3945
|
const executingRef = react.useRef(false);
|
|
3232
3946
|
const walletCapabilitiesRef = react.useRef({});
|
|
3233
3947
|
const walletCapabilitiesRefreshedRef = react.useRef(false);
|
|
@@ -3266,16 +3980,17 @@ function useAuthorizationExecutor(options) {
|
|
|
3266
3980
|
}
|
|
3267
3981
|
setError(null);
|
|
3268
3982
|
setCurrentAction(null);
|
|
3983
|
+
setApproveSplConfirming(null);
|
|
3269
3984
|
setExecuting(false);
|
|
3270
3985
|
executingRef.current = false;
|
|
3271
3986
|
}, []);
|
|
3272
3987
|
const dispatchAction = react.useCallback(
|
|
3273
|
-
async (action) => {
|
|
3988
|
+
async (action, dispatchOptions) => {
|
|
3274
3989
|
setCurrentAction(action);
|
|
3275
3990
|
switch (action.type) {
|
|
3276
3991
|
case "OPEN_PROVIDER": {
|
|
3277
3992
|
const result = await executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
|
|
3278
|
-
if (result.status === "success") {
|
|
3993
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3279
3994
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3280
3995
|
wagmiConfig2,
|
|
3281
3996
|
"open-provider"
|
|
@@ -3287,7 +4002,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3287
4002
|
return executeSelectSource(action, waitForSelection);
|
|
3288
4003
|
case "SWITCH_CHAIN": {
|
|
3289
4004
|
const result = await executeSwitchChain(action, wagmiConfig2, switchChainAsync);
|
|
3290
|
-
if (result.status === "success") {
|
|
4005
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3291
4006
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3292
4007
|
wagmiConfig2,
|
|
3293
4008
|
"switch-chain"
|
|
@@ -3307,7 +4022,33 @@ function useAuthorizationExecutor(options) {
|
|
|
3307
4022
|
}
|
|
3308
4023
|
return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
|
|
3309
4024
|
}
|
|
4025
|
+
case "APPROVE_SPL": {
|
|
4026
|
+
if (action.metadata?.awaitingLimit) {
|
|
4027
|
+
throw new Error(
|
|
4028
|
+
"APPROVE_SPL action has awaitingLimit. The orchestrator must handle one-tap setup before executing this action."
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
const externalConfirmingCallback = dispatchOptions?.onApproveSplConfirming;
|
|
4032
|
+
return executeApproveSplSolana(action, {
|
|
4033
|
+
onConfirming: (a) => {
|
|
4034
|
+
setApproveSplConfirming(a);
|
|
4035
|
+
if (externalConfirmingCallback) {
|
|
4036
|
+
try {
|
|
4037
|
+
externalConfirmingCallback(a);
|
|
4038
|
+
} catch (callbackErr) {
|
|
4039
|
+
appendDebug("warn", "APPROVE_SPL: orchestrator onApproveSplConfirming threw", {
|
|
4040
|
+
actionId: a.id,
|
|
4041
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
3310
4048
|
case "EXECUTE_BRIDGE":
|
|
4049
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
4050
|
+
return executeExecuteBridgeSolana(action);
|
|
4051
|
+
}
|
|
3311
4052
|
return executeExecuteBridge(action, wagmiConfig2);
|
|
3312
4053
|
default:
|
|
3313
4054
|
return actionError(action, `Unsupported action type: ${action.type}`);
|
|
@@ -3320,7 +4061,11 @@ function useAuthorizationExecutor(options) {
|
|
|
3320
4061
|
if (options2?.sessionId) {
|
|
3321
4062
|
sessionIdRef.current = options2.sessionId;
|
|
3322
4063
|
}
|
|
3323
|
-
|
|
4064
|
+
try {
|
|
4065
|
+
return await dispatchAction(action, options2);
|
|
4066
|
+
} finally {
|
|
4067
|
+
setApproveSplConfirming(null);
|
|
4068
|
+
}
|
|
3324
4069
|
},
|
|
3325
4070
|
[dispatchAction]
|
|
3326
4071
|
);
|
|
@@ -3341,6 +4086,43 @@ function useAuthorizationExecutor(options) {
|
|
|
3341
4086
|
},
|
|
3342
4087
|
[apiBaseUrl, wagmiConfig2]
|
|
3343
4088
|
);
|
|
4089
|
+
const executeSvmCombinedFirstTransferImpl = react.useCallback(
|
|
4090
|
+
async (actions, options2) => {
|
|
4091
|
+
setCurrentAction(actions[0]?.action ?? null);
|
|
4092
|
+
const externalConfirmingCallback = options2?.onApproveSplConfirming;
|
|
4093
|
+
return executeSvmCombinedFirstTransfer(actions, {
|
|
4094
|
+
onApproveSplConfirming: (a) => {
|
|
4095
|
+
setApproveSplConfirming(a);
|
|
4096
|
+
if (externalConfirmingCallback) {
|
|
4097
|
+
try {
|
|
4098
|
+
externalConfirmingCallback(a);
|
|
4099
|
+
} catch (callbackErr) {
|
|
4100
|
+
appendDebug("warn", "SVM_COMBINED: orchestrator onApproveSplConfirming threw", {
|
|
4101
|
+
actionId: a.id,
|
|
4102
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
});
|
|
4108
|
+
},
|
|
4109
|
+
[]
|
|
4110
|
+
);
|
|
4111
|
+
const canSignAllSolanaTransactions = react.useCallback(
|
|
4112
|
+
async (action) => {
|
|
4113
|
+
try {
|
|
4114
|
+
const selection = getSolanaWalletSelection(action);
|
|
4115
|
+
return await supportsSignAllSolanaTransactions(selection);
|
|
4116
|
+
} catch (err) {
|
|
4117
|
+
appendDebug("warn", "SVM_COMBINED: signAllTransactions feature-detect failed", {
|
|
4118
|
+
actionId: action.id,
|
|
4119
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4120
|
+
});
|
|
4121
|
+
return false;
|
|
4122
|
+
}
|
|
4123
|
+
},
|
|
4124
|
+
[]
|
|
4125
|
+
);
|
|
3344
4126
|
const canBatch = react.useCallback(async () => {
|
|
3345
4127
|
const cacheKey = getBatchCapabilityCacheKey(wagmiConfig2);
|
|
3346
4128
|
const cachedDecision = batchCapabilityDecisionRef.current;
|
|
@@ -3384,11 +4166,13 @@ function useAuthorizationExecutor(options) {
|
|
|
3384
4166
|
setResults([]);
|
|
3385
4167
|
setError(null);
|
|
3386
4168
|
setBatchTxHash(null);
|
|
4169
|
+
setApproveSplConfirming(null);
|
|
3387
4170
|
return true;
|
|
3388
4171
|
}, []);
|
|
3389
4172
|
const endExecution = react.useCallback(() => {
|
|
3390
4173
|
sessionIdRef.current = null;
|
|
3391
4174
|
setCurrentAction(null);
|
|
4175
|
+
setApproveSplConfirming(null);
|
|
3392
4176
|
setExecuting(false);
|
|
3393
4177
|
executingRef.current = false;
|
|
3394
4178
|
}, []);
|
|
@@ -3481,12 +4265,15 @@ function useAuthorizationExecutor(options) {
|
|
|
3481
4265
|
results,
|
|
3482
4266
|
error,
|
|
3483
4267
|
currentAction,
|
|
4268
|
+
approveSplConfirming,
|
|
3484
4269
|
pendingSelectSource,
|
|
3485
4270
|
resolveSelectSource,
|
|
3486
4271
|
cancelPendingExecution,
|
|
3487
4272
|
batchTxHash,
|
|
3488
4273
|
executeAction,
|
|
3489
4274
|
executeBatch,
|
|
4275
|
+
executeSvmCombinedFirstTransfer: executeSvmCombinedFirstTransferImpl,
|
|
4276
|
+
canSignAllSolanaTransactions,
|
|
3490
4277
|
canBatch,
|
|
3491
4278
|
checkPaymasterSupport,
|
|
3492
4279
|
getCapabilitySnapshot,
|
|
@@ -3515,6 +4302,8 @@ function isWebAuthnPasskeyDismissalError(err) {
|
|
|
3515
4302
|
|
|
3516
4303
|
// src/hooks/useTransferSigning.ts
|
|
3517
4304
|
var TRANSFER_SIGN_MAX_POLLS = 60;
|
|
4305
|
+
var FAST_POLL_COUNT2 = 6;
|
|
4306
|
+
var FAST_POLL_INTERVAL_MS2 = 500;
|
|
3518
4307
|
function waitForDocumentFocus2(timeoutMs = 5e3, intervalMs = 100) {
|
|
3519
4308
|
return new Promise((resolve, reject) => {
|
|
3520
4309
|
if (typeof document === "undefined") {
|
|
@@ -3542,9 +4331,23 @@ function hexToBytes(hex) {
|
|
|
3542
4331
|
const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
|
|
3543
4332
|
return new Uint8Array(bytes);
|
|
3544
4333
|
}
|
|
4334
|
+
function base64ToBytes2(base64) {
|
|
4335
|
+
const binary = atob(base64);
|
|
4336
|
+
const bytes = new Uint8Array(binary.length);
|
|
4337
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
4338
|
+
bytes[i] = binary.charCodeAt(i);
|
|
4339
|
+
}
|
|
4340
|
+
return bytes;
|
|
4341
|
+
}
|
|
3545
4342
|
function toBase642(buffer) {
|
|
3546
4343
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
3547
4344
|
}
|
|
4345
|
+
function getSigningChallenge(payload) {
|
|
4346
|
+
if (payload.chainFamily === "svm") {
|
|
4347
|
+
return base64ToBytes2(payload.message);
|
|
4348
|
+
}
|
|
4349
|
+
return hexToBytes(payload.userOpHash);
|
|
4350
|
+
}
|
|
3548
4351
|
function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
3549
4352
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3550
4353
|
const apiBaseUrl = options?.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3561,7 +4364,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3561
4364
|
const [error, setError] = react.useState(null);
|
|
3562
4365
|
const [passkeyDismissed, setPasskeyDismissed] = react.useState(false);
|
|
3563
4366
|
const signTransfer2 = react.useCallback(
|
|
3564
|
-
async (transferId) => {
|
|
4367
|
+
async (transferId, opts) => {
|
|
3565
4368
|
setSigning(true);
|
|
3566
4369
|
setError(null);
|
|
3567
4370
|
setPasskeyDismissed(false);
|
|
@@ -3578,22 +4381,28 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3578
4381
|
throw new Error("Could not get access token");
|
|
3579
4382
|
}
|
|
3580
4383
|
let payload = null;
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
4384
|
+
if (opts?.knownSignPayload) {
|
|
4385
|
+
payload = opts.knownSignPayload;
|
|
4386
|
+
setSignPayload(payload);
|
|
4387
|
+
} else {
|
|
4388
|
+
for (let i = 0; i < TRANSFER_SIGN_MAX_POLLS; i++) {
|
|
4389
|
+
const transfer = await fetchTransfer(
|
|
4390
|
+
apiBaseUrl,
|
|
4391
|
+
token ?? "",
|
|
4392
|
+
transferId,
|
|
4393
|
+
authorizationSessionToken
|
|
4394
|
+
);
|
|
4395
|
+
if (transfer.signPayload) {
|
|
4396
|
+
payload = transfer.signPayload;
|
|
4397
|
+
setSignPayload(payload);
|
|
4398
|
+
break;
|
|
4399
|
+
}
|
|
4400
|
+
if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
|
|
4401
|
+
throw new Error(`Unexpected transfer status: ${transfer.status}`);
|
|
4402
|
+
}
|
|
4403
|
+
const intervalMs = i < FAST_POLL_COUNT2 ? Math.min(FAST_POLL_INTERVAL_MS2, pollIntervalMs) : pollIntervalMs;
|
|
4404
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
3595
4405
|
}
|
|
3596
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
3597
4406
|
}
|
|
3598
4407
|
if (!payload) {
|
|
3599
4408
|
throw new Error("Timed out waiting for sign payload. Please try again.");
|
|
@@ -3604,8 +4413,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3604
4413
|
"Sign payload is missing passkeyCredentialId. Cannot request passkey signing without it."
|
|
3605
4414
|
);
|
|
3606
4415
|
}
|
|
3607
|
-
const
|
|
3608
|
-
let signedUserOp;
|
|
4416
|
+
const challengeBytes = getSigningChallenge(payload);
|
|
3609
4417
|
const allowCredentials = [{
|
|
3610
4418
|
type: "public-key",
|
|
3611
4419
|
id: credentialIdBase64ToBytes(passkeyCredentialId)
|
|
@@ -3624,7 +4432,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3624
4432
|
try {
|
|
3625
4433
|
assertion = await navigator.credentials.get({
|
|
3626
4434
|
publicKey: {
|
|
3627
|
-
challenge:
|
|
4435
|
+
challenge: challengeBytes,
|
|
3628
4436
|
rpId: signingRpId,
|
|
3629
4437
|
allowCredentials,
|
|
3630
4438
|
userVerification: "required",
|
|
@@ -3641,18 +4449,24 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3641
4449
|
throw new TransferSigningPasskeyDismissedError();
|
|
3642
4450
|
}
|
|
3643
4451
|
const response = assertion.response;
|
|
3644
|
-
|
|
3645
|
-
...payload.userOp,
|
|
4452
|
+
const webauthnAssertion = {
|
|
3646
4453
|
credentialId: toBase642(assertion.rawId),
|
|
3647
4454
|
signature: toBase642(response.signature),
|
|
3648
4455
|
authenticatorData: toBase642(response.authenticatorData),
|
|
3649
4456
|
clientDataJSON: toBase642(response.clientDataJSON)
|
|
3650
4457
|
};
|
|
4458
|
+
const signedTransfer = payload.chainFamily === "svm" ? {
|
|
4459
|
+
chainFamily: "svm",
|
|
4460
|
+
webauthnAssertion
|
|
4461
|
+
} : {
|
|
4462
|
+
...payload.userOp,
|
|
4463
|
+
...webauthnAssertion
|
|
4464
|
+
};
|
|
3651
4465
|
return await signTransfer(
|
|
3652
4466
|
apiBaseUrl,
|
|
3653
4467
|
token ?? "",
|
|
3654
4468
|
transferId,
|
|
3655
|
-
|
|
4469
|
+
signedTransfer,
|
|
3656
4470
|
authorizationSessionToken
|
|
3657
4471
|
);
|
|
3658
4472
|
} catch (err) {
|
|
@@ -3767,6 +4581,8 @@ function assertLinkedTransferBridgeExecuted(params) {
|
|
|
3767
4581
|
// src/hooks/useAuthorizationOrchestrator.ts
|
|
3768
4582
|
var ACTION_POLL_INTERVAL_MS2 = 500;
|
|
3769
4583
|
var ACTION_POLL_MAX_RETRIES2 = 20;
|
|
4584
|
+
var REPORT_COMPLETION_TIMEOUT_MS = 9e4;
|
|
4585
|
+
var REPORT_COMPLETION_TIMEOUT_MESSAGE = "REPORT_COMPLETION_TIMEOUT";
|
|
3770
4586
|
function useAuthorizationOrchestrator(deps) {
|
|
3771
4587
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3772
4588
|
const resolvedApiBaseUrl = deps.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3894,6 +4710,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3894
4710
|
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
3895
4711
|
});
|
|
3896
4712
|
let cachedBatchSupport = null;
|
|
4713
|
+
let cachedSvmSignAllSupport = null;
|
|
3897
4714
|
while (mergedPending.length > 0) {
|
|
3898
4715
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
3899
4716
|
mergedPending = getMergedPending(sessions, completedIds);
|
|
@@ -3981,6 +4798,112 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3981
4798
|
});
|
|
3982
4799
|
continue;
|
|
3983
4800
|
}
|
|
4801
|
+
if (action.type === "APPROVE_SPL" && (action.metadata?.awaitingLimit || options?.alwaysPauseForOneTap) && !oneTapCompletedActionIds.has(action.id)) {
|
|
4802
|
+
if (options?.onAwaitingOneTap) {
|
|
4803
|
+
console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup via callback.", {
|
|
4804
|
+
actionId: action.id,
|
|
4805
|
+
ownerSessionId
|
|
4806
|
+
});
|
|
4807
|
+
await options.onAwaitingOneTap(action);
|
|
4808
|
+
console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed via callback.", {
|
|
4809
|
+
actionId: action.id,
|
|
4810
|
+
ownerSessionId
|
|
4811
|
+
});
|
|
4812
|
+
} else {
|
|
4813
|
+
console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup.", {
|
|
4814
|
+
actionId: action.id,
|
|
4815
|
+
ownerSessionId
|
|
4816
|
+
});
|
|
4817
|
+
await waitForOneTapPause(action);
|
|
4818
|
+
console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed.", {
|
|
4819
|
+
actionId: action.id,
|
|
4820
|
+
ownerSessionId
|
|
4821
|
+
});
|
|
4822
|
+
}
|
|
4823
|
+
oneTapCompletedActionIds.add(action.id);
|
|
4824
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4825
|
+
await refreshAllSessions(apiBaseUrl, sessions, actionSessionMap);
|
|
4826
|
+
queueLinkedTransferSessions({
|
|
4827
|
+
sessions,
|
|
4828
|
+
linkedTransferSessionIds,
|
|
4829
|
+
pendingSessionIdsRef
|
|
4830
|
+
});
|
|
4831
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4832
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4833
|
+
mergedPending = await waitForPendingActions({
|
|
4834
|
+
apiBaseUrl,
|
|
4835
|
+
sessions,
|
|
4836
|
+
completedIds,
|
|
4837
|
+
actionSessionMap,
|
|
4838
|
+
pendingSessionIdsRef,
|
|
4839
|
+
linkedTransferSessionIds,
|
|
4840
|
+
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
4841
|
+
});
|
|
4842
|
+
continue;
|
|
4843
|
+
}
|
|
4844
|
+
if (isSvmCombinedFirstTransferPair(mergedPending)) {
|
|
4845
|
+
const pair = mergedPending.slice(0, 2);
|
|
4846
|
+
const [svmApproveAction, svmBridgeAction] = pair;
|
|
4847
|
+
if (cachedSvmSignAllSupport == null) {
|
|
4848
|
+
cachedSvmSignAllSupport = await authExecutor.canSignAllSolanaTransactions(
|
|
4849
|
+
svmApproveAction
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
4852
|
+
appendDebug("info", "orchestrator:svm-combined evaluation", {
|
|
4853
|
+
approveActionId: svmApproveAction.id,
|
|
4854
|
+
bridgeActionId: svmBridgeAction.id,
|
|
4855
|
+
ownerSessionId,
|
|
4856
|
+
signAllSupported: cachedSvmSignAllSupport
|
|
4857
|
+
});
|
|
4858
|
+
if (cachedSvmSignAllSupport) {
|
|
4859
|
+
const svmInputs = pair.map((candidate) => ({
|
|
4860
|
+
action: candidate,
|
|
4861
|
+
sessionId: actionSessionMap.get(candidate.id) ?? sessionId
|
|
4862
|
+
}));
|
|
4863
|
+
const svmBatchResult = await authExecutor.executeSvmCombinedFirstTransfer(
|
|
4864
|
+
svmInputs,
|
|
4865
|
+
{
|
|
4866
|
+
onApproveSplConfirming: options?.onApproveSplConfirming
|
|
4867
|
+
}
|
|
4868
|
+
);
|
|
4869
|
+
const errorResult = svmBatchResult.actionResults.find(
|
|
4870
|
+
({ result: result2 }) => result2.status === "error"
|
|
4871
|
+
);
|
|
4872
|
+
if (errorResult) {
|
|
4873
|
+
authExecutor.addResult(errorResult.result);
|
|
4874
|
+
throw new Error(errorResult.result.message);
|
|
4875
|
+
}
|
|
4876
|
+
for (const { action: svmAction, result: result2 } of svmBatchResult.actionResults) {
|
|
4877
|
+
completedIds.add(svmAction.id);
|
|
4878
|
+
oneTapCompletedActionIds.delete(svmAction.id);
|
|
4879
|
+
authExecutor.addResult(result2);
|
|
4880
|
+
const svmOwnerSessionId = actionSessionMap.get(svmAction.id) ?? ownerSessionId;
|
|
4881
|
+
const reportedSession2 = await reportActionCompletionWithLogging(
|
|
4882
|
+
apiBaseUrl,
|
|
4883
|
+
svmAction,
|
|
4884
|
+
svmOwnerSessionId,
|
|
4885
|
+
result2
|
|
4886
|
+
);
|
|
4887
|
+
updateTrackedSession(sessions, svmOwnerSessionId, reportedSession2, actionSessionMap);
|
|
4888
|
+
}
|
|
4889
|
+
queueLinkedTransferSessions({
|
|
4890
|
+
sessions,
|
|
4891
|
+
linkedTransferSessionIds,
|
|
4892
|
+
pendingSessionIdsRef
|
|
4893
|
+
});
|
|
4894
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4895
|
+
mergedPending = await waitForPendingActions({
|
|
4896
|
+
apiBaseUrl,
|
|
4897
|
+
sessions,
|
|
4898
|
+
completedIds,
|
|
4899
|
+
actionSessionMap,
|
|
4900
|
+
pendingSessionIdsRef,
|
|
4901
|
+
linkedTransferSessionIds,
|
|
4902
|
+
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
4903
|
+
});
|
|
4904
|
+
continue;
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
3984
4907
|
if (isBatchableAction(action)) {
|
|
3985
4908
|
const batchable = getLeadingBatchableActions(
|
|
3986
4909
|
mergedPending
|
|
@@ -4007,7 +4930,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4007
4930
|
pendingSessionIdsRef
|
|
4008
4931
|
});
|
|
4009
4932
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4010
|
-
|
|
4933
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4011
4934
|
mergedPending = await waitForPendingActions({
|
|
4012
4935
|
apiBaseUrl,
|
|
4013
4936
|
sessions,
|
|
@@ -4038,7 +4961,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4038
4961
|
pendingSessionIdsRef
|
|
4039
4962
|
});
|
|
4040
4963
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4041
|
-
|
|
4964
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4042
4965
|
mergedPending = await waitForPendingActions({
|
|
4043
4966
|
apiBaseUrl,
|
|
4044
4967
|
sessions,
|
|
@@ -4110,7 +5033,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4110
5033
|
continue;
|
|
4111
5034
|
}
|
|
4112
5035
|
}
|
|
4113
|
-
const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2";
|
|
5036
|
+
const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2" || action.type === "APPROVE_SPL";
|
|
4114
5037
|
if (isPrePromptProbableActionType && probeBeforePrompt && !preProbedIds.has(action.id)) {
|
|
4115
5038
|
preProbedIds.add(action.id);
|
|
4116
5039
|
appendDebug("info", `${action.type}: pre-prompt probe start`, {
|
|
@@ -4155,7 +5078,10 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4155
5078
|
actionId: action.id,
|
|
4156
5079
|
ownerSessionId
|
|
4157
5080
|
});
|
|
4158
|
-
const result = await authExecutor.executeAction(action, {
|
|
5081
|
+
const result = await authExecutor.executeAction(action, {
|
|
5082
|
+
sessionId: ownerSessionId,
|
|
5083
|
+
onApproveSplConfirming: options?.onApproveSplConfirming
|
|
5084
|
+
});
|
|
4159
5085
|
if (result.status === "success" && (action.type === "OPEN_PROVIDER" || action.type === "SWITCH_CHAIN")) {
|
|
4160
5086
|
cachedBatchSupport = null;
|
|
4161
5087
|
}
|
|
@@ -4395,7 +5321,24 @@ function getMergedPending(sessions, completedIds) {
|
|
|
4395
5321
|
for (const { session } of sessions) {
|
|
4396
5322
|
allActions.push(...getPendingActions(session, completedIds));
|
|
4397
5323
|
}
|
|
4398
|
-
|
|
5324
|
+
const hasPendingSvmSetupAction = allActions.some((action) => isSvmSetupAction(action));
|
|
5325
|
+
return allActions.sort((a, b) => {
|
|
5326
|
+
const leftIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(a);
|
|
5327
|
+
const rightIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(b);
|
|
5328
|
+
if (leftIsDeferredSvmBridge !== rightIsDeferredSvmBridge) {
|
|
5329
|
+
return leftIsDeferredSvmBridge ? 1 : -1;
|
|
5330
|
+
}
|
|
5331
|
+
return a.orderIndex - b.orderIndex;
|
|
5332
|
+
});
|
|
5333
|
+
}
|
|
5334
|
+
function isSvmSetupAction(action) {
|
|
5335
|
+
if (action.type === "APPROVE_SPL") {
|
|
5336
|
+
return true;
|
|
5337
|
+
}
|
|
5338
|
+
return action.type === "OPEN_PROVIDER" && action.metadata?.chainFamily === "svm";
|
|
5339
|
+
}
|
|
5340
|
+
function isSvmTransferBridgeAction(action) {
|
|
5341
|
+
return action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm";
|
|
4399
5342
|
}
|
|
4400
5343
|
async function waitForPendingActions(params) {
|
|
4401
5344
|
const {
|
|
@@ -4469,14 +5412,19 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4469
5412
|
actionId: action.id,
|
|
4470
5413
|
ownerSessionId
|
|
4471
5414
|
});
|
|
5415
|
+
const label = `reportActionCompletion ${action.type}`;
|
|
4472
5416
|
try {
|
|
4473
5417
|
const reportedSession = await withWatchdog(
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
5418
|
+
withTimeout(
|
|
5419
|
+
reportActionCompletion(
|
|
5420
|
+
apiBaseUrl,
|
|
5421
|
+
action.id,
|
|
5422
|
+
result.data ?? {}
|
|
5423
|
+
),
|
|
5424
|
+
REPORT_COMPLETION_TIMEOUT_MS,
|
|
5425
|
+
label
|
|
4478
5426
|
),
|
|
4479
|
-
|
|
5427
|
+
label
|
|
4480
5428
|
);
|
|
4481
5429
|
console.info("[blink-sdk][orchestrator] Action completion reported.", {
|
|
4482
5430
|
actionId: action.id,
|
|
@@ -4492,16 +5440,22 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4492
5440
|
});
|
|
4493
5441
|
return reportedSession;
|
|
4494
5442
|
} catch (err) {
|
|
5443
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
4495
5444
|
console.warn("[blink-sdk][orchestrator] Failed to report action completion.", {
|
|
4496
5445
|
actionId: action.id,
|
|
4497
5446
|
actionType: action.type,
|
|
4498
5447
|
ownerSessionId,
|
|
5448
|
+
timedOut,
|
|
4499
5449
|
error: err instanceof Error ? err.message : String(err)
|
|
4500
5450
|
});
|
|
4501
|
-
appendDebug("error", `orchestrator:reportActionCompletion failed ${action.type}`, {
|
|
5451
|
+
appendDebug(timedOut ? "warn" : "error", `orchestrator:reportActionCompletion ${timedOut ? "timed out" : "failed"} ${action.type}`, {
|
|
4502
5452
|
actionId: action.id,
|
|
5453
|
+
timedOut,
|
|
4503
5454
|
error: err instanceof Error ? err.message : String(err)
|
|
4504
5455
|
});
|
|
5456
|
+
if (timedOut) {
|
|
5457
|
+
throw new Error(REPORT_COMPLETION_TIMEOUT_MESSAGE);
|
|
5458
|
+
}
|
|
4505
5459
|
throw err;
|
|
4506
5460
|
}
|
|
4507
5461
|
}
|
|
@@ -4534,10 +5488,10 @@ function updateTrackedSession(sessions, ownerSessionId, reportedSession, actionS
|
|
|
4534
5488
|
}
|
|
4535
5489
|
}
|
|
4536
5490
|
}
|
|
4537
|
-
function
|
|
5491
|
+
function markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds) {
|
|
4538
5492
|
for (const { session } of sessions) {
|
|
4539
5493
|
for (const action of session.actions) {
|
|
4540
|
-
if (action.type === "SIGN_PERMIT2" && action.status === "PENDING") {
|
|
5494
|
+
if ((action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL") && action.status === "PENDING") {
|
|
4541
5495
|
oneTapCompletedActionIds.add(action.id);
|
|
4542
5496
|
}
|
|
4543
5497
|
}
|
|
@@ -4590,6 +5544,8 @@ function screenForPhase(phase) {
|
|
|
4590
5544
|
case "completed":
|
|
4591
5545
|
case "failed":
|
|
4592
5546
|
return "success";
|
|
5547
|
+
case "amount-too-low":
|
|
5548
|
+
return "amount-too-low";
|
|
4593
5549
|
}
|
|
4594
5550
|
}
|
|
4595
5551
|
|
|
@@ -4639,6 +5595,12 @@ function hasActiveWallet(accounts) {
|
|
|
4639
5595
|
return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
|
|
4640
5596
|
}
|
|
4641
5597
|
function resolveTerminalPhase(state) {
|
|
5598
|
+
if (state.amountTooLow != null) {
|
|
5599
|
+
return {
|
|
5600
|
+
step: "amount-too-low",
|
|
5601
|
+
minAmountUsd: state.amountTooLow.minAmountUsd
|
|
5602
|
+
};
|
|
5603
|
+
}
|
|
4642
5604
|
const transferCompleted = state.transfer?.status === "COMPLETED";
|
|
4643
5605
|
const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
|
|
4644
5606
|
if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
|
|
@@ -4792,7 +5754,8 @@ function createInitialState(config) {
|
|
|
4792
5754
|
setupDepositToken: null,
|
|
4793
5755
|
setupDepositConfirmed: false,
|
|
4794
5756
|
guestWalletPrepared: null,
|
|
4795
|
-
guestWalletDeeplinksPreparing: false
|
|
5757
|
+
guestWalletDeeplinksPreparing: false,
|
|
5758
|
+
amountTooLow: null
|
|
4796
5759
|
};
|
|
4797
5760
|
}
|
|
4798
5761
|
function clearAuthenticatedSessionState(state) {
|
|
@@ -4829,7 +5792,8 @@ function clearAuthenticatedSessionState(state) {
|
|
|
4829
5792
|
setupDepositToken: null,
|
|
4830
5793
|
setupDepositConfirmed: false,
|
|
4831
5794
|
guestWalletPrepared: null,
|
|
4832
|
-
guestWalletDeeplinksPreparing: false
|
|
5795
|
+
guestWalletDeeplinksPreparing: false,
|
|
5796
|
+
amountTooLow: null
|
|
4833
5797
|
};
|
|
4834
5798
|
}
|
|
4835
5799
|
function paymentReducer(state, action) {
|
|
@@ -5168,6 +6132,13 @@ function applyAction(state, action) {
|
|
|
5168
6132
|
};
|
|
5169
6133
|
case "SET_ERROR":
|
|
5170
6134
|
return { ...state, error: action.error };
|
|
6135
|
+
case "AMOUNT_TOO_LOW":
|
|
6136
|
+
return {
|
|
6137
|
+
...state,
|
|
6138
|
+
amountTooLow: { minAmountUsd: action.minAmountUsd },
|
|
6139
|
+
creatingTransfer: false,
|
|
6140
|
+
error: null
|
|
6141
|
+
};
|
|
5171
6142
|
case "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP":
|
|
5172
6143
|
return { ...state, oneTapLimitSavedDuringSetup: action.saved };
|
|
5173
6144
|
case "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET":
|
|
@@ -5201,7 +6172,8 @@ function applyAction(state, action) {
|
|
|
5201
6172
|
setupDepositConfirmed: false,
|
|
5202
6173
|
setupFlowScreen: null,
|
|
5203
6174
|
guestWalletPrepared: null,
|
|
5204
|
-
guestWalletDeeplinksPreparing: false
|
|
6175
|
+
guestWalletDeeplinksPreparing: false,
|
|
6176
|
+
amountTooLow: null
|
|
5205
6177
|
};
|
|
5206
6178
|
case "LOGOUT":
|
|
5207
6179
|
return {
|
|
@@ -5548,6 +6520,17 @@ var RABBY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="70 88 386 334"
|
|
|
5548
6520
|
</linearGradient>
|
|
5549
6521
|
</defs>
|
|
5550
6522
|
</svg>`;
|
|
6523
|
+
var PHANTOM_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200" fill="none">
|
|
6524
|
+
<g clip-path="url(#clip0_2596_138580)">
|
|
6525
|
+
<rect y="-0.000976562" width="1200" height="1200" fill="#AB9FF2"/>
|
|
6526
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M522.218 764.813C475.101 837.011 396.147 928.377 291.089 928.377C241.425 928.377 193.671 907.932 193.671 819.121C193.671 592.942 502.479 242.812 789.003 242.812C952.003 242.812 1016.95 355.901 1016.95 484.325C1016.95 649.167 909.979 837.65 803.647 837.65C769.901 837.65 753.346 819.121 753.346 789.731C753.346 782.064 754.62 773.758 757.167 764.813C720.874 826.788 650.835 884.292 585.253 884.292C537.499 884.292 513.304 854.262 513.304 812.093C513.304 796.759 516.487 780.786 522.218 764.813ZM769.035 479.871C769.035 517.293 746.956 536.003 722.258 536.003C697.185 536.003 675.481 517.293 675.481 479.871C675.481 442.449 697.185 423.738 722.258 423.738C746.956 423.738 769.035 442.449 769.035 479.871ZM909.367 479.872C909.367 517.294 887.288 536.005 862.59 536.005C837.517 536.005 815.813 517.294 815.813 479.872C815.813 442.45 837.517 423.74 862.59 423.74C887.288 423.74 909.367 442.45 909.367 479.872Z" fill="#FFFDF8"/>
|
|
6527
|
+
</g>
|
|
6528
|
+
<defs>
|
|
6529
|
+
<clipPath id="clip0_2596_138580">
|
|
6530
|
+
<rect y="-0.000976562" width="1200" height="1200" rx="600" fill="white"/>
|
|
6531
|
+
</clipPath>
|
|
6532
|
+
</defs>
|
|
6533
|
+
</svg>`;
|
|
5551
6534
|
var BLINK_SVG = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
5552
6535
|
<g clip-path="url(#clip0_125_480)">
|
|
5553
6536
|
<rect width="100" height="100" rx="18.5874" fill="black"/>
|
|
@@ -5590,11 +6573,13 @@ var POLYGON_CHAIN_LOGO = svgToDataUri(POLYGON_CHAIN_SVG);
|
|
|
5590
6573
|
var ETHEREUM_CHAIN_LOGO = "https://assets.relay.link/icons/1/light.png";
|
|
5591
6574
|
var MEGAETH_CHAIN_LOGO = svgToDataUri(MEGAETH_CHAIN_SVG);
|
|
5592
6575
|
var MONAD_CHAIN_LOGO = svgToDataUri(MONAD_CHAIN_SVG);
|
|
6576
|
+
var SOLANA_CHAIN_LOGO = "https://assets.relay.link/icons/792703809/light.png";
|
|
5593
6577
|
var USDM_LOGO = svgToDataUri(USDM_TOKEN_SVG);
|
|
5594
6578
|
var METAMASK_LOGO = svgToDataUri(METAMASK_SVG);
|
|
5595
6579
|
var TRUST_WALLET_LOGO = svgToDataUri(TRUST_WALLET_SVG);
|
|
5596
6580
|
var OKX_WALLET_LOGO = svgToDataUri(OKX_WALLET_SVG);
|
|
5597
6581
|
var RABBY_LOGO = svgToDataUri(RABBY_SVG);
|
|
6582
|
+
var PHANTOM_LOGO = svgToDataUri(PHANTOM_SVG);
|
|
5598
6583
|
var USDC_LOGO = svgToDataUri(USDC_SVG);
|
|
5599
6584
|
var TETHER_LOGO = svgToDataUri(TETHER_SVG);
|
|
5600
6585
|
var KNOWN_LOGOS = {
|
|
@@ -5603,6 +6588,7 @@ var KNOWN_LOGOS = {
|
|
|
5603
6588
|
"base account": BASE_LOGO,
|
|
5604
6589
|
"base app": BASE_LOGO,
|
|
5605
6590
|
"okx wallet": OKX_WALLET_LOGO,
|
|
6591
|
+
phantom: PHANTOM_LOGO,
|
|
5606
6592
|
rabby: RABBY_LOGO,
|
|
5607
6593
|
"trust wallet": TRUST_WALLET_LOGO
|
|
5608
6594
|
};
|
|
@@ -5615,15 +6601,20 @@ var TOKEN_LOGOS = {
|
|
|
5615
6601
|
var CHAIN_LOGOS = {
|
|
5616
6602
|
base: BASE_CHAIN_LOGO,
|
|
5617
6603
|
ethereum: ETHEREUM_CHAIN_LOGO,
|
|
6604
|
+
"ethereum mainnet": ETHEREUM_CHAIN_LOGO,
|
|
5618
6605
|
polygon: POLYGON_CHAIN_LOGO,
|
|
6606
|
+
"polygon mainnet": POLYGON_CHAIN_LOGO,
|
|
5619
6607
|
arbitrum: ARBITRUM_CHAIN_LOGO,
|
|
5620
6608
|
"arbitrum one": ARBITRUM_CHAIN_LOGO,
|
|
6609
|
+
"arbitrum mainnet": ARBITRUM_CHAIN_LOGO,
|
|
5621
6610
|
bnb: BNB_CHAIN_LOGO,
|
|
5622
6611
|
"bnb chain": BNB_CHAIN_LOGO,
|
|
5623
6612
|
"bnb smart chain": BNB_CHAIN_LOGO,
|
|
5624
6613
|
bsc: BNB_CHAIN_LOGO,
|
|
5625
6614
|
megaeth: MEGAETH_CHAIN_LOGO,
|
|
5626
|
-
monad: MONAD_CHAIN_LOGO
|
|
6615
|
+
monad: MONAD_CHAIN_LOGO,
|
|
6616
|
+
solana: SOLANA_CHAIN_LOGO,
|
|
6617
|
+
"solana mainnet": SOLANA_CHAIN_LOGO
|
|
5627
6618
|
};
|
|
5628
6619
|
function ScreenLayout({ children, footer }) {
|
|
5629
6620
|
const { tokens, theme } = useBlinkConfig();
|
|
@@ -5871,6 +6862,8 @@ function PrimaryButton({
|
|
|
5871
6862
|
children,
|
|
5872
6863
|
onClick,
|
|
5873
6864
|
href,
|
|
6865
|
+
target,
|
|
6866
|
+
rel,
|
|
5874
6867
|
disabled,
|
|
5875
6868
|
loading,
|
|
5876
6869
|
loadingText = "Please wait...",
|
|
@@ -5923,6 +6916,8 @@ function PrimaryButton({
|
|
|
5923
6916
|
"a",
|
|
5924
6917
|
{
|
|
5925
6918
|
href,
|
|
6919
|
+
target,
|
|
6920
|
+
rel,
|
|
5926
6921
|
onClick,
|
|
5927
6922
|
style: {
|
|
5928
6923
|
...buttonStyle2(tokens, { disabled: false, loading: false }),
|
|
@@ -6679,6 +7674,8 @@ function SourceRow(props) {
|
|
|
6679
7674
|
"a",
|
|
6680
7675
|
{
|
|
6681
7676
|
href: props.href,
|
|
7677
|
+
target: props.target,
|
|
7678
|
+
rel: props.rel,
|
|
6682
7679
|
onClick: props.onClick,
|
|
6683
7680
|
onMouseEnter: () => setHovered(true),
|
|
6684
7681
|
onMouseLeave: () => setHovered(false),
|
|
@@ -7202,6 +8199,22 @@ var closeButtonStyle2 = (tokens) => ({
|
|
|
7202
8199
|
cursor: "pointer",
|
|
7203
8200
|
padding: 0
|
|
7204
8201
|
});
|
|
8202
|
+
function AmountTooLowScreen({
|
|
8203
|
+
minAmountUsd,
|
|
8204
|
+
onRetry,
|
|
8205
|
+
onClose
|
|
8206
|
+
}) {
|
|
8207
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
8208
|
+
BlinkErrorScreen,
|
|
8209
|
+
{
|
|
8210
|
+
title: "Amount too low",
|
|
8211
|
+
message: `The minimum payment amount is $${minAmountUsd.toFixed(2)}.`,
|
|
8212
|
+
retryLabel: onRetry ? "Try Again" : void 0,
|
|
8213
|
+
onRetry,
|
|
8214
|
+
onClose
|
|
8215
|
+
}
|
|
8216
|
+
);
|
|
8217
|
+
}
|
|
7205
8218
|
var RESEND_COOLDOWN_SECONDS = 30;
|
|
7206
8219
|
function OtpVerifyScreen({
|
|
7207
8220
|
maskedIdentifier,
|
|
@@ -7477,19 +8490,15 @@ var contentStyle5 = {
|
|
|
7477
8490
|
gap: 16
|
|
7478
8491
|
};
|
|
7479
8492
|
var illustrationFrameStyle = {
|
|
7480
|
-
flex:
|
|
7481
|
-
minHeight: 0,
|
|
8493
|
+
flex: "0 0 auto",
|
|
7482
8494
|
width: "100%",
|
|
7483
|
-
maxWidth: 329,
|
|
7484
8495
|
display: "flex",
|
|
7485
8496
|
alignItems: "center",
|
|
7486
8497
|
justifyContent: "center"
|
|
7487
8498
|
};
|
|
7488
8499
|
var illustrationImgStyle = {
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
width: "auto",
|
|
7492
|
-
height: "auto",
|
|
8500
|
+
width: 200,
|
|
8501
|
+
height: 200,
|
|
7493
8502
|
objectFit: "contain",
|
|
7494
8503
|
display: "block",
|
|
7495
8504
|
pointerEvents: "none",
|
|
@@ -7497,7 +8506,7 @@ var illustrationImgStyle = {
|
|
|
7497
8506
|
};
|
|
7498
8507
|
var footerButtonStyle = {
|
|
7499
8508
|
width: "100%",
|
|
7500
|
-
paddingTop:
|
|
8509
|
+
paddingTop: 48,
|
|
7501
8510
|
paddingBottom: 36
|
|
7502
8511
|
};
|
|
7503
8512
|
var errorBannerStyle3 = (tokens) => ({
|
|
@@ -7571,19 +8580,17 @@ var contentStyle6 = {
|
|
|
7571
8580
|
gap: 16
|
|
7572
8581
|
};
|
|
7573
8582
|
var illustrationFrameStyle2 = {
|
|
7574
|
-
flex: 1,
|
|
7575
|
-
minHeight: 0,
|
|
8583
|
+
flex: "0 1 auto",
|
|
7576
8584
|
width: "100%",
|
|
7577
|
-
maxWidth:
|
|
8585
|
+
maxWidth: 280,
|
|
8586
|
+
height: "clamp(140px, 34vh, 248px)",
|
|
7578
8587
|
display: "flex",
|
|
7579
8588
|
alignItems: "center",
|
|
7580
8589
|
justifyContent: "center"
|
|
7581
8590
|
};
|
|
7582
8591
|
var illustrationImgStyle2 = {
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
width: "auto",
|
|
7586
|
-
height: "auto",
|
|
8592
|
+
width: "100%",
|
|
8593
|
+
height: "100%",
|
|
7587
8594
|
objectFit: "contain",
|
|
7588
8595
|
display: "block",
|
|
7589
8596
|
pointerEvents: "none",
|
|
@@ -7727,6 +8734,36 @@ function triggerDeeplink(uri) {
|
|
|
7727
8734
|
}
|
|
7728
8735
|
}
|
|
7729
8736
|
|
|
8737
|
+
// src/walletDeeplinks.ts
|
|
8738
|
+
function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
|
|
8739
|
+
const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
|
|
8740
|
+
return matchedUri ?? fallbackUri;
|
|
8741
|
+
}
|
|
8742
|
+
function classifyWalletDeeplinkNavigation(uri) {
|
|
8743
|
+
if (isCustomSchemeUri(uri)) {
|
|
8744
|
+
return { navigationClass: "javascript" };
|
|
8745
|
+
}
|
|
8746
|
+
try {
|
|
8747
|
+
const parsed = new URL(uri);
|
|
8748
|
+
if (isBrowseUniversalLink(parsed)) {
|
|
8749
|
+
return {
|
|
8750
|
+
navigationClass: "iframe-escaping-native-anchor",
|
|
8751
|
+
anchorTarget: "_blank",
|
|
8752
|
+
anchorRel: "noopener noreferrer"
|
|
8753
|
+
};
|
|
8754
|
+
}
|
|
8755
|
+
} catch {
|
|
8756
|
+
return { navigationClass: "javascript" };
|
|
8757
|
+
}
|
|
8758
|
+
return { navigationClass: "javascript" };
|
|
8759
|
+
}
|
|
8760
|
+
function shouldOpenWithJavaScript(navigation) {
|
|
8761
|
+
return navigation.navigationClass === "javascript";
|
|
8762
|
+
}
|
|
8763
|
+
function isBrowseUniversalLink(parsed) {
|
|
8764
|
+
return parsed.protocol === "https:" && parsed.pathname.startsWith("/ul/browse/") && parsed.searchParams.has("ref");
|
|
8765
|
+
}
|
|
8766
|
+
|
|
7730
8767
|
// src/sentry.ts
|
|
7731
8768
|
var _mod;
|
|
7732
8769
|
function captureException(error) {
|
|
@@ -7801,14 +8838,20 @@ function WalletPickerScreen({
|
|
|
7801
8838
|
const rowLoader = isRowPreparing ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 20 }) : void 0;
|
|
7802
8839
|
if (usesDirectLinkCards) {
|
|
7803
8840
|
if (directPreparedSession?.uri) {
|
|
8841
|
+
const navigation = classifyWalletDeeplinkNavigation(directPreparedSession.uri);
|
|
8842
|
+
const openWithJavaScript = shouldOpenWithJavaScript(navigation);
|
|
7804
8843
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7805
8844
|
SourceRow,
|
|
7806
8845
|
{
|
|
7807
8846
|
logo: logoSrc,
|
|
7808
8847
|
name: provider.name,
|
|
7809
8848
|
href: directPreparedSession.uri,
|
|
8849
|
+
target: navigation.anchorTarget,
|
|
8850
|
+
rel: navigation.anchorRel,
|
|
7810
8851
|
onClick: (e) => {
|
|
7811
|
-
|
|
8852
|
+
if (openWithJavaScript) {
|
|
8853
|
+
e.preventDefault();
|
|
8854
|
+
}
|
|
7812
8855
|
setSelectedProviderId(provider.id);
|
|
7813
8856
|
if (directPreparedSession.sessionId) {
|
|
7814
8857
|
void setAuthorizationSessionProvider(
|
|
@@ -7819,7 +8862,9 @@ function WalletPickerScreen({
|
|
|
7819
8862
|
captureException(err);
|
|
7820
8863
|
});
|
|
7821
8864
|
}
|
|
7822
|
-
|
|
8865
|
+
if (openWithJavaScript) {
|
|
8866
|
+
openDeeplink(directPreparedSession.uri);
|
|
8867
|
+
}
|
|
7823
8868
|
void onSelectProvider(provider.id, directPreparedSession);
|
|
7824
8869
|
}
|
|
7825
8870
|
},
|
|
@@ -8677,6 +9722,7 @@ function SelectDepositSourceScreen({
|
|
|
8677
9722
|
const rowAccountId = (opt) => opt.accountId ?? fallbackAccountId;
|
|
8678
9723
|
const isSelected = (opt) => !!selectedTokenSymbol && !!selectedChainName && !!selectedWalletId && opt.symbol === selectedTokenSymbol && opt.chainName === selectedChainName && opt.walletId === selectedWalletId;
|
|
8679
9724
|
const tokenOptionKey = (opt) => `${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`;
|
|
9725
|
+
const hasPendingMobileSelection = pendingMobileSelection != null;
|
|
8680
9726
|
const handleAuthorizedPick = (opt) => {
|
|
8681
9727
|
onPickToken(opt.symbol, opt.chainName, opt.walletId);
|
|
8682
9728
|
onDone();
|
|
@@ -8706,14 +9752,20 @@ function SelectDepositSourceScreen({
|
|
|
8706
9752
|
handleAuthorizedPick(opt);
|
|
8707
9753
|
};
|
|
8708
9754
|
const footerProviderName = preparedAuthorization?.providerName ?? pendingMobileSelection?.providerName ?? "your wallet";
|
|
9755
|
+
const preparedAuthorizationNavigation = preparedAuthorization ? classifyWalletDeeplinkNavigation(preparedAuthorization.deeplinkUri) : null;
|
|
9756
|
+
const preparedAuthorizationOpensWithJavaScript = preparedAuthorizationNavigation ? shouldOpenWithJavaScript(preparedAuthorizationNavigation) : false;
|
|
8709
9757
|
const footer = pendingMobileSelection ? preparedAuthorization ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8710
9758
|
PrimaryButton,
|
|
8711
9759
|
{
|
|
8712
9760
|
href: preparedAuthorization.deeplinkUri,
|
|
9761
|
+
target: preparedAuthorizationNavigation?.anchorTarget,
|
|
9762
|
+
rel: preparedAuthorizationNavigation?.anchorRel,
|
|
8713
9763
|
onClick: (event) => {
|
|
8714
|
-
event.preventDefault();
|
|
8715
9764
|
onCommitTokenAuthorization?.(preparedAuthorization);
|
|
8716
|
-
|
|
9765
|
+
if (preparedAuthorizationOpensWithJavaScript) {
|
|
9766
|
+
event.preventDefault();
|
|
9767
|
+
openDeeplink(preparedAuthorization.deeplinkUri);
|
|
9768
|
+
}
|
|
8717
9769
|
},
|
|
8718
9770
|
children: `Continue in ${footerProviderName}`
|
|
8719
9771
|
}
|
|
@@ -8763,7 +9815,7 @@ function SelectDepositSourceScreen({
|
|
|
8763
9815
|
symbol: opt.symbol,
|
|
8764
9816
|
chainName: opt.chainName,
|
|
8765
9817
|
balance: opt.balance,
|
|
8766
|
-
selected: isSelected(opt),
|
|
9818
|
+
selected: !hasPendingMobileSelection && isSelected(opt),
|
|
8767
9819
|
onClick: () => handleAuthorizedPick(opt)
|
|
8768
9820
|
},
|
|
8769
9821
|
`auth-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -8777,7 +9829,7 @@ function SelectDepositSourceScreen({
|
|
|
8777
9829
|
chainName: opt.chainName,
|
|
8778
9830
|
balance: opt.balance,
|
|
8779
9831
|
requiresAuth: true,
|
|
8780
|
-
selected:
|
|
9832
|
+
selected: hasPendingMobileSelection ? pendingMobileSelection?.key === tokenOptionKey(opt) : isSelected(opt),
|
|
8781
9833
|
onClick: () => handleRequiresAuthPick(account, opt)
|
|
8782
9834
|
},
|
|
8783
9835
|
`req-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -9962,11 +11014,13 @@ function OpenWalletScreen({
|
|
|
9962
11014
|
!loading && onRetryStatus != null && error == null,
|
|
9963
11015
|
deeplinkUri
|
|
9964
11016
|
);
|
|
11017
|
+
const deeplinkNavigation = classifyWalletDeeplinkNavigation(deeplinkUri);
|
|
11018
|
+
const openWithJavaScript = shouldOpenWithJavaScript(deeplinkNavigation);
|
|
9965
11019
|
react.useEffect(() => {
|
|
9966
|
-
if (!useDeeplink || loading || !deeplinkUri || autoOpenedRef.current === deeplinkUri) return;
|
|
11020
|
+
if (!useDeeplink || loading || !deeplinkUri || !openWithJavaScript || autoOpenedRef.current === deeplinkUri) return;
|
|
9967
11021
|
autoOpenedRef.current = deeplinkUri;
|
|
9968
11022
|
triggerDeeplink(deeplinkUri);
|
|
9969
|
-
}, [useDeeplink, loading, deeplinkUri]);
|
|
11023
|
+
}, [useDeeplink, loading, deeplinkUri, openWithJavaScript]);
|
|
9970
11024
|
const handleOpen = react.useCallback(() => {
|
|
9971
11025
|
openDeeplink(deeplinkUri);
|
|
9972
11026
|
}, [deeplinkUri]);
|
|
@@ -10006,7 +11060,10 @@ function OpenWalletScreen({
|
|
|
10006
11060
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
10007
11061
|
PrimaryButton,
|
|
10008
11062
|
{
|
|
10009
|
-
|
|
11063
|
+
href: !openWithJavaScript ? deeplinkUri : void 0,
|
|
11064
|
+
target: deeplinkNavigation.anchorTarget,
|
|
11065
|
+
rel: deeplinkNavigation.anchorRel,
|
|
11066
|
+
onClick: openWithJavaScript ? handleOpen : void 0,
|
|
10010
11067
|
loading,
|
|
10011
11068
|
loadingText: "Preparing authorization\u2026",
|
|
10012
11069
|
children: [
|
|
@@ -10098,6 +11155,7 @@ var inlineWaitStyle = (color) => ({
|
|
|
10098
11155
|
});
|
|
10099
11156
|
function ConfirmSignScreen({
|
|
10100
11157
|
walletName,
|
|
11158
|
+
chainFamily,
|
|
10101
11159
|
signing,
|
|
10102
11160
|
error,
|
|
10103
11161
|
onSign,
|
|
@@ -10106,26 +11164,28 @@ function ConfirmSignScreen({
|
|
|
10106
11164
|
const { tokens } = useBlinkConfig();
|
|
10107
11165
|
const displayName = walletName ?? "your wallet";
|
|
10108
11166
|
const logoSrc = walletName ? KNOWN_LOGOS[walletName.toLowerCase()] : void 0;
|
|
11167
|
+
const isSvmTransfer = chainFamily === "svm";
|
|
11168
|
+
const heading = isSvmTransfer ? "Ready for passkey approval" : "Wallet authorized";
|
|
11169
|
+
const subtitle = isSvmTransfer ? `${displayName} setup is complete. Your Solana payment signs with your passkey only.` : `${displayName} approved the connection. Tap below to confirm your payment.`;
|
|
11170
|
+
const badge = isSvmTransfer ? "Wallet setup complete" : "Authorization complete";
|
|
11171
|
+
const hint = isSvmTransfer ? "No wallet pop-up is needed for this transfer" : "You may be prompted for biometric verification";
|
|
10109
11172
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10110
11173
|
ScreenLayout,
|
|
10111
11174
|
{
|
|
10112
11175
|
footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10113
11176
|
/* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onSign, disabled: signing, children: "Confirm payment" }),
|
|
10114
11177
|
error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: errorStyle2(tokens.textMuted), children: error }),
|
|
10115
|
-
!error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: hintStyle(tokens.textMuted), children:
|
|
11178
|
+
!error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: hintStyle(tokens.textMuted), children: hint })
|
|
10116
11179
|
] }),
|
|
10117
11180
|
children: [
|
|
10118
11181
|
/* @__PURE__ */ jsxRuntime.jsx(ScreenHeader, { onLogout }),
|
|
10119
11182
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle15, children: [
|
|
10120
11183
|
logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: displayName, style: logoStyle3 }) : /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
|
|
10121
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle14(tokens.text), children:
|
|
10122
|
-
/* @__PURE__ */ jsxRuntime.
|
|
10123
|
-
displayName,
|
|
10124
|
-
" approved the connection. Tap below to confirm your payment."
|
|
10125
|
-
] }),
|
|
11184
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle14(tokens.text), children: heading }),
|
|
11185
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: subtitleStyle11(tokens.textSecondary), children: subtitle }),
|
|
10126
11186
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: successBadgeStyle(tokens), children: [
|
|
10127
11187
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: checkmarkStyle, children: "\u2713" }),
|
|
10128
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
11188
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: badge })
|
|
10129
11189
|
] })
|
|
10130
11190
|
] })
|
|
10131
11191
|
]
|
|
@@ -10868,6 +11928,7 @@ function buildConfirmSignScreenProps({
|
|
|
10868
11928
|
}) {
|
|
10869
11929
|
return {
|
|
10870
11930
|
walletName: flow.state.providers.find((p) => p.id === flow.state.selectedProviderId)?.name ?? null,
|
|
11931
|
+
chainFamily: remote.pollingTransfer?.signPayload?.chainFamily ?? null,
|
|
10871
11932
|
signing: remote.transferSigningSigning,
|
|
10872
11933
|
error: flow.state.error || remote.transferSigningError,
|
|
10873
11934
|
onSign: handlers.onConfirmSign,
|
|
@@ -11088,6 +12149,19 @@ function buildSuccessScreenProps({
|
|
|
11088
12149
|
onLogout: authenticated ? handlers.onLogout : void 0
|
|
11089
12150
|
};
|
|
11090
12151
|
}
|
|
12152
|
+
function buildAmountTooLowScreenProps({
|
|
12153
|
+
flow,
|
|
12154
|
+
handlers
|
|
12155
|
+
}) {
|
|
12156
|
+
const { state, onDismiss } = flow;
|
|
12157
|
+
const minAmountUsd = state.phase.step === "amount-too-low" ? state.phase.minAmountUsd : state.amountTooLow?.minAmountUsd ?? flow.minTransferAmountUsd;
|
|
12158
|
+
const canRetry = flow.depositAmount == null;
|
|
12159
|
+
return {
|
|
12160
|
+
minAmountUsd,
|
|
12161
|
+
onRetry: canRetry ? handlers.onNewPayment : void 0,
|
|
12162
|
+
onClose: onDismiss
|
|
12163
|
+
};
|
|
12164
|
+
}
|
|
11091
12165
|
function StepRenderer(props) {
|
|
11092
12166
|
const screen = screenForPhase(props.flow.state.phase);
|
|
11093
12167
|
return /* @__PURE__ */ jsxRuntime.jsx(StepRendererContent, { ...props, screen });
|
|
@@ -11155,6 +12229,8 @@ function StepRendererContent({
|
|
|
11155
12229
|
return /* @__PURE__ */ jsxRuntime.jsx(SelectSourceScreen, { ...buildSelectSourceScreenProps(input) });
|
|
11156
12230
|
case "success":
|
|
11157
12231
|
return /* @__PURE__ */ jsxRuntime.jsx(SuccessScreen, { ...buildSuccessScreenProps(input) });
|
|
12232
|
+
case "amount-too-low":
|
|
12233
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AmountTooLowScreen, { ...buildAmountTooLowScreenProps(input) });
|
|
11158
12234
|
default: {
|
|
11159
12235
|
const _exhaustive = screen;
|
|
11160
12236
|
throw new Error(`Unhandled screen: ${_exhaustive}`);
|
|
@@ -11584,9 +12660,8 @@ function useTransferHandlers(deps) {
|
|
|
11584
12660
|
destination
|
|
11585
12661
|
]);
|
|
11586
12662
|
const handlePay = react.useCallback(async (payAmount, sourceOverrides) => {
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
dispatch({ type: "SET_ERROR", error: `Minimum amount is $${minUsd.toFixed(2)}.` });
|
|
12663
|
+
if (isNaN(payAmount) || payAmount < minTransferAmountUsd) {
|
|
12664
|
+
dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
|
|
11590
12665
|
return;
|
|
11591
12666
|
}
|
|
11592
12667
|
if (!sourceOverrides?.sourceId && !sourceId) {
|
|
@@ -11637,7 +12712,10 @@ function useTransferHandlers(deps) {
|
|
|
11637
12712
|
);
|
|
11638
12713
|
clearPendingTransferState();
|
|
11639
12714
|
dispatch({ type: "TRANSFER_CREATED", transfer: stagedTransfer });
|
|
11640
|
-
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12715
|
+
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12716
|
+
stagedTransfer.id,
|
|
12717
|
+
{ knownSignPayload: stagedTransfer.signPayload }
|
|
12718
|
+
);
|
|
11641
12719
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
|
|
11642
12720
|
polling.startPolling(stagedTransfer.id);
|
|
11643
12721
|
return;
|
|
@@ -11668,7 +12746,10 @@ function useTransferHandlers(deps) {
|
|
|
11668
12746
|
polling.startPolling(t.id);
|
|
11669
12747
|
return;
|
|
11670
12748
|
}
|
|
11671
|
-
const signedTransfer = await transferSigning.signTransfer(
|
|
12749
|
+
const signedTransfer = await transferSigning.signTransfer(
|
|
12750
|
+
t.id,
|
|
12751
|
+
{ knownSignPayload: t.signPayload }
|
|
12752
|
+
);
|
|
11672
12753
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
|
|
11673
12754
|
polling.startPolling(t.id);
|
|
11674
12755
|
} catch (err) {
|
|
@@ -11875,12 +12956,6 @@ function useMobileFlowHandlers(dispatch, polling, reloadAccounts, pollingTransfe
|
|
|
11875
12956
|
};
|
|
11876
12957
|
}
|
|
11877
12958
|
|
|
11878
|
-
// src/walletDeeplinks.ts
|
|
11879
|
-
function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
|
|
11880
|
-
const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
|
|
11881
|
-
return matchedUri ?? fallbackUri;
|
|
11882
|
-
}
|
|
11883
|
-
|
|
11884
12959
|
// src/hooks/providerSelectionGuards.ts
|
|
11885
12960
|
function resolveSetupFlowDepositAmount({
|
|
11886
12961
|
hasActiveWallet: hasActiveWallet2,
|
|
@@ -11947,9 +13022,10 @@ function buildDesktopDirectDuringSetupRunOptions() {
|
|
|
11947
13022
|
alwaysPauseForOneTap: true
|
|
11948
13023
|
};
|
|
11949
13024
|
}
|
|
11950
|
-
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol) {
|
|
13025
|
+
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol, _chainFamily = "evm") {
|
|
13026
|
+
const options = buildDesktopDirectDuringSetupRunOptions();
|
|
11951
13027
|
return {
|
|
11952
|
-
...
|
|
13028
|
+
...options,
|
|
11953
13029
|
// Token authorization should batch like the first-time desktop setup flow:
|
|
11954
13030
|
// auto-resolve SELECT_SOURCE now, then inject the transfer session during one-tap setup.
|
|
11955
13031
|
autoResolveSource: { chainName, tokenSymbol }
|
|
@@ -12618,10 +13694,10 @@ function useProviderHandlers(deps) {
|
|
|
12618
13694
|
dispatch({ type: "SET_ERROR", error: null });
|
|
12619
13695
|
dispatch({ type: "SET_INCREASING_LIMIT", value: true });
|
|
12620
13696
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: depositAmount ?? 5 });
|
|
12621
|
-
let
|
|
13697
|
+
let desktopChain = null;
|
|
12622
13698
|
if (!isMobile) {
|
|
12623
|
-
|
|
12624
|
-
if (!
|
|
13699
|
+
desktopChain = chains.find((chain) => chain.commonId === chainId) ?? null;
|
|
13700
|
+
if (!desktopChain) {
|
|
12625
13701
|
dispatch({ type: "SET_INCREASING_LIMIT", value: false });
|
|
12626
13702
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
12627
13703
|
dispatch({ type: "SET_ERROR", error: `No chain found for chainId ${chainId}` });
|
|
@@ -12630,7 +13706,7 @@ function useProviderHandlers(deps) {
|
|
|
12630
13706
|
dispatch({
|
|
12631
13707
|
type: "SET_SETUP_DEPOSIT_TOKEN",
|
|
12632
13708
|
symbol: tokenSymbol,
|
|
12633
|
-
chainName:
|
|
13709
|
+
chainName: desktopChain.name,
|
|
12634
13710
|
walletId: _walletId,
|
|
12635
13711
|
tokenAddress,
|
|
12636
13712
|
chainId
|
|
@@ -12664,7 +13740,11 @@ function useProviderHandlers(deps) {
|
|
|
12664
13740
|
dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
|
|
12665
13741
|
const result = await orchestrator.run(
|
|
12666
13742
|
session.id,
|
|
12667
|
-
buildDesktopTokenAuthorizationRunOptions(
|
|
13743
|
+
buildDesktopTokenAuthorizationRunOptions(
|
|
13744
|
+
desktopChain.name,
|
|
13745
|
+
tokenSymbol,
|
|
13746
|
+
desktopChain.chainFamily
|
|
13747
|
+
)
|
|
12668
13748
|
);
|
|
12669
13749
|
if (result.status === "cancelled") {
|
|
12670
13750
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
@@ -14070,6 +15150,11 @@ function BlinkPaymentInner({
|
|
|
14070
15150
|
dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
|
|
14071
15151
|
}
|
|
14072
15152
|
}, [depositAmount, dispatch]);
|
|
15153
|
+
react.useEffect(() => {
|
|
15154
|
+
if (depositAmount != null && depositAmount < minTransferAmountUsd) {
|
|
15155
|
+
dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
|
|
15156
|
+
}
|
|
15157
|
+
}, [depositAmount, minTransferAmountUsd, dispatch]);
|
|
14073
15158
|
react.useEffect(() => {
|
|
14074
15159
|
if (!ready || effectiveAuthenticated) return;
|
|
14075
15160
|
clearLocalSessionArtifacts();
|