@swype-org/react-sdk 0.2.173 → 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 +1211 -116
- 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 +1211 -116
- 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();
|
|
@@ -1995,6 +2025,7 @@ var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
|
1995
2025
|
var DEFAULT_MAX_ATTEMPTS = 60;
|
|
1996
2026
|
var DEFAULT_REQUEST_TIMEOUT_MS = 8e3;
|
|
1997
2027
|
var DEFAULT_MAX_CONSECUTIVE_ERRORS = 5;
|
|
2028
|
+
var DEFAULT_ERROR_GRACE_PERIOD_MS = 6e4;
|
|
1998
2029
|
var DEFAULT_LOG_EVERY_N_ATTEMPTS = 5;
|
|
1999
2030
|
function classifyCallsStatus(status) {
|
|
2000
2031
|
if (typeof status === "number") {
|
|
@@ -2016,15 +2047,18 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2016
2047
|
const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
2017
2048
|
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2018
2049
|
const maxConsecutiveErrors = options.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS;
|
|
2050
|
+
const errorGracePeriodMs = options.errorGracePeriodMs ?? DEFAULT_ERROR_GRACE_PERIOD_MS;
|
|
2019
2051
|
const logEveryN = Math.max(1, options.logEveryNAttempts ?? DEFAULT_LOG_EVERY_N_ATTEMPTS);
|
|
2020
|
-
const
|
|
2052
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2053
|
+
const now = options.now ?? (() => Date.now());
|
|
2021
2054
|
const info = options.logger?.info ?? ((m) => console.info(m));
|
|
2022
2055
|
const warn = options.logger?.warn ?? ((m) => console.warn(m));
|
|
2023
2056
|
const error = options.logger?.error ?? ((m) => console.error(m));
|
|
2057
|
+
const pollStartedAt = now();
|
|
2024
2058
|
let consecutiveErrors = 0;
|
|
2025
2059
|
let lastStatus;
|
|
2026
2060
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2027
|
-
await
|
|
2061
|
+
await sleep2(pollIntervalMs);
|
|
2028
2062
|
let status;
|
|
2029
2063
|
try {
|
|
2030
2064
|
status = await withTimeout(
|
|
@@ -2040,9 +2074,15 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2040
2074
|
consecutiveErrors += 1;
|
|
2041
2075
|
const message = err instanceof Error ? err.message : String(err);
|
|
2042
2076
|
const isTimeout = err instanceof PromiseTimeoutError;
|
|
2077
|
+
const elapsedMs = now() - pollStartedAt;
|
|
2078
|
+
const inGracePeriod = elapsedMs < errorGracePeriodMs;
|
|
2043
2079
|
warn(
|
|
2044
|
-
`[blink-sdk][batch-wallet-calls] wallet_getCallsStatus errored. callsId=${callsId}, attempt=${attempt}, consecutiveErrors=${consecutiveErrors}, isTimeout=${isTimeout}, error=${message}`
|
|
2080
|
+
`[blink-sdk][batch-wallet-calls] wallet_getCallsStatus errored. callsId=${callsId}, attempt=${attempt}, consecutiveErrors=${consecutiveErrors}, isTimeout=${isTimeout}, inGracePeriod=${inGracePeriod}, elapsedMs=${elapsedMs}, error=${message}`
|
|
2045
2081
|
);
|
|
2082
|
+
if (inGracePeriod) {
|
|
2083
|
+
consecutiveErrors = 0;
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2046
2086
|
if (consecutiveErrors >= maxConsecutiveErrors) {
|
|
2047
2087
|
error(
|
|
2048
2088
|
`[blink-sdk][batch-wallet-calls] Aborting poll after ${consecutiveErrors} consecutive errors. callsId=${callsId}, attempt=${attempt}, lastError=${message}`
|
|
@@ -2200,6 +2240,10 @@ function buildTargetMatchers(target) {
|
|
|
2200
2240
|
aliases.add("rabby");
|
|
2201
2241
|
aliases.add("io.rabby");
|
|
2202
2242
|
}
|
|
2243
|
+
if (value.includes("phantom")) {
|
|
2244
|
+
aliases.add("phantom");
|
|
2245
|
+
aliases.add("app.phantom");
|
|
2246
|
+
}
|
|
2203
2247
|
if (value.includes("injected")) {
|
|
2204
2248
|
aliases.add("injected");
|
|
2205
2249
|
}
|
|
@@ -2237,6 +2281,252 @@ function resolveWalletConnector(connectors, target) {
|
|
|
2237
2281
|
return metaMaskConnector ?? connectors[0];
|
|
2238
2282
|
}
|
|
2239
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
|
+
|
|
2240
2530
|
// src/hooks/authorizationExecutor.ts
|
|
2241
2531
|
var WALLET_CLIENT_MAX_ATTEMPTS = 25;
|
|
2242
2532
|
var WALLET_CLIENT_POLL_MS = 400;
|
|
@@ -2248,6 +2538,7 @@ var EXECUTE_BRIDGE_TX_TIMEOUT_MS = 12e4;
|
|
|
2248
2538
|
var EXECUTE_BRIDGE_TX_TIMEOUT_MESSAGE = "EXECUTE_BRIDGE_TX_TIMEOUT";
|
|
2249
2539
|
var WALLET_PROMPT_TIMEOUT_MS = 9e4;
|
|
2250
2540
|
var WALLET_PROMPT_TIMEOUT_MESSAGE = "WALLET_PROMPT_TIMEOUT";
|
|
2541
|
+
var PHANTOM_SOLANA_CONNECT_TIMEOUT_MS = 8e3;
|
|
2251
2542
|
var BATCH_RECEIPT_POLL_INTERVAL_MS = 1500;
|
|
2252
2543
|
var BATCH_RECEIPT_MAX_ATTEMPTS = 20;
|
|
2253
2544
|
var BATCHABLE_ACTION_TYPES = /* @__PURE__ */ new Set(["APPROVE_PERMIT2", "SIGN_PERMIT2", "EXECUTE_BRIDGE"]);
|
|
@@ -2361,13 +2652,110 @@ function parseSignTypedDataPayload(typedData) {
|
|
|
2361
2652
|
};
|
|
2362
2653
|
}
|
|
2363
2654
|
function isBatchableAction(action) {
|
|
2655
|
+
if (action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm") {
|
|
2656
|
+
return false;
|
|
2657
|
+
}
|
|
2364
2658
|
return BATCHABLE_ACTION_TYPES.has(action.type);
|
|
2365
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
|
+
}
|
|
2366
2719
|
function getPendingActions(session, completedIds) {
|
|
2367
2720
|
return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
|
|
2368
2721
|
}
|
|
2369
2722
|
async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
|
|
2370
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
|
+
}
|
|
2371
2759
|
const account = core.getAccount(wagmiConfig2);
|
|
2372
2760
|
const targetId = action.metadata?.wagmiConnectorId;
|
|
2373
2761
|
const connector = resolveWalletConnector(connectors, { wagmiConnectorId: targetId });
|
|
@@ -2493,9 +2881,16 @@ async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsyn
|
|
|
2493
2881
|
{ accounts: [...result.accounts], chainId: hexChainId }
|
|
2494
2882
|
);
|
|
2495
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
|
+
}
|
|
2496
2891
|
return actionError(
|
|
2497
2892
|
action,
|
|
2498
|
-
|
|
2893
|
+
msg
|
|
2499
2894
|
);
|
|
2500
2895
|
}
|
|
2501
2896
|
}
|
|
@@ -2573,6 +2968,334 @@ async function executeSwitchChain(action, wagmiConfig2, switchChainAsync) {
|
|
|
2573
2968
|
);
|
|
2574
2969
|
}
|
|
2575
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
|
+
}
|
|
2576
3299
|
async function executeApprovePermit2(action, wagmiConfig2) {
|
|
2577
3300
|
let walletClient = null;
|
|
2578
3301
|
let sender = null;
|
|
@@ -3218,6 +3941,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3218
3941
|
const [results, setResults] = react.useState([]);
|
|
3219
3942
|
const [error, setError] = react.useState(null);
|
|
3220
3943
|
const [currentAction, setCurrentAction] = react.useState(null);
|
|
3944
|
+
const [approveSplConfirming, setApproveSplConfirming] = react.useState(null);
|
|
3221
3945
|
const executingRef = react.useRef(false);
|
|
3222
3946
|
const walletCapabilitiesRef = react.useRef({});
|
|
3223
3947
|
const walletCapabilitiesRefreshedRef = react.useRef(false);
|
|
@@ -3256,16 +3980,17 @@ function useAuthorizationExecutor(options) {
|
|
|
3256
3980
|
}
|
|
3257
3981
|
setError(null);
|
|
3258
3982
|
setCurrentAction(null);
|
|
3983
|
+
setApproveSplConfirming(null);
|
|
3259
3984
|
setExecuting(false);
|
|
3260
3985
|
executingRef.current = false;
|
|
3261
3986
|
}, []);
|
|
3262
3987
|
const dispatchAction = react.useCallback(
|
|
3263
|
-
async (action) => {
|
|
3988
|
+
async (action, dispatchOptions) => {
|
|
3264
3989
|
setCurrentAction(action);
|
|
3265
3990
|
switch (action.type) {
|
|
3266
3991
|
case "OPEN_PROVIDER": {
|
|
3267
3992
|
const result = await executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
|
|
3268
|
-
if (result.status === "success") {
|
|
3993
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3269
3994
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3270
3995
|
wagmiConfig2,
|
|
3271
3996
|
"open-provider"
|
|
@@ -3277,7 +4002,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3277
4002
|
return executeSelectSource(action, waitForSelection);
|
|
3278
4003
|
case "SWITCH_CHAIN": {
|
|
3279
4004
|
const result = await executeSwitchChain(action, wagmiConfig2, switchChainAsync);
|
|
3280
|
-
if (result.status === "success") {
|
|
4005
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3281
4006
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3282
4007
|
wagmiConfig2,
|
|
3283
4008
|
"switch-chain"
|
|
@@ -3297,7 +4022,33 @@ function useAuthorizationExecutor(options) {
|
|
|
3297
4022
|
}
|
|
3298
4023
|
return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
|
|
3299
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
|
+
}
|
|
3300
4048
|
case "EXECUTE_BRIDGE":
|
|
4049
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
4050
|
+
return executeExecuteBridgeSolana(action);
|
|
4051
|
+
}
|
|
3301
4052
|
return executeExecuteBridge(action, wagmiConfig2);
|
|
3302
4053
|
default:
|
|
3303
4054
|
return actionError(action, `Unsupported action type: ${action.type}`);
|
|
@@ -3310,7 +4061,11 @@ function useAuthorizationExecutor(options) {
|
|
|
3310
4061
|
if (options2?.sessionId) {
|
|
3311
4062
|
sessionIdRef.current = options2.sessionId;
|
|
3312
4063
|
}
|
|
3313
|
-
|
|
4064
|
+
try {
|
|
4065
|
+
return await dispatchAction(action, options2);
|
|
4066
|
+
} finally {
|
|
4067
|
+
setApproveSplConfirming(null);
|
|
4068
|
+
}
|
|
3314
4069
|
},
|
|
3315
4070
|
[dispatchAction]
|
|
3316
4071
|
);
|
|
@@ -3331,6 +4086,43 @@ function useAuthorizationExecutor(options) {
|
|
|
3331
4086
|
},
|
|
3332
4087
|
[apiBaseUrl, wagmiConfig2]
|
|
3333
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
|
+
);
|
|
3334
4126
|
const canBatch = react.useCallback(async () => {
|
|
3335
4127
|
const cacheKey = getBatchCapabilityCacheKey(wagmiConfig2);
|
|
3336
4128
|
const cachedDecision = batchCapabilityDecisionRef.current;
|
|
@@ -3374,11 +4166,13 @@ function useAuthorizationExecutor(options) {
|
|
|
3374
4166
|
setResults([]);
|
|
3375
4167
|
setError(null);
|
|
3376
4168
|
setBatchTxHash(null);
|
|
4169
|
+
setApproveSplConfirming(null);
|
|
3377
4170
|
return true;
|
|
3378
4171
|
}, []);
|
|
3379
4172
|
const endExecution = react.useCallback(() => {
|
|
3380
4173
|
sessionIdRef.current = null;
|
|
3381
4174
|
setCurrentAction(null);
|
|
4175
|
+
setApproveSplConfirming(null);
|
|
3382
4176
|
setExecuting(false);
|
|
3383
4177
|
executingRef.current = false;
|
|
3384
4178
|
}, []);
|
|
@@ -3471,12 +4265,15 @@ function useAuthorizationExecutor(options) {
|
|
|
3471
4265
|
results,
|
|
3472
4266
|
error,
|
|
3473
4267
|
currentAction,
|
|
4268
|
+
approveSplConfirming,
|
|
3474
4269
|
pendingSelectSource,
|
|
3475
4270
|
resolveSelectSource,
|
|
3476
4271
|
cancelPendingExecution,
|
|
3477
4272
|
batchTxHash,
|
|
3478
4273
|
executeAction,
|
|
3479
4274
|
executeBatch,
|
|
4275
|
+
executeSvmCombinedFirstTransfer: executeSvmCombinedFirstTransferImpl,
|
|
4276
|
+
canSignAllSolanaTransactions,
|
|
3480
4277
|
canBatch,
|
|
3481
4278
|
checkPaymasterSupport,
|
|
3482
4279
|
getCapabilitySnapshot,
|
|
@@ -3505,6 +4302,8 @@ function isWebAuthnPasskeyDismissalError(err) {
|
|
|
3505
4302
|
|
|
3506
4303
|
// src/hooks/useTransferSigning.ts
|
|
3507
4304
|
var TRANSFER_SIGN_MAX_POLLS = 60;
|
|
4305
|
+
var FAST_POLL_COUNT2 = 6;
|
|
4306
|
+
var FAST_POLL_INTERVAL_MS2 = 500;
|
|
3508
4307
|
function waitForDocumentFocus2(timeoutMs = 5e3, intervalMs = 100) {
|
|
3509
4308
|
return new Promise((resolve, reject) => {
|
|
3510
4309
|
if (typeof document === "undefined") {
|
|
@@ -3532,9 +4331,23 @@ function hexToBytes(hex) {
|
|
|
3532
4331
|
const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
|
|
3533
4332
|
return new Uint8Array(bytes);
|
|
3534
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
|
+
}
|
|
3535
4342
|
function toBase642(buffer) {
|
|
3536
4343
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
3537
4344
|
}
|
|
4345
|
+
function getSigningChallenge(payload) {
|
|
4346
|
+
if (payload.chainFamily === "svm") {
|
|
4347
|
+
return base64ToBytes2(payload.message);
|
|
4348
|
+
}
|
|
4349
|
+
return hexToBytes(payload.userOpHash);
|
|
4350
|
+
}
|
|
3538
4351
|
function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
3539
4352
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3540
4353
|
const apiBaseUrl = options?.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3551,7 +4364,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3551
4364
|
const [error, setError] = react.useState(null);
|
|
3552
4365
|
const [passkeyDismissed, setPasskeyDismissed] = react.useState(false);
|
|
3553
4366
|
const signTransfer2 = react.useCallback(
|
|
3554
|
-
async (transferId) => {
|
|
4367
|
+
async (transferId, opts) => {
|
|
3555
4368
|
setSigning(true);
|
|
3556
4369
|
setError(null);
|
|
3557
4370
|
setPasskeyDismissed(false);
|
|
@@ -3568,22 +4381,28 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3568
4381
|
throw new Error("Could not get access token");
|
|
3569
4382
|
}
|
|
3570
4383
|
let payload = null;
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
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));
|
|
3585
4405
|
}
|
|
3586
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
3587
4406
|
}
|
|
3588
4407
|
if (!payload) {
|
|
3589
4408
|
throw new Error("Timed out waiting for sign payload. Please try again.");
|
|
@@ -3594,8 +4413,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3594
4413
|
"Sign payload is missing passkeyCredentialId. Cannot request passkey signing without it."
|
|
3595
4414
|
);
|
|
3596
4415
|
}
|
|
3597
|
-
const
|
|
3598
|
-
let signedUserOp;
|
|
4416
|
+
const challengeBytes = getSigningChallenge(payload);
|
|
3599
4417
|
const allowCredentials = [{
|
|
3600
4418
|
type: "public-key",
|
|
3601
4419
|
id: credentialIdBase64ToBytes(passkeyCredentialId)
|
|
@@ -3614,7 +4432,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3614
4432
|
try {
|
|
3615
4433
|
assertion = await navigator.credentials.get({
|
|
3616
4434
|
publicKey: {
|
|
3617
|
-
challenge:
|
|
4435
|
+
challenge: challengeBytes,
|
|
3618
4436
|
rpId: signingRpId,
|
|
3619
4437
|
allowCredentials,
|
|
3620
4438
|
userVerification: "required",
|
|
@@ -3631,18 +4449,24 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3631
4449
|
throw new TransferSigningPasskeyDismissedError();
|
|
3632
4450
|
}
|
|
3633
4451
|
const response = assertion.response;
|
|
3634
|
-
|
|
3635
|
-
...payload.userOp,
|
|
4452
|
+
const webauthnAssertion = {
|
|
3636
4453
|
credentialId: toBase642(assertion.rawId),
|
|
3637
4454
|
signature: toBase642(response.signature),
|
|
3638
4455
|
authenticatorData: toBase642(response.authenticatorData),
|
|
3639
4456
|
clientDataJSON: toBase642(response.clientDataJSON)
|
|
3640
4457
|
};
|
|
4458
|
+
const signedTransfer = payload.chainFamily === "svm" ? {
|
|
4459
|
+
chainFamily: "svm",
|
|
4460
|
+
webauthnAssertion
|
|
4461
|
+
} : {
|
|
4462
|
+
...payload.userOp,
|
|
4463
|
+
...webauthnAssertion
|
|
4464
|
+
};
|
|
3641
4465
|
return await signTransfer(
|
|
3642
4466
|
apiBaseUrl,
|
|
3643
4467
|
token ?? "",
|
|
3644
4468
|
transferId,
|
|
3645
|
-
|
|
4469
|
+
signedTransfer,
|
|
3646
4470
|
authorizationSessionToken
|
|
3647
4471
|
);
|
|
3648
4472
|
} catch (err) {
|
|
@@ -3757,6 +4581,8 @@ function assertLinkedTransferBridgeExecuted(params) {
|
|
|
3757
4581
|
// src/hooks/useAuthorizationOrchestrator.ts
|
|
3758
4582
|
var ACTION_POLL_INTERVAL_MS2 = 500;
|
|
3759
4583
|
var ACTION_POLL_MAX_RETRIES2 = 20;
|
|
4584
|
+
var REPORT_COMPLETION_TIMEOUT_MS = 9e4;
|
|
4585
|
+
var REPORT_COMPLETION_TIMEOUT_MESSAGE = "REPORT_COMPLETION_TIMEOUT";
|
|
3760
4586
|
function useAuthorizationOrchestrator(deps) {
|
|
3761
4587
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3762
4588
|
const resolvedApiBaseUrl = deps.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3884,6 +4710,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3884
4710
|
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
3885
4711
|
});
|
|
3886
4712
|
let cachedBatchSupport = null;
|
|
4713
|
+
let cachedSvmSignAllSupport = null;
|
|
3887
4714
|
while (mergedPending.length > 0) {
|
|
3888
4715
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
3889
4716
|
mergedPending = getMergedPending(sessions, completedIds);
|
|
@@ -3971,6 +4798,112 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3971
4798
|
});
|
|
3972
4799
|
continue;
|
|
3973
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
|
+
}
|
|
3974
4907
|
if (isBatchableAction(action)) {
|
|
3975
4908
|
const batchable = getLeadingBatchableActions(
|
|
3976
4909
|
mergedPending
|
|
@@ -3997,7 +4930,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3997
4930
|
pendingSessionIdsRef
|
|
3998
4931
|
});
|
|
3999
4932
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4000
|
-
|
|
4933
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4001
4934
|
mergedPending = await waitForPendingActions({
|
|
4002
4935
|
apiBaseUrl,
|
|
4003
4936
|
sessions,
|
|
@@ -4028,7 +4961,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4028
4961
|
pendingSessionIdsRef
|
|
4029
4962
|
});
|
|
4030
4963
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4031
|
-
|
|
4964
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4032
4965
|
mergedPending = await waitForPendingActions({
|
|
4033
4966
|
apiBaseUrl,
|
|
4034
4967
|
sessions,
|
|
@@ -4100,7 +5033,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4100
5033
|
continue;
|
|
4101
5034
|
}
|
|
4102
5035
|
}
|
|
4103
|
-
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";
|
|
4104
5037
|
if (isPrePromptProbableActionType && probeBeforePrompt && !preProbedIds.has(action.id)) {
|
|
4105
5038
|
preProbedIds.add(action.id);
|
|
4106
5039
|
appendDebug("info", `${action.type}: pre-prompt probe start`, {
|
|
@@ -4145,7 +5078,10 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4145
5078
|
actionId: action.id,
|
|
4146
5079
|
ownerSessionId
|
|
4147
5080
|
});
|
|
4148
|
-
const result = await authExecutor.executeAction(action, {
|
|
5081
|
+
const result = await authExecutor.executeAction(action, {
|
|
5082
|
+
sessionId: ownerSessionId,
|
|
5083
|
+
onApproveSplConfirming: options?.onApproveSplConfirming
|
|
5084
|
+
});
|
|
4149
5085
|
if (result.status === "success" && (action.type === "OPEN_PROVIDER" || action.type === "SWITCH_CHAIN")) {
|
|
4150
5086
|
cachedBatchSupport = null;
|
|
4151
5087
|
}
|
|
@@ -4385,7 +5321,24 @@ function getMergedPending(sessions, completedIds) {
|
|
|
4385
5321
|
for (const { session } of sessions) {
|
|
4386
5322
|
allActions.push(...getPendingActions(session, completedIds));
|
|
4387
5323
|
}
|
|
4388
|
-
|
|
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";
|
|
4389
5342
|
}
|
|
4390
5343
|
async function waitForPendingActions(params) {
|
|
4391
5344
|
const {
|
|
@@ -4459,14 +5412,19 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4459
5412
|
actionId: action.id,
|
|
4460
5413
|
ownerSessionId
|
|
4461
5414
|
});
|
|
5415
|
+
const label = `reportActionCompletion ${action.type}`;
|
|
4462
5416
|
try {
|
|
4463
5417
|
const reportedSession = await withWatchdog(
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
5418
|
+
withTimeout(
|
|
5419
|
+
reportActionCompletion(
|
|
5420
|
+
apiBaseUrl,
|
|
5421
|
+
action.id,
|
|
5422
|
+
result.data ?? {}
|
|
5423
|
+
),
|
|
5424
|
+
REPORT_COMPLETION_TIMEOUT_MS,
|
|
5425
|
+
label
|
|
4468
5426
|
),
|
|
4469
|
-
|
|
5427
|
+
label
|
|
4470
5428
|
);
|
|
4471
5429
|
console.info("[blink-sdk][orchestrator] Action completion reported.", {
|
|
4472
5430
|
actionId: action.id,
|
|
@@ -4482,16 +5440,22 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4482
5440
|
});
|
|
4483
5441
|
return reportedSession;
|
|
4484
5442
|
} catch (err) {
|
|
5443
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
4485
5444
|
console.warn("[blink-sdk][orchestrator] Failed to report action completion.", {
|
|
4486
5445
|
actionId: action.id,
|
|
4487
5446
|
actionType: action.type,
|
|
4488
5447
|
ownerSessionId,
|
|
5448
|
+
timedOut,
|
|
4489
5449
|
error: err instanceof Error ? err.message : String(err)
|
|
4490
5450
|
});
|
|
4491
|
-
appendDebug("error", `orchestrator:reportActionCompletion failed ${action.type}`, {
|
|
5451
|
+
appendDebug(timedOut ? "warn" : "error", `orchestrator:reportActionCompletion ${timedOut ? "timed out" : "failed"} ${action.type}`, {
|
|
4492
5452
|
actionId: action.id,
|
|
5453
|
+
timedOut,
|
|
4493
5454
|
error: err instanceof Error ? err.message : String(err)
|
|
4494
5455
|
});
|
|
5456
|
+
if (timedOut) {
|
|
5457
|
+
throw new Error(REPORT_COMPLETION_TIMEOUT_MESSAGE);
|
|
5458
|
+
}
|
|
4495
5459
|
throw err;
|
|
4496
5460
|
}
|
|
4497
5461
|
}
|
|
@@ -4524,10 +5488,10 @@ function updateTrackedSession(sessions, ownerSessionId, reportedSession, actionS
|
|
|
4524
5488
|
}
|
|
4525
5489
|
}
|
|
4526
5490
|
}
|
|
4527
|
-
function
|
|
5491
|
+
function markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds) {
|
|
4528
5492
|
for (const { session } of sessions) {
|
|
4529
5493
|
for (const action of session.actions) {
|
|
4530
|
-
if (action.type === "SIGN_PERMIT2" && action.status === "PENDING") {
|
|
5494
|
+
if ((action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL") && action.status === "PENDING") {
|
|
4531
5495
|
oneTapCompletedActionIds.add(action.id);
|
|
4532
5496
|
}
|
|
4533
5497
|
}
|
|
@@ -4580,6 +5544,8 @@ function screenForPhase(phase) {
|
|
|
4580
5544
|
case "completed":
|
|
4581
5545
|
case "failed":
|
|
4582
5546
|
return "success";
|
|
5547
|
+
case "amount-too-low":
|
|
5548
|
+
return "amount-too-low";
|
|
4583
5549
|
}
|
|
4584
5550
|
}
|
|
4585
5551
|
|
|
@@ -4629,6 +5595,12 @@ function hasActiveWallet(accounts) {
|
|
|
4629
5595
|
return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
|
|
4630
5596
|
}
|
|
4631
5597
|
function resolveTerminalPhase(state) {
|
|
5598
|
+
if (state.amountTooLow != null) {
|
|
5599
|
+
return {
|
|
5600
|
+
step: "amount-too-low",
|
|
5601
|
+
minAmountUsd: state.amountTooLow.minAmountUsd
|
|
5602
|
+
};
|
|
5603
|
+
}
|
|
4632
5604
|
const transferCompleted = state.transfer?.status === "COMPLETED";
|
|
4633
5605
|
const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
|
|
4634
5606
|
if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
|
|
@@ -4782,7 +5754,8 @@ function createInitialState(config) {
|
|
|
4782
5754
|
setupDepositToken: null,
|
|
4783
5755
|
setupDepositConfirmed: false,
|
|
4784
5756
|
guestWalletPrepared: null,
|
|
4785
|
-
guestWalletDeeplinksPreparing: false
|
|
5757
|
+
guestWalletDeeplinksPreparing: false,
|
|
5758
|
+
amountTooLow: null
|
|
4786
5759
|
};
|
|
4787
5760
|
}
|
|
4788
5761
|
function clearAuthenticatedSessionState(state) {
|
|
@@ -4819,7 +5792,8 @@ function clearAuthenticatedSessionState(state) {
|
|
|
4819
5792
|
setupDepositToken: null,
|
|
4820
5793
|
setupDepositConfirmed: false,
|
|
4821
5794
|
guestWalletPrepared: null,
|
|
4822
|
-
guestWalletDeeplinksPreparing: false
|
|
5795
|
+
guestWalletDeeplinksPreparing: false,
|
|
5796
|
+
amountTooLow: null
|
|
4823
5797
|
};
|
|
4824
5798
|
}
|
|
4825
5799
|
function paymentReducer(state, action) {
|
|
@@ -5158,6 +6132,13 @@ function applyAction(state, action) {
|
|
|
5158
6132
|
};
|
|
5159
6133
|
case "SET_ERROR":
|
|
5160
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
|
+
};
|
|
5161
6142
|
case "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP":
|
|
5162
6143
|
return { ...state, oneTapLimitSavedDuringSetup: action.saved };
|
|
5163
6144
|
case "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET":
|
|
@@ -5191,7 +6172,8 @@ function applyAction(state, action) {
|
|
|
5191
6172
|
setupDepositConfirmed: false,
|
|
5192
6173
|
setupFlowScreen: null,
|
|
5193
6174
|
guestWalletPrepared: null,
|
|
5194
|
-
guestWalletDeeplinksPreparing: false
|
|
6175
|
+
guestWalletDeeplinksPreparing: false,
|
|
6176
|
+
amountTooLow: null
|
|
5195
6177
|
};
|
|
5196
6178
|
case "LOGOUT":
|
|
5197
6179
|
return {
|
|
@@ -5538,6 +6520,17 @@ var RABBY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="70 88 386 334"
|
|
|
5538
6520
|
</linearGradient>
|
|
5539
6521
|
</defs>
|
|
5540
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>`;
|
|
5541
6534
|
var BLINK_SVG = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
5542
6535
|
<g clip-path="url(#clip0_125_480)">
|
|
5543
6536
|
<rect width="100" height="100" rx="18.5874" fill="black"/>
|
|
@@ -5580,11 +6573,13 @@ var POLYGON_CHAIN_LOGO = svgToDataUri(POLYGON_CHAIN_SVG);
|
|
|
5580
6573
|
var ETHEREUM_CHAIN_LOGO = "https://assets.relay.link/icons/1/light.png";
|
|
5581
6574
|
var MEGAETH_CHAIN_LOGO = svgToDataUri(MEGAETH_CHAIN_SVG);
|
|
5582
6575
|
var MONAD_CHAIN_LOGO = svgToDataUri(MONAD_CHAIN_SVG);
|
|
6576
|
+
var SOLANA_CHAIN_LOGO = "https://assets.relay.link/icons/792703809/light.png";
|
|
5583
6577
|
var USDM_LOGO = svgToDataUri(USDM_TOKEN_SVG);
|
|
5584
6578
|
var METAMASK_LOGO = svgToDataUri(METAMASK_SVG);
|
|
5585
6579
|
var TRUST_WALLET_LOGO = svgToDataUri(TRUST_WALLET_SVG);
|
|
5586
6580
|
var OKX_WALLET_LOGO = svgToDataUri(OKX_WALLET_SVG);
|
|
5587
6581
|
var RABBY_LOGO = svgToDataUri(RABBY_SVG);
|
|
6582
|
+
var PHANTOM_LOGO = svgToDataUri(PHANTOM_SVG);
|
|
5588
6583
|
var USDC_LOGO = svgToDataUri(USDC_SVG);
|
|
5589
6584
|
var TETHER_LOGO = svgToDataUri(TETHER_SVG);
|
|
5590
6585
|
var KNOWN_LOGOS = {
|
|
@@ -5593,6 +6588,7 @@ var KNOWN_LOGOS = {
|
|
|
5593
6588
|
"base account": BASE_LOGO,
|
|
5594
6589
|
"base app": BASE_LOGO,
|
|
5595
6590
|
"okx wallet": OKX_WALLET_LOGO,
|
|
6591
|
+
phantom: PHANTOM_LOGO,
|
|
5596
6592
|
rabby: RABBY_LOGO,
|
|
5597
6593
|
"trust wallet": TRUST_WALLET_LOGO
|
|
5598
6594
|
};
|
|
@@ -5605,15 +6601,20 @@ var TOKEN_LOGOS = {
|
|
|
5605
6601
|
var CHAIN_LOGOS = {
|
|
5606
6602
|
base: BASE_CHAIN_LOGO,
|
|
5607
6603
|
ethereum: ETHEREUM_CHAIN_LOGO,
|
|
6604
|
+
"ethereum mainnet": ETHEREUM_CHAIN_LOGO,
|
|
5608
6605
|
polygon: POLYGON_CHAIN_LOGO,
|
|
6606
|
+
"polygon mainnet": POLYGON_CHAIN_LOGO,
|
|
5609
6607
|
arbitrum: ARBITRUM_CHAIN_LOGO,
|
|
5610
6608
|
"arbitrum one": ARBITRUM_CHAIN_LOGO,
|
|
6609
|
+
"arbitrum mainnet": ARBITRUM_CHAIN_LOGO,
|
|
5611
6610
|
bnb: BNB_CHAIN_LOGO,
|
|
5612
6611
|
"bnb chain": BNB_CHAIN_LOGO,
|
|
5613
6612
|
"bnb smart chain": BNB_CHAIN_LOGO,
|
|
5614
6613
|
bsc: BNB_CHAIN_LOGO,
|
|
5615
6614
|
megaeth: MEGAETH_CHAIN_LOGO,
|
|
5616
|
-
monad: MONAD_CHAIN_LOGO
|
|
6615
|
+
monad: MONAD_CHAIN_LOGO,
|
|
6616
|
+
solana: SOLANA_CHAIN_LOGO,
|
|
6617
|
+
"solana mainnet": SOLANA_CHAIN_LOGO
|
|
5617
6618
|
};
|
|
5618
6619
|
function ScreenLayout({ children, footer }) {
|
|
5619
6620
|
const { tokens, theme } = useBlinkConfig();
|
|
@@ -5861,6 +6862,8 @@ function PrimaryButton({
|
|
|
5861
6862
|
children,
|
|
5862
6863
|
onClick,
|
|
5863
6864
|
href,
|
|
6865
|
+
target,
|
|
6866
|
+
rel,
|
|
5864
6867
|
disabled,
|
|
5865
6868
|
loading,
|
|
5866
6869
|
loadingText = "Please wait...",
|
|
@@ -5913,6 +6916,8 @@ function PrimaryButton({
|
|
|
5913
6916
|
"a",
|
|
5914
6917
|
{
|
|
5915
6918
|
href,
|
|
6919
|
+
target,
|
|
6920
|
+
rel,
|
|
5916
6921
|
onClick,
|
|
5917
6922
|
style: {
|
|
5918
6923
|
...buttonStyle2(tokens, { disabled: false, loading: false }),
|
|
@@ -6669,6 +7674,8 @@ function SourceRow(props) {
|
|
|
6669
7674
|
"a",
|
|
6670
7675
|
{
|
|
6671
7676
|
href: props.href,
|
|
7677
|
+
target: props.target,
|
|
7678
|
+
rel: props.rel,
|
|
6672
7679
|
onClick: props.onClick,
|
|
6673
7680
|
onMouseEnter: () => setHovered(true),
|
|
6674
7681
|
onMouseLeave: () => setHovered(false),
|
|
@@ -7192,6 +8199,22 @@ var closeButtonStyle2 = (tokens) => ({
|
|
|
7192
8199
|
cursor: "pointer",
|
|
7193
8200
|
padding: 0
|
|
7194
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
|
+
}
|
|
7195
8218
|
var RESEND_COOLDOWN_SECONDS = 30;
|
|
7196
8219
|
function OtpVerifyScreen({
|
|
7197
8220
|
maskedIdentifier,
|
|
@@ -7467,19 +8490,15 @@ var contentStyle5 = {
|
|
|
7467
8490
|
gap: 16
|
|
7468
8491
|
};
|
|
7469
8492
|
var illustrationFrameStyle = {
|
|
7470
|
-
flex:
|
|
7471
|
-
minHeight: 0,
|
|
8493
|
+
flex: "0 0 auto",
|
|
7472
8494
|
width: "100%",
|
|
7473
|
-
maxWidth: 329,
|
|
7474
8495
|
display: "flex",
|
|
7475
8496
|
alignItems: "center",
|
|
7476
8497
|
justifyContent: "center"
|
|
7477
8498
|
};
|
|
7478
8499
|
var illustrationImgStyle = {
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
width: "auto",
|
|
7482
|
-
height: "auto",
|
|
8500
|
+
width: 200,
|
|
8501
|
+
height: 200,
|
|
7483
8502
|
objectFit: "contain",
|
|
7484
8503
|
display: "block",
|
|
7485
8504
|
pointerEvents: "none",
|
|
@@ -7487,7 +8506,7 @@ var illustrationImgStyle = {
|
|
|
7487
8506
|
};
|
|
7488
8507
|
var footerButtonStyle = {
|
|
7489
8508
|
width: "100%",
|
|
7490
|
-
paddingTop:
|
|
8509
|
+
paddingTop: 48,
|
|
7491
8510
|
paddingBottom: 36
|
|
7492
8511
|
};
|
|
7493
8512
|
var errorBannerStyle3 = (tokens) => ({
|
|
@@ -7561,19 +8580,17 @@ var contentStyle6 = {
|
|
|
7561
8580
|
gap: 16
|
|
7562
8581
|
};
|
|
7563
8582
|
var illustrationFrameStyle2 = {
|
|
7564
|
-
flex: 1,
|
|
7565
|
-
minHeight: 0,
|
|
8583
|
+
flex: "0 1 auto",
|
|
7566
8584
|
width: "100%",
|
|
7567
|
-
maxWidth:
|
|
8585
|
+
maxWidth: 280,
|
|
8586
|
+
height: "clamp(140px, 34vh, 248px)",
|
|
7568
8587
|
display: "flex",
|
|
7569
8588
|
alignItems: "center",
|
|
7570
8589
|
justifyContent: "center"
|
|
7571
8590
|
};
|
|
7572
8591
|
var illustrationImgStyle2 = {
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
width: "auto",
|
|
7576
|
-
height: "auto",
|
|
8592
|
+
width: "100%",
|
|
8593
|
+
height: "100%",
|
|
7577
8594
|
objectFit: "contain",
|
|
7578
8595
|
display: "block",
|
|
7579
8596
|
pointerEvents: "none",
|
|
@@ -7717,6 +8734,36 @@ function triggerDeeplink(uri) {
|
|
|
7717
8734
|
}
|
|
7718
8735
|
}
|
|
7719
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
|
+
|
|
7720
8767
|
// src/sentry.ts
|
|
7721
8768
|
var _mod;
|
|
7722
8769
|
function captureException(error) {
|
|
@@ -7791,14 +8838,20 @@ function WalletPickerScreen({
|
|
|
7791
8838
|
const rowLoader = isRowPreparing ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 20 }) : void 0;
|
|
7792
8839
|
if (usesDirectLinkCards) {
|
|
7793
8840
|
if (directPreparedSession?.uri) {
|
|
8841
|
+
const navigation = classifyWalletDeeplinkNavigation(directPreparedSession.uri);
|
|
8842
|
+
const openWithJavaScript = shouldOpenWithJavaScript(navigation);
|
|
7794
8843
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7795
8844
|
SourceRow,
|
|
7796
8845
|
{
|
|
7797
8846
|
logo: logoSrc,
|
|
7798
8847
|
name: provider.name,
|
|
7799
8848
|
href: directPreparedSession.uri,
|
|
8849
|
+
target: navigation.anchorTarget,
|
|
8850
|
+
rel: navigation.anchorRel,
|
|
7800
8851
|
onClick: (e) => {
|
|
7801
|
-
|
|
8852
|
+
if (openWithJavaScript) {
|
|
8853
|
+
e.preventDefault();
|
|
8854
|
+
}
|
|
7802
8855
|
setSelectedProviderId(provider.id);
|
|
7803
8856
|
if (directPreparedSession.sessionId) {
|
|
7804
8857
|
void setAuthorizationSessionProvider(
|
|
@@ -7809,7 +8862,9 @@ function WalletPickerScreen({
|
|
|
7809
8862
|
captureException(err);
|
|
7810
8863
|
});
|
|
7811
8864
|
}
|
|
7812
|
-
|
|
8865
|
+
if (openWithJavaScript) {
|
|
8866
|
+
openDeeplink(directPreparedSession.uri);
|
|
8867
|
+
}
|
|
7813
8868
|
void onSelectProvider(provider.id, directPreparedSession);
|
|
7814
8869
|
}
|
|
7815
8870
|
},
|
|
@@ -8667,6 +9722,7 @@ function SelectDepositSourceScreen({
|
|
|
8667
9722
|
const rowAccountId = (opt) => opt.accountId ?? fallbackAccountId;
|
|
8668
9723
|
const isSelected = (opt) => !!selectedTokenSymbol && !!selectedChainName && !!selectedWalletId && opt.symbol === selectedTokenSymbol && opt.chainName === selectedChainName && opt.walletId === selectedWalletId;
|
|
8669
9724
|
const tokenOptionKey = (opt) => `${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`;
|
|
9725
|
+
const hasPendingMobileSelection = pendingMobileSelection != null;
|
|
8670
9726
|
const handleAuthorizedPick = (opt) => {
|
|
8671
9727
|
onPickToken(opt.symbol, opt.chainName, opt.walletId);
|
|
8672
9728
|
onDone();
|
|
@@ -8696,14 +9752,20 @@ function SelectDepositSourceScreen({
|
|
|
8696
9752
|
handleAuthorizedPick(opt);
|
|
8697
9753
|
};
|
|
8698
9754
|
const footerProviderName = preparedAuthorization?.providerName ?? pendingMobileSelection?.providerName ?? "your wallet";
|
|
9755
|
+
const preparedAuthorizationNavigation = preparedAuthorization ? classifyWalletDeeplinkNavigation(preparedAuthorization.deeplinkUri) : null;
|
|
9756
|
+
const preparedAuthorizationOpensWithJavaScript = preparedAuthorizationNavigation ? shouldOpenWithJavaScript(preparedAuthorizationNavigation) : false;
|
|
8699
9757
|
const footer = pendingMobileSelection ? preparedAuthorization ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8700
9758
|
PrimaryButton,
|
|
8701
9759
|
{
|
|
8702
9760
|
href: preparedAuthorization.deeplinkUri,
|
|
9761
|
+
target: preparedAuthorizationNavigation?.anchorTarget,
|
|
9762
|
+
rel: preparedAuthorizationNavigation?.anchorRel,
|
|
8703
9763
|
onClick: (event) => {
|
|
8704
|
-
event.preventDefault();
|
|
8705
9764
|
onCommitTokenAuthorization?.(preparedAuthorization);
|
|
8706
|
-
|
|
9765
|
+
if (preparedAuthorizationOpensWithJavaScript) {
|
|
9766
|
+
event.preventDefault();
|
|
9767
|
+
openDeeplink(preparedAuthorization.deeplinkUri);
|
|
9768
|
+
}
|
|
8707
9769
|
},
|
|
8708
9770
|
children: `Continue in ${footerProviderName}`
|
|
8709
9771
|
}
|
|
@@ -8753,7 +9815,7 @@ function SelectDepositSourceScreen({
|
|
|
8753
9815
|
symbol: opt.symbol,
|
|
8754
9816
|
chainName: opt.chainName,
|
|
8755
9817
|
balance: opt.balance,
|
|
8756
|
-
selected: isSelected(opt),
|
|
9818
|
+
selected: !hasPendingMobileSelection && isSelected(opt),
|
|
8757
9819
|
onClick: () => handleAuthorizedPick(opt)
|
|
8758
9820
|
},
|
|
8759
9821
|
`auth-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -8767,7 +9829,7 @@ function SelectDepositSourceScreen({
|
|
|
8767
9829
|
chainName: opt.chainName,
|
|
8768
9830
|
balance: opt.balance,
|
|
8769
9831
|
requiresAuth: true,
|
|
8770
|
-
selected:
|
|
9832
|
+
selected: hasPendingMobileSelection ? pendingMobileSelection?.key === tokenOptionKey(opt) : isSelected(opt),
|
|
8771
9833
|
onClick: () => handleRequiresAuthPick(account, opt)
|
|
8772
9834
|
},
|
|
8773
9835
|
`req-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -9952,11 +11014,13 @@ function OpenWalletScreen({
|
|
|
9952
11014
|
!loading && onRetryStatus != null && error == null,
|
|
9953
11015
|
deeplinkUri
|
|
9954
11016
|
);
|
|
11017
|
+
const deeplinkNavigation = classifyWalletDeeplinkNavigation(deeplinkUri);
|
|
11018
|
+
const openWithJavaScript = shouldOpenWithJavaScript(deeplinkNavigation);
|
|
9955
11019
|
react.useEffect(() => {
|
|
9956
|
-
if (!useDeeplink || loading || !deeplinkUri || autoOpenedRef.current === deeplinkUri) return;
|
|
11020
|
+
if (!useDeeplink || loading || !deeplinkUri || !openWithJavaScript || autoOpenedRef.current === deeplinkUri) return;
|
|
9957
11021
|
autoOpenedRef.current = deeplinkUri;
|
|
9958
11022
|
triggerDeeplink(deeplinkUri);
|
|
9959
|
-
}, [useDeeplink, loading, deeplinkUri]);
|
|
11023
|
+
}, [useDeeplink, loading, deeplinkUri, openWithJavaScript]);
|
|
9960
11024
|
const handleOpen = react.useCallback(() => {
|
|
9961
11025
|
openDeeplink(deeplinkUri);
|
|
9962
11026
|
}, [deeplinkUri]);
|
|
@@ -9996,7 +11060,10 @@ function OpenWalletScreen({
|
|
|
9996
11060
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
9997
11061
|
PrimaryButton,
|
|
9998
11062
|
{
|
|
9999
|
-
|
|
11063
|
+
href: !openWithJavaScript ? deeplinkUri : void 0,
|
|
11064
|
+
target: deeplinkNavigation.anchorTarget,
|
|
11065
|
+
rel: deeplinkNavigation.anchorRel,
|
|
11066
|
+
onClick: openWithJavaScript ? handleOpen : void 0,
|
|
10000
11067
|
loading,
|
|
10001
11068
|
loadingText: "Preparing authorization\u2026",
|
|
10002
11069
|
children: [
|
|
@@ -10088,6 +11155,7 @@ var inlineWaitStyle = (color) => ({
|
|
|
10088
11155
|
});
|
|
10089
11156
|
function ConfirmSignScreen({
|
|
10090
11157
|
walletName,
|
|
11158
|
+
chainFamily,
|
|
10091
11159
|
signing,
|
|
10092
11160
|
error,
|
|
10093
11161
|
onSign,
|
|
@@ -10096,26 +11164,28 @@ function ConfirmSignScreen({
|
|
|
10096
11164
|
const { tokens } = useBlinkConfig();
|
|
10097
11165
|
const displayName = walletName ?? "your wallet";
|
|
10098
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";
|
|
10099
11172
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
10100
11173
|
ScreenLayout,
|
|
10101
11174
|
{
|
|
10102
11175
|
footer: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
10103
11176
|
/* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onSign, disabled: signing, children: "Confirm payment" }),
|
|
10104
11177
|
error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: errorStyle2(tokens.textMuted), children: error }),
|
|
10105
|
-
!error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: hintStyle(tokens.textMuted), children:
|
|
11178
|
+
!error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: hintStyle(tokens.textMuted), children: hint })
|
|
10106
11179
|
] }),
|
|
10107
11180
|
children: [
|
|
10108
11181
|
/* @__PURE__ */ jsxRuntime.jsx(ScreenHeader, { onLogout }),
|
|
10109
11182
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle15, children: [
|
|
10110
11183
|
logoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoSrc, alt: displayName, style: logoStyle3 }) : /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
|
|
10111
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle14(tokens.text), children:
|
|
10112
|
-
/* @__PURE__ */ jsxRuntime.
|
|
10113
|
-
displayName,
|
|
10114
|
-
" approved the connection. Tap below to confirm your payment."
|
|
10115
|
-
] }),
|
|
11184
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle14(tokens.text), children: heading }),
|
|
11185
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: subtitleStyle11(tokens.textSecondary), children: subtitle }),
|
|
10116
11186
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: successBadgeStyle(tokens), children: [
|
|
10117
11187
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: checkmarkStyle, children: "\u2713" }),
|
|
10118
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
11188
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: badge })
|
|
10119
11189
|
] })
|
|
10120
11190
|
] })
|
|
10121
11191
|
]
|
|
@@ -10858,6 +11928,7 @@ function buildConfirmSignScreenProps({
|
|
|
10858
11928
|
}) {
|
|
10859
11929
|
return {
|
|
10860
11930
|
walletName: flow.state.providers.find((p) => p.id === flow.state.selectedProviderId)?.name ?? null,
|
|
11931
|
+
chainFamily: remote.pollingTransfer?.signPayload?.chainFamily ?? null,
|
|
10861
11932
|
signing: remote.transferSigningSigning,
|
|
10862
11933
|
error: flow.state.error || remote.transferSigningError,
|
|
10863
11934
|
onSign: handlers.onConfirmSign,
|
|
@@ -11078,6 +12149,19 @@ function buildSuccessScreenProps({
|
|
|
11078
12149
|
onLogout: authenticated ? handlers.onLogout : void 0
|
|
11079
12150
|
};
|
|
11080
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
|
+
}
|
|
11081
12165
|
function StepRenderer(props) {
|
|
11082
12166
|
const screen = screenForPhase(props.flow.state.phase);
|
|
11083
12167
|
return /* @__PURE__ */ jsxRuntime.jsx(StepRendererContent, { ...props, screen });
|
|
@@ -11145,6 +12229,8 @@ function StepRendererContent({
|
|
|
11145
12229
|
return /* @__PURE__ */ jsxRuntime.jsx(SelectSourceScreen, { ...buildSelectSourceScreenProps(input) });
|
|
11146
12230
|
case "success":
|
|
11147
12231
|
return /* @__PURE__ */ jsxRuntime.jsx(SuccessScreen, { ...buildSuccessScreenProps(input) });
|
|
12232
|
+
case "amount-too-low":
|
|
12233
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AmountTooLowScreen, { ...buildAmountTooLowScreenProps(input) });
|
|
11148
12234
|
default: {
|
|
11149
12235
|
const _exhaustive = screen;
|
|
11150
12236
|
throw new Error(`Unhandled screen: ${_exhaustive}`);
|
|
@@ -11574,9 +12660,8 @@ function useTransferHandlers(deps) {
|
|
|
11574
12660
|
destination
|
|
11575
12661
|
]);
|
|
11576
12662
|
const handlePay = react.useCallback(async (payAmount, sourceOverrides) => {
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
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 });
|
|
11580
12665
|
return;
|
|
11581
12666
|
}
|
|
11582
12667
|
if (!sourceOverrides?.sourceId && !sourceId) {
|
|
@@ -11627,7 +12712,10 @@ function useTransferHandlers(deps) {
|
|
|
11627
12712
|
);
|
|
11628
12713
|
clearPendingTransferState();
|
|
11629
12714
|
dispatch({ type: "TRANSFER_CREATED", transfer: stagedTransfer });
|
|
11630
|
-
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12715
|
+
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12716
|
+
stagedTransfer.id,
|
|
12717
|
+
{ knownSignPayload: stagedTransfer.signPayload }
|
|
12718
|
+
);
|
|
11631
12719
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
|
|
11632
12720
|
polling.startPolling(stagedTransfer.id);
|
|
11633
12721
|
return;
|
|
@@ -11658,7 +12746,10 @@ function useTransferHandlers(deps) {
|
|
|
11658
12746
|
polling.startPolling(t.id);
|
|
11659
12747
|
return;
|
|
11660
12748
|
}
|
|
11661
|
-
const signedTransfer = await transferSigning.signTransfer(
|
|
12749
|
+
const signedTransfer = await transferSigning.signTransfer(
|
|
12750
|
+
t.id,
|
|
12751
|
+
{ knownSignPayload: t.signPayload }
|
|
12752
|
+
);
|
|
11662
12753
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
|
|
11663
12754
|
polling.startPolling(t.id);
|
|
11664
12755
|
} catch (err) {
|
|
@@ -11865,12 +12956,6 @@ function useMobileFlowHandlers(dispatch, polling, reloadAccounts, pollingTransfe
|
|
|
11865
12956
|
};
|
|
11866
12957
|
}
|
|
11867
12958
|
|
|
11868
|
-
// src/walletDeeplinks.ts
|
|
11869
|
-
function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
|
|
11870
|
-
const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
|
|
11871
|
-
return matchedUri ?? fallbackUri;
|
|
11872
|
-
}
|
|
11873
|
-
|
|
11874
12959
|
// src/hooks/providerSelectionGuards.ts
|
|
11875
12960
|
function resolveSetupFlowDepositAmount({
|
|
11876
12961
|
hasActiveWallet: hasActiveWallet2,
|
|
@@ -11937,9 +13022,10 @@ function buildDesktopDirectDuringSetupRunOptions() {
|
|
|
11937
13022
|
alwaysPauseForOneTap: true
|
|
11938
13023
|
};
|
|
11939
13024
|
}
|
|
11940
|
-
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol) {
|
|
13025
|
+
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol, _chainFamily = "evm") {
|
|
13026
|
+
const options = buildDesktopDirectDuringSetupRunOptions();
|
|
11941
13027
|
return {
|
|
11942
|
-
...
|
|
13028
|
+
...options,
|
|
11943
13029
|
// Token authorization should batch like the first-time desktop setup flow:
|
|
11944
13030
|
// auto-resolve SELECT_SOURCE now, then inject the transfer session during one-tap setup.
|
|
11945
13031
|
autoResolveSource: { chainName, tokenSymbol }
|
|
@@ -12608,10 +13694,10 @@ function useProviderHandlers(deps) {
|
|
|
12608
13694
|
dispatch({ type: "SET_ERROR", error: null });
|
|
12609
13695
|
dispatch({ type: "SET_INCREASING_LIMIT", value: true });
|
|
12610
13696
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: depositAmount ?? 5 });
|
|
12611
|
-
let
|
|
13697
|
+
let desktopChain = null;
|
|
12612
13698
|
if (!isMobile) {
|
|
12613
|
-
|
|
12614
|
-
if (!
|
|
13699
|
+
desktopChain = chains.find((chain) => chain.commonId === chainId) ?? null;
|
|
13700
|
+
if (!desktopChain) {
|
|
12615
13701
|
dispatch({ type: "SET_INCREASING_LIMIT", value: false });
|
|
12616
13702
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
12617
13703
|
dispatch({ type: "SET_ERROR", error: `No chain found for chainId ${chainId}` });
|
|
@@ -12620,7 +13706,7 @@ function useProviderHandlers(deps) {
|
|
|
12620
13706
|
dispatch({
|
|
12621
13707
|
type: "SET_SETUP_DEPOSIT_TOKEN",
|
|
12622
13708
|
symbol: tokenSymbol,
|
|
12623
|
-
chainName:
|
|
13709
|
+
chainName: desktopChain.name,
|
|
12624
13710
|
walletId: _walletId,
|
|
12625
13711
|
tokenAddress,
|
|
12626
13712
|
chainId
|
|
@@ -12654,7 +13740,11 @@ function useProviderHandlers(deps) {
|
|
|
12654
13740
|
dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
|
|
12655
13741
|
const result = await orchestrator.run(
|
|
12656
13742
|
session.id,
|
|
12657
|
-
buildDesktopTokenAuthorizationRunOptions(
|
|
13743
|
+
buildDesktopTokenAuthorizationRunOptions(
|
|
13744
|
+
desktopChain.name,
|
|
13745
|
+
tokenSymbol,
|
|
13746
|
+
desktopChain.chainFamily
|
|
13747
|
+
)
|
|
12658
13748
|
);
|
|
12659
13749
|
if (result.status === "cancelled") {
|
|
12660
13750
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
@@ -14060,6 +15150,11 @@ function BlinkPaymentInner({
|
|
|
14060
15150
|
dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
|
|
14061
15151
|
}
|
|
14062
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]);
|
|
14063
15158
|
react.useEffect(() => {
|
|
14064
15159
|
if (!ready || effectiveAuthenticated) return;
|
|
14065
15160
|
clearLocalSessionArtifacts();
|