@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.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
1
2
|
import { createContext, forwardRef, useContext, useRef, useState, useCallback, useMemo, useEffect, useSyncExternalStore, useReducer, Component } from 'react';
|
|
2
3
|
import { PrivyProvider, usePrivy, useLoginWithPasskey, useSignupWithPasskey } from '@privy-io/react-auth';
|
|
3
4
|
import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChain } from 'wagmi';
|
|
@@ -14,6 +15,10 @@ var __export = (target, all) => {
|
|
|
14
15
|
for (var name in all)
|
|
15
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
17
|
};
|
|
18
|
+
var g = globalThis;
|
|
19
|
+
if (typeof g.Buffer === "undefined") {
|
|
20
|
+
g.Buffer = Buffer;
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
// src/theme.ts
|
|
19
24
|
var darkTheme = {
|
|
@@ -1198,7 +1203,7 @@ async function fetchTransfer(apiBaseUrl, token, transferId, authorizationSession
|
|
|
1198
1203
|
if (!res.ok) await throwApiError(res);
|
|
1199
1204
|
return await res.json();
|
|
1200
1205
|
}
|
|
1201
|
-
async function signTransfer(apiBaseUrl, token, transferId,
|
|
1206
|
+
async function signTransfer(apiBaseUrl, token, transferId, signedTransfer, authorizationSessionToken) {
|
|
1202
1207
|
if (!token && !authorizationSessionToken) {
|
|
1203
1208
|
throw new Error("Missing auth credentials for transfer signing.");
|
|
1204
1209
|
}
|
|
@@ -1209,7 +1214,9 @@ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp, authori
|
|
|
1209
1214
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
1210
1215
|
...authorizationSessionToken ? { "x-authorization-session-token": authorizationSessionToken } : {}
|
|
1211
1216
|
},
|
|
1212
|
-
body: JSON.stringify(
|
|
1217
|
+
body: JSON.stringify(
|
|
1218
|
+
signedTransfer.chainFamily === "svm" ? { signedTransfer } : { signedUserOp: signedTransfer }
|
|
1219
|
+
)
|
|
1213
1220
|
});
|
|
1214
1221
|
if (!res.ok) await throwApiError(res);
|
|
1215
1222
|
return await res.json();
|
|
@@ -1323,7 +1330,14 @@ async function probeActionCompletion(apiBaseUrl, actionId) {
|
|
|
1323
1330
|
if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
|
|
1324
1331
|
return { detected: false, reason: "not-found", status: res.status, code, message };
|
|
1325
1332
|
}
|
|
1326
|
-
|
|
1333
|
+
const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
|
|
1334
|
+
"APPROVE_NOT_DETECTED",
|
|
1335
|
+
"APPROVE_SPL_NOT_DETECTED",
|
|
1336
|
+
"SPL_DELEGATE_MISSING",
|
|
1337
|
+
"SPL_DELEGATE_INSUFFICIENT",
|
|
1338
|
+
"SPL_DELEGATE_WRONG_OWNER"
|
|
1339
|
+
]);
|
|
1340
|
+
if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
|
|
1327
1341
|
return { detected: false, reason: "not-found", status: res.status, code, message };
|
|
1328
1342
|
}
|
|
1329
1343
|
if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
|
|
@@ -1351,6 +1365,8 @@ async function pollTransferTick(params) {
|
|
|
1351
1365
|
}
|
|
1352
1366
|
|
|
1353
1367
|
// src/hooks/useTransferPolling.ts
|
|
1368
|
+
var FAST_POLL_COUNT = 8;
|
|
1369
|
+
var FAST_POLL_INTERVAL_MS = 1e3;
|
|
1354
1370
|
function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
1355
1371
|
const { apiBaseUrl } = useBlinkConfig();
|
|
1356
1372
|
const { getAccessToken: privyGetAccessToken } = usePrivy();
|
|
@@ -1358,12 +1374,15 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
|
1358
1374
|
const [transfer, setTransfer] = useState(null);
|
|
1359
1375
|
const [error, setError] = useState(null);
|
|
1360
1376
|
const [isPolling, setIsPolling] = useState(false);
|
|
1361
|
-
const
|
|
1377
|
+
const timeoutRef = useRef(null);
|
|
1362
1378
|
const transferIdRef = useRef(null);
|
|
1379
|
+
const tickCountRef = useRef(0);
|
|
1380
|
+
const runningRef = useRef(false);
|
|
1363
1381
|
const stopPolling = useCallback(() => {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1382
|
+
runningRef.current = false;
|
|
1383
|
+
if (timeoutRef.current) {
|
|
1384
|
+
clearTimeout(timeoutRef.current);
|
|
1385
|
+
timeoutRef.current = null;
|
|
1367
1386
|
}
|
|
1368
1387
|
setIsPolling(false);
|
|
1369
1388
|
}, []);
|
|
@@ -1388,16 +1407,27 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
|
|
|
1388
1407
|
stopPolling();
|
|
1389
1408
|
}
|
|
1390
1409
|
}, [apiBaseUrl, getAccessToken, stopPolling]);
|
|
1410
|
+
const scheduleNext = useCallback(() => {
|
|
1411
|
+
if (!runningRef.current) return;
|
|
1412
|
+
const next = tickCountRef.current < FAST_POLL_COUNT ? Math.min(FAST_POLL_INTERVAL_MS, intervalMs) : intervalMs;
|
|
1413
|
+
timeoutRef.current = setTimeout(async () => {
|
|
1414
|
+
tickCountRef.current += 1;
|
|
1415
|
+
await poll();
|
|
1416
|
+
scheduleNext();
|
|
1417
|
+
}, next);
|
|
1418
|
+
}, [intervalMs, poll]);
|
|
1391
1419
|
const startPolling = useCallback(
|
|
1392
1420
|
(transferId) => {
|
|
1393
1421
|
stopPolling();
|
|
1394
1422
|
transferIdRef.current = transferId;
|
|
1423
|
+
tickCountRef.current = 0;
|
|
1424
|
+
runningRef.current = true;
|
|
1395
1425
|
setIsPolling(true);
|
|
1396
1426
|
setError(null);
|
|
1397
1427
|
poll();
|
|
1398
|
-
|
|
1428
|
+
scheduleNext();
|
|
1399
1429
|
},
|
|
1400
|
-
[poll,
|
|
1430
|
+
[poll, scheduleNext, stopPolling]
|
|
1401
1431
|
);
|
|
1402
1432
|
useEffect(() => () => stopPolling(), [stopPolling]);
|
|
1403
1433
|
return { transfer, error, isPolling, startPolling, stopPolling };
|
|
@@ -1650,7 +1680,7 @@ async function resolvePermit2BatchMetadata(options) {
|
|
|
1650
1680
|
approveAction = null,
|
|
1651
1681
|
signAction,
|
|
1652
1682
|
fetchAuthorizationSession: fetchAuthorizationSession2 = fetchAuthorizationSession,
|
|
1653
|
-
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1683
|
+
sleep: sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1654
1684
|
logPrefix = "[blink-sdk][permit2-batch]",
|
|
1655
1685
|
waitForAwaitingLimit = false
|
|
1656
1686
|
} = options;
|
|
@@ -1690,7 +1720,7 @@ async function resolvePermit2BatchMetadata(options) {
|
|
|
1690
1720
|
`SIGN_PERMIT2 metadata is incomplete for batch execution. Missing: ${missingFields.join(", ")}.`
|
|
1691
1721
|
);
|
|
1692
1722
|
}
|
|
1693
|
-
await
|
|
1723
|
+
await sleep2(BATCH_SIGN_PERMIT2_POLL_MS);
|
|
1694
1724
|
const refreshedSession = await fetchAuthorizationSession2(apiBaseUrl, sessionId);
|
|
1695
1725
|
const refreshedSignAction = refreshedSession.actions.find((action) => action.id === signAction.id);
|
|
1696
1726
|
if (!refreshedSignAction) {
|
|
@@ -1744,7 +1774,7 @@ function isTxSettlementTimeoutError(err) {
|
|
|
1744
1774
|
async function waitForTransactionReceipt(walletClient, txHash, logContext, options = {}) {
|
|
1745
1775
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
|
|
1746
1776
|
const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
|
|
1747
|
-
const
|
|
1777
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1748
1778
|
const info = options.logger?.info ?? ((message) => console.info(message));
|
|
1749
1779
|
const warn = options.logger?.warn ?? ((message) => console.warn(message));
|
|
1750
1780
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
@@ -1764,7 +1794,7 @@ async function waitForTransactionReceipt(walletClient, txHash, logContext, optio
|
|
|
1764
1794
|
`[blink-sdk][tx-receipt] eth_getTransactionReceipt errored; will retry. context=${logContext}, txHash=${txHash}, error=${err instanceof Error ? err.message : String(err)}`
|
|
1765
1795
|
);
|
|
1766
1796
|
}
|
|
1767
|
-
await
|
|
1797
|
+
await sleep2(pollIntervalMs);
|
|
1768
1798
|
}
|
|
1769
1799
|
const waitedMs = pollIntervalMs * maxAttempts;
|
|
1770
1800
|
if (options.strict) {
|
|
@@ -1807,7 +1837,7 @@ function parseNonce(raw) {
|
|
|
1807
1837
|
async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logContext, options = {}) {
|
|
1808
1838
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
|
|
1809
1839
|
const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
|
|
1810
|
-
const
|
|
1840
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1811
1841
|
const info = options.logger?.info ?? ((message) => console.info(message));
|
|
1812
1842
|
const warn = options.logger?.warn ?? ((message) => console.warn(message));
|
|
1813
1843
|
let lastObservedNonce = null;
|
|
@@ -1826,7 +1856,7 @@ async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logCo
|
|
|
1826
1856
|
`[blink-sdk][tx-nonce] eth_getTransactionCount errored; will retry. context=${logContext}, sender=${sender}, error=${err instanceof Error ? err.message : String(err)}`
|
|
1827
1857
|
);
|
|
1828
1858
|
}
|
|
1829
|
-
await
|
|
1859
|
+
await sleep2(pollIntervalMs);
|
|
1830
1860
|
}
|
|
1831
1861
|
const waitedMs = pollIntervalMs * maxAttempts;
|
|
1832
1862
|
throw new TxSettlementTimeoutError({
|
|
@@ -1849,7 +1879,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1849
1879
|
logContext,
|
|
1850
1880
|
pollIntervalMs,
|
|
1851
1881
|
maxAttempts,
|
|
1852
|
-
sleep,
|
|
1882
|
+
sleep: sleep2,
|
|
1853
1883
|
logger
|
|
1854
1884
|
} = params;
|
|
1855
1885
|
const expectedMinNonce = preSendNonce + 1;
|
|
@@ -1864,7 +1894,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1864
1894
|
await waitForTransactionReceipt(walletClient, txHash, logContext, {
|
|
1865
1895
|
pollIntervalMs: resolvedPollIntervalMs,
|
|
1866
1896
|
maxAttempts: resolvedMaxAttempts,
|
|
1867
|
-
sleep,
|
|
1897
|
+
sleep: sleep2,
|
|
1868
1898
|
logger: sharedLogger,
|
|
1869
1899
|
strict: true
|
|
1870
1900
|
});
|
|
@@ -1886,7 +1916,7 @@ async function waitForTransactionSettled(params) {
|
|
|
1886
1916
|
{
|
|
1887
1917
|
pollIntervalMs: resolvedPollIntervalMs,
|
|
1888
1918
|
maxAttempts: resolvedMaxAttempts,
|
|
1889
|
-
sleep,
|
|
1919
|
+
sleep: sleep2,
|
|
1890
1920
|
logger: sharedLogger
|
|
1891
1921
|
}
|
|
1892
1922
|
);
|
|
@@ -1924,13 +1954,13 @@ async function waitForWalletNonceAgreement(params) {
|
|
|
1924
1954
|
pollIntervalMs,
|
|
1925
1955
|
maxAttempts,
|
|
1926
1956
|
minWaitMs,
|
|
1927
|
-
sleep,
|
|
1957
|
+
sleep: sleep2,
|
|
1928
1958
|
logger
|
|
1929
1959
|
} = params;
|
|
1930
1960
|
const resolvedPollIntervalMs = pollIntervalMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_POLL_INTERVAL_MS;
|
|
1931
1961
|
const resolvedMaxAttempts = maxAttempts ?? DEFAULT_WALLET_NONCE_AGREEMENT_MAX_ATTEMPTS;
|
|
1932
1962
|
const resolvedMinWaitMs = minWaitMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_MIN_WAIT_MS;
|
|
1933
|
-
const resolvedSleep =
|
|
1963
|
+
const resolvedSleep = sleep2 ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1934
1964
|
const info = logger?.info ?? ((message) => console.info(message));
|
|
1935
1965
|
const warn = logger?.warn ?? ((message) => console.warn(message));
|
|
1936
1966
|
const startedAt = Date.now();
|
|
@@ -1992,6 +2022,7 @@ var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
|
1992
2022
|
var DEFAULT_MAX_ATTEMPTS = 60;
|
|
1993
2023
|
var DEFAULT_REQUEST_TIMEOUT_MS = 8e3;
|
|
1994
2024
|
var DEFAULT_MAX_CONSECUTIVE_ERRORS = 5;
|
|
2025
|
+
var DEFAULT_ERROR_GRACE_PERIOD_MS = 6e4;
|
|
1995
2026
|
var DEFAULT_LOG_EVERY_N_ATTEMPTS = 5;
|
|
1996
2027
|
function classifyCallsStatus(status) {
|
|
1997
2028
|
if (typeof status === "number") {
|
|
@@ -2013,15 +2044,18 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2013
2044
|
const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
2014
2045
|
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2015
2046
|
const maxConsecutiveErrors = options.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS;
|
|
2047
|
+
const errorGracePeriodMs = options.errorGracePeriodMs ?? DEFAULT_ERROR_GRACE_PERIOD_MS;
|
|
2016
2048
|
const logEveryN = Math.max(1, options.logEveryNAttempts ?? DEFAULT_LOG_EVERY_N_ATTEMPTS);
|
|
2017
|
-
const
|
|
2049
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2050
|
+
const now = options.now ?? (() => Date.now());
|
|
2018
2051
|
const info = options.logger?.info ?? ((m) => console.info(m));
|
|
2019
2052
|
const warn = options.logger?.warn ?? ((m) => console.warn(m));
|
|
2020
2053
|
const error = options.logger?.error ?? ((m) => console.error(m));
|
|
2054
|
+
const pollStartedAt = now();
|
|
2021
2055
|
let consecutiveErrors = 0;
|
|
2022
2056
|
let lastStatus;
|
|
2023
2057
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2024
|
-
await
|
|
2058
|
+
await sleep2(pollIntervalMs);
|
|
2025
2059
|
let status;
|
|
2026
2060
|
try {
|
|
2027
2061
|
status = await withTimeout(
|
|
@@ -2037,9 +2071,15 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
|
|
|
2037
2071
|
consecutiveErrors += 1;
|
|
2038
2072
|
const message = err instanceof Error ? err.message : String(err);
|
|
2039
2073
|
const isTimeout = err instanceof PromiseTimeoutError;
|
|
2074
|
+
const elapsedMs = now() - pollStartedAt;
|
|
2075
|
+
const inGracePeriod = elapsedMs < errorGracePeriodMs;
|
|
2040
2076
|
warn(
|
|
2041
|
-
`[blink-sdk][batch-wallet-calls] wallet_getCallsStatus errored. callsId=${callsId}, attempt=${attempt}, consecutiveErrors=${consecutiveErrors}, isTimeout=${isTimeout}, error=${message}`
|
|
2077
|
+
`[blink-sdk][batch-wallet-calls] wallet_getCallsStatus errored. callsId=${callsId}, attempt=${attempt}, consecutiveErrors=${consecutiveErrors}, isTimeout=${isTimeout}, inGracePeriod=${inGracePeriod}, elapsedMs=${elapsedMs}, error=${message}`
|
|
2042
2078
|
);
|
|
2079
|
+
if (inGracePeriod) {
|
|
2080
|
+
consecutiveErrors = 0;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2043
2083
|
if (consecutiveErrors >= maxConsecutiveErrors) {
|
|
2044
2084
|
error(
|
|
2045
2085
|
`[blink-sdk][batch-wallet-calls] Aborting poll after ${consecutiveErrors} consecutive errors. callsId=${callsId}, attempt=${attempt}, lastError=${message}`
|
|
@@ -2197,6 +2237,10 @@ function buildTargetMatchers(target) {
|
|
|
2197
2237
|
aliases.add("rabby");
|
|
2198
2238
|
aliases.add("io.rabby");
|
|
2199
2239
|
}
|
|
2240
|
+
if (value.includes("phantom")) {
|
|
2241
|
+
aliases.add("phantom");
|
|
2242
|
+
aliases.add("app.phantom");
|
|
2243
|
+
}
|
|
2200
2244
|
if (value.includes("injected")) {
|
|
2201
2245
|
aliases.add("injected");
|
|
2202
2246
|
}
|
|
@@ -2234,6 +2278,252 @@ function resolveWalletConnector(connectors, target) {
|
|
|
2234
2278
|
return metaMaskConnector ?? connectors[0];
|
|
2235
2279
|
}
|
|
2236
2280
|
|
|
2281
|
+
// src/solanaWalletRuntime.ts
|
|
2282
|
+
function asSigner(adapter) {
|
|
2283
|
+
return adapter;
|
|
2284
|
+
}
|
|
2285
|
+
var PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE = "PHANTOM_SOLANA_CONNECT_TIMEOUT";
|
|
2286
|
+
var APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE = "APPROVE_SPL_CONFIRMATION_TIMEOUT";
|
|
2287
|
+
var APPROVE_SPL_ONCHAIN_FAILURE_PREFIX = "Solana transaction failed:";
|
|
2288
|
+
var DEFAULT_CONFIRM_TIMEOUT_MS = 45e3;
|
|
2289
|
+
var DEFAULT_POLL_INTERVAL_MS2 = 1e3;
|
|
2290
|
+
var DEFAULT_COMMITMENT = "confirmed";
|
|
2291
|
+
var cachedAdapter = null;
|
|
2292
|
+
var cachedProviderKey = null;
|
|
2293
|
+
function providerKey(selection) {
|
|
2294
|
+
const providerName = selection.providerName?.trim();
|
|
2295
|
+
if (!providerName) {
|
|
2296
|
+
throw new Error("Solana wallet metadata is missing providerName.");
|
|
2297
|
+
}
|
|
2298
|
+
return `${selection.providerId ?? ""}:${providerName.toLowerCase()}`;
|
|
2299
|
+
}
|
|
2300
|
+
function assertSupportedProvider(selection) {
|
|
2301
|
+
const name = selection.providerName?.trim().toLowerCase();
|
|
2302
|
+
if (name !== "phantom") {
|
|
2303
|
+
throw new Error(`Unsupported Solana wallet provider: ${selection.providerName ?? "unknown"}.`);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
async function createAdapter(selection) {
|
|
2307
|
+
assertSupportedProvider(selection);
|
|
2308
|
+
const { PhantomWalletAdapter } = await import('@solana/wallet-adapter-phantom');
|
|
2309
|
+
return new PhantomWalletAdapter();
|
|
2310
|
+
}
|
|
2311
|
+
function readPublicKey(adapter) {
|
|
2312
|
+
const publicKey = adapter.publicKey?.toBase58();
|
|
2313
|
+
if (!publicKey) {
|
|
2314
|
+
throw new Error("Solana wallet did not return a public key.");
|
|
2315
|
+
}
|
|
2316
|
+
return publicKey;
|
|
2317
|
+
}
|
|
2318
|
+
async function connectSolanaWallet(selection, options) {
|
|
2319
|
+
const key = providerKey(selection);
|
|
2320
|
+
if (!cachedAdapter || cachedProviderKey !== key) {
|
|
2321
|
+
cachedAdapter = await createAdapter(selection);
|
|
2322
|
+
cachedProviderKey = key;
|
|
2323
|
+
}
|
|
2324
|
+
if (!cachedAdapter.connected) {
|
|
2325
|
+
const adapter = cachedAdapter;
|
|
2326
|
+
const connectPromise = adapter.connect();
|
|
2327
|
+
const timeoutMs = options?.timeoutMs;
|
|
2328
|
+
if (typeof timeoutMs === "number" && timeoutMs > 0) {
|
|
2329
|
+
let timeoutHandle = null;
|
|
2330
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2331
|
+
timeoutHandle = setTimeout(() => {
|
|
2332
|
+
reject(new Error(PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE));
|
|
2333
|
+
}, timeoutMs);
|
|
2334
|
+
});
|
|
2335
|
+
try {
|
|
2336
|
+
await Promise.race([connectPromise, timeoutPromise]);
|
|
2337
|
+
} finally {
|
|
2338
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2339
|
+
}
|
|
2340
|
+
} else {
|
|
2341
|
+
await connectPromise;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return {
|
|
2345
|
+
adapter: cachedAdapter,
|
|
2346
|
+
publicKey: readPublicKey(cachedAdapter)
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
async function signSolanaTransaction(selection, unsignedTxBase64, expectedOwnerPubkey) {
|
|
2350
|
+
const { adapter, publicKey } = await connectSolanaWallet(selection);
|
|
2351
|
+
if (publicKey !== expectedOwnerPubkey) {
|
|
2352
|
+
throw new Error(
|
|
2353
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
const signer = asSigner(adapter);
|
|
2357
|
+
if (typeof signer.signTransaction !== "function") {
|
|
2358
|
+
throw new Error("Selected Solana wallet does not support transaction signing.");
|
|
2359
|
+
}
|
|
2360
|
+
const transaction = await deserializeTransaction(unsignedTxBase64);
|
|
2361
|
+
const signedTransaction = await signer.signTransaction(transaction);
|
|
2362
|
+
return {
|
|
2363
|
+
ownerPubkey: publicKey,
|
|
2364
|
+
signedTxBase64: bytesToBase64(serializeTransaction(signedTransaction))
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
async function supportsSignAllSolanaTransactions(selection) {
|
|
2368
|
+
const { adapter } = await connectSolanaWallet(selection);
|
|
2369
|
+
return typeof asSigner(adapter).signAllTransactions === "function";
|
|
2370
|
+
}
|
|
2371
|
+
async function signAllSolanaTransactions(selection, unsignedTxsBase64, expectedOwnerPubkey) {
|
|
2372
|
+
if (!Array.isArray(unsignedTxsBase64) || unsignedTxsBase64.length === 0) {
|
|
2373
|
+
throw new Error("signAllSolanaTransactions requires at least one unsigned transaction.");
|
|
2374
|
+
}
|
|
2375
|
+
const { adapter, publicKey } = await connectSolanaWallet(selection);
|
|
2376
|
+
if (publicKey !== expectedOwnerPubkey) {
|
|
2377
|
+
throw new Error(
|
|
2378
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
const signer = asSigner(adapter);
|
|
2382
|
+
if (typeof signer.signAllTransactions !== "function") {
|
|
2383
|
+
throw new Error("Selected Solana wallet does not support signAllTransactions.");
|
|
2384
|
+
}
|
|
2385
|
+
const transactions = [];
|
|
2386
|
+
for (const txBase64 of unsignedTxsBase64) {
|
|
2387
|
+
transactions.push(await deserializeTransaction(txBase64));
|
|
2388
|
+
}
|
|
2389
|
+
const signed = await signer.signAllTransactions(transactions);
|
|
2390
|
+
if (!Array.isArray(signed) || signed.length !== transactions.length) {
|
|
2391
|
+
throw new Error(
|
|
2392
|
+
`signAllTransactions returned ${Array.isArray(signed) ? signed.length : "non-array"} transactions; expected ${transactions.length}.`
|
|
2393
|
+
);
|
|
2394
|
+
}
|
|
2395
|
+
return {
|
|
2396
|
+
ownerPubkey: publicKey,
|
|
2397
|
+
signedTxsBase64: signed.map((tx) => bytesToBase64(serializeTransaction(tx)))
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
async function deserializeTransaction(unsignedTxBase64) {
|
|
2401
|
+
const { Transaction, VersionedTransaction } = await import('@solana/web3.js');
|
|
2402
|
+
const bytes = base64ToBytes(unsignedTxBase64);
|
|
2403
|
+
try {
|
|
2404
|
+
return Transaction.from(bytes);
|
|
2405
|
+
} catch {
|
|
2406
|
+
return VersionedTransaction.deserialize(bytes);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
function serializeTransaction(transaction) {
|
|
2410
|
+
if ("version" in transaction) {
|
|
2411
|
+
return transaction.serialize();
|
|
2412
|
+
}
|
|
2413
|
+
return transaction.serialize({
|
|
2414
|
+
requireAllSignatures: false,
|
|
2415
|
+
verifySignatures: false
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
function base64ToBytes(value) {
|
|
2419
|
+
if (typeof globalThis.atob === "function") {
|
|
2420
|
+
const binary = globalThis.atob(value);
|
|
2421
|
+
const bytes = new Uint8Array(binary.length);
|
|
2422
|
+
for (let i = 0; i < binary.length; i++) {
|
|
2423
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2424
|
+
}
|
|
2425
|
+
return bytes;
|
|
2426
|
+
}
|
|
2427
|
+
const bufferCtor = globalThis.Buffer;
|
|
2428
|
+
if (bufferCtor) {
|
|
2429
|
+
return bufferCtor.from(value, "base64");
|
|
2430
|
+
}
|
|
2431
|
+
throw new Error("Base64 decoding is not available in this environment.");
|
|
2432
|
+
}
|
|
2433
|
+
function bytesToBase64(bytes) {
|
|
2434
|
+
if (typeof globalThis.btoa === "function") {
|
|
2435
|
+
let binary = "";
|
|
2436
|
+
for (const byte of bytes) {
|
|
2437
|
+
binary += String.fromCharCode(byte);
|
|
2438
|
+
}
|
|
2439
|
+
return globalThis.btoa(binary);
|
|
2440
|
+
}
|
|
2441
|
+
const bufferCtor = globalThis.Buffer;
|
|
2442
|
+
if (bufferCtor) {
|
|
2443
|
+
return bufferCtor.from(bytes).toString("base64");
|
|
2444
|
+
}
|
|
2445
|
+
throw new Error("Base64 encoding is not available in this environment.");
|
|
2446
|
+
}
|
|
2447
|
+
async function signAndSendApproveSplViaWallet(params) {
|
|
2448
|
+
const rpcUrl = params.rpcUrl?.trim();
|
|
2449
|
+
if (!rpcUrl) {
|
|
2450
|
+
throw new Error("signAndSendApproveSplViaWallet requires an rpcUrl.");
|
|
2451
|
+
}
|
|
2452
|
+
const unsignedTxBase64 = params.unsignedTxBase64?.trim();
|
|
2453
|
+
if (!unsignedTxBase64) {
|
|
2454
|
+
throw new Error("signAndSendApproveSplViaWallet requires unsignedTxBase64.");
|
|
2455
|
+
}
|
|
2456
|
+
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
2457
|
+
const { adapter, publicKey } = await connectSolanaWallet(params.selection);
|
|
2458
|
+
if (publicKey !== params.expectedOwnerPubkey) {
|
|
2459
|
+
throw new Error(
|
|
2460
|
+
`Connected Solana wallet ${publicKey} does not match the required source wallet ${params.expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
if (!adapter.sendTransaction) {
|
|
2464
|
+
throw new Error("Selected Solana wallet does not support sendTransaction.");
|
|
2465
|
+
}
|
|
2466
|
+
const transaction = await deserializeTransaction(unsignedTxBase64);
|
|
2467
|
+
const connection = await defaultConnectionFactory(rpcUrl, commitment);
|
|
2468
|
+
const signature = await adapter.sendTransaction(
|
|
2469
|
+
transaction,
|
|
2470
|
+
connection,
|
|
2471
|
+
{
|
|
2472
|
+
preflightCommitment: commitment,
|
|
2473
|
+
maxRetries: 3
|
|
2474
|
+
}
|
|
2475
|
+
);
|
|
2476
|
+
return { ownerPubkey: publicKey, signature };
|
|
2477
|
+
}
|
|
2478
|
+
async function confirmApproveSplViaPolling(params) {
|
|
2479
|
+
const rpcUrl = params.rpcUrl?.trim();
|
|
2480
|
+
if (!rpcUrl) {
|
|
2481
|
+
throw new Error("confirmApproveSplViaPolling requires an rpcUrl.");
|
|
2482
|
+
}
|
|
2483
|
+
const signature = params.signature?.trim();
|
|
2484
|
+
if (!signature) {
|
|
2485
|
+
throw new Error("confirmApproveSplViaPolling requires a signature.");
|
|
2486
|
+
}
|
|
2487
|
+
const confirmTimeoutMs = params.confirmTimeoutMs ?? DEFAULT_CONFIRM_TIMEOUT_MS;
|
|
2488
|
+
const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
2489
|
+
const commitment = params.commitment ?? DEFAULT_COMMITMENT;
|
|
2490
|
+
const connection = await defaultConnectionFactory(rpcUrl, commitment);
|
|
2491
|
+
const slot = await pollForConfirmation({
|
|
2492
|
+
connection,
|
|
2493
|
+
signature,
|
|
2494
|
+
confirmTimeoutMs,
|
|
2495
|
+
pollIntervalMs
|
|
2496
|
+
});
|
|
2497
|
+
return { signature, slot };
|
|
2498
|
+
}
|
|
2499
|
+
async function defaultConnectionFactory(rpcUrl, commitment) {
|
|
2500
|
+
const { Connection } = await import('@solana/web3.js');
|
|
2501
|
+
return new Connection(rpcUrl, commitment);
|
|
2502
|
+
}
|
|
2503
|
+
async function pollForConfirmation(args) {
|
|
2504
|
+
const { connection, signature, confirmTimeoutMs, pollIntervalMs } = args;
|
|
2505
|
+
const expiresAt = Date.now() + confirmTimeoutMs;
|
|
2506
|
+
while (Date.now() <= expiresAt) {
|
|
2507
|
+
const result = await connection.getSignatureStatuses(
|
|
2508
|
+
[signature],
|
|
2509
|
+
{ searchTransactionHistory: true }
|
|
2510
|
+
);
|
|
2511
|
+
const status = result.value[0];
|
|
2512
|
+
if (status?.err) {
|
|
2513
|
+
throw new Error(`${APPROVE_SPL_ONCHAIN_FAILURE_PREFIX} ${JSON.stringify(status.err)}`);
|
|
2514
|
+
}
|
|
2515
|
+
const confirmationStatus = status?.confirmationStatus;
|
|
2516
|
+
if (confirmationStatus === "confirmed" || confirmationStatus === "finalized") {
|
|
2517
|
+
return status?.slot;
|
|
2518
|
+
}
|
|
2519
|
+
await sleep(pollIntervalMs);
|
|
2520
|
+
}
|
|
2521
|
+
throw new Error(APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE);
|
|
2522
|
+
}
|
|
2523
|
+
function sleep(ms) {
|
|
2524
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2237
2527
|
// src/hooks/authorizationExecutor.ts
|
|
2238
2528
|
var WALLET_CLIENT_MAX_ATTEMPTS = 25;
|
|
2239
2529
|
var WALLET_CLIENT_POLL_MS = 400;
|
|
@@ -2245,6 +2535,7 @@ var EXECUTE_BRIDGE_TX_TIMEOUT_MS = 12e4;
|
|
|
2245
2535
|
var EXECUTE_BRIDGE_TX_TIMEOUT_MESSAGE = "EXECUTE_BRIDGE_TX_TIMEOUT";
|
|
2246
2536
|
var WALLET_PROMPT_TIMEOUT_MS = 9e4;
|
|
2247
2537
|
var WALLET_PROMPT_TIMEOUT_MESSAGE = "WALLET_PROMPT_TIMEOUT";
|
|
2538
|
+
var PHANTOM_SOLANA_CONNECT_TIMEOUT_MS = 8e3;
|
|
2248
2539
|
var BATCH_RECEIPT_POLL_INTERVAL_MS = 1500;
|
|
2249
2540
|
var BATCH_RECEIPT_MAX_ATTEMPTS = 20;
|
|
2250
2541
|
var BATCHABLE_ACTION_TYPES = /* @__PURE__ */ new Set(["APPROVE_PERMIT2", "SIGN_PERMIT2", "EXECUTE_BRIDGE"]);
|
|
@@ -2358,13 +2649,110 @@ function parseSignTypedDataPayload(typedData) {
|
|
|
2358
2649
|
};
|
|
2359
2650
|
}
|
|
2360
2651
|
function isBatchableAction(action) {
|
|
2652
|
+
if (action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm") {
|
|
2653
|
+
return false;
|
|
2654
|
+
}
|
|
2361
2655
|
return BATCHABLE_ACTION_TYPES.has(action.type);
|
|
2362
2656
|
}
|
|
2657
|
+
function isSvmCombinedFirstTransferPair(pending) {
|
|
2658
|
+
if (pending.length < 2) return false;
|
|
2659
|
+
const [first, second] = pending;
|
|
2660
|
+
if (first.type !== "APPROVE_SPL") return false;
|
|
2661
|
+
if (second.type !== "EXECUTE_BRIDGE") return false;
|
|
2662
|
+
if (second.metadata?.chainFamily !== "svm") return false;
|
|
2663
|
+
if (first.metadata?.awaitingLimit) return false;
|
|
2664
|
+
const approveTx = first.metadata?.unsignedApproveTxBase64;
|
|
2665
|
+
const bridgeTx = second.metadata?.unsignedTxBase64;
|
|
2666
|
+
if (typeof approveTx !== "string" || approveTx.trim() === "") return false;
|
|
2667
|
+
if (typeof bridgeTx !== "string" || bridgeTx.trim() === "") return false;
|
|
2668
|
+
const approveOwner = first.metadata?.ownerPubkey;
|
|
2669
|
+
const bridgeOwner = second.metadata?.ownerPubkey ?? second.metadata?.senderAddress;
|
|
2670
|
+
if (typeof approveOwner !== "string" || approveOwner.trim() === "") return false;
|
|
2671
|
+
if (typeof bridgeOwner !== "string" || bridgeOwner.trim() === "") return false;
|
|
2672
|
+
if (approveOwner !== bridgeOwner) return false;
|
|
2673
|
+
return true;
|
|
2674
|
+
}
|
|
2675
|
+
function getSolanaWalletSelection(action) {
|
|
2676
|
+
const providerName = action.metadata?.providerName;
|
|
2677
|
+
if (typeof providerName !== "string" || providerName.trim() === "") {
|
|
2678
|
+
throw new Error(`${action.type} metadata is missing providerName.`);
|
|
2679
|
+
}
|
|
2680
|
+
const providerId = action.metadata?.providerId;
|
|
2681
|
+
return {
|
|
2682
|
+
...typeof providerId === "string" && providerId.trim() !== "" ? { providerId } : {},
|
|
2683
|
+
providerName
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
function isPhantomEvmProviderAvailable() {
|
|
2687
|
+
if (typeof window === "undefined") return false;
|
|
2688
|
+
const phantom = window.phantom;
|
|
2689
|
+
return Boolean(phantom?.ethereum?.isPhantom);
|
|
2690
|
+
}
|
|
2691
|
+
async function phantomEvmHasAuthorizedAccount() {
|
|
2692
|
+
if (typeof window === "undefined") return false;
|
|
2693
|
+
const provider = window.phantom?.ethereum;
|
|
2694
|
+
if (!provider?.isPhantom) return false;
|
|
2695
|
+
try {
|
|
2696
|
+
const accounts = await provider.request({ method: "eth_accounts" });
|
|
2697
|
+
return Array.isArray(accounts) && accounts.length > 0;
|
|
2698
|
+
} catch {
|
|
2699
|
+
return false;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
async function attemptPhantomEvmConnect(connectors, connectAsync) {
|
|
2703
|
+
let connector = resolveWalletConnector(connectors, { providerName: "Phantom" });
|
|
2704
|
+
if (!connector) {
|
|
2705
|
+
const ethereum = typeof window !== "undefined" ? window.ethereum : void 0;
|
|
2706
|
+
if (ethereum?.isPhantom) {
|
|
2707
|
+
connector = resolveWalletConnector(connectors, { wagmiConnectorId: "injected" });
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
if (!connector) return null;
|
|
2711
|
+
const result = await connectAsync({ connector });
|
|
2712
|
+
const address = result.accounts[0];
|
|
2713
|
+
if (!address) return null;
|
|
2714
|
+
return { address, chainId: result.chainId };
|
|
2715
|
+
}
|
|
2363
2716
|
function getPendingActions(session, completedIds) {
|
|
2364
2717
|
return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
|
|
2365
2718
|
}
|
|
2366
2719
|
async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
|
|
2367
2720
|
try {
|
|
2721
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
2722
|
+
const selection = getSolanaWalletSelection(action);
|
|
2723
|
+
const evmProbed = isPhantomEvmProviderAvailable() && await phantomEvmHasAuthorizedAccount();
|
|
2724
|
+
const svmPromise = connectSolanaWallet(selection, {
|
|
2725
|
+
timeoutMs: evmProbed ? PHANTOM_SOLANA_CONNECT_TIMEOUT_MS : void 0
|
|
2726
|
+
});
|
|
2727
|
+
const evmPromise = evmProbed ? attemptPhantomEvmConnect(connectors, connectAsync) : Promise.resolve(null);
|
|
2728
|
+
const [svmSettled, evmSettled] = await Promise.allSettled([svmPromise, evmPromise]);
|
|
2729
|
+
const svmPublicKey = svmSettled.status === "fulfilled" ? svmSettled.value.publicKey : null;
|
|
2730
|
+
const evmAddress = evmSettled.status === "fulfilled" && evmSettled.value ? evmSettled.value.address : null;
|
|
2731
|
+
if (!svmPublicKey && !evmAddress) {
|
|
2732
|
+
const svmReason = svmSettled.status === "rejected" ? svmSettled.reason instanceof Error ? svmSettled.reason.message : String(svmSettled.reason) : "Failed to connect Phantom.";
|
|
2733
|
+
const friendlyMsg = svmReason === PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE ? "Phantom did not respond. Please try again." : svmReason;
|
|
2734
|
+
return actionError(
|
|
2735
|
+
action,
|
|
2736
|
+
isUserRejection(friendlyMsg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : friendlyMsg
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
if (evmAddress) {
|
|
2740
|
+
await refreshWalletCapabilities(wagmiConfig2, "open-provider:phantom-dual");
|
|
2741
|
+
}
|
|
2742
|
+
const messageParts = [];
|
|
2743
|
+
if (svmPublicKey) messageParts.push(`SVM: ${svmPublicKey}`);
|
|
2744
|
+
if (evmAddress) messageParts.push(`EVM: ${evmAddress}`);
|
|
2745
|
+
return actionSuccess(
|
|
2746
|
+
action,
|
|
2747
|
+
`Connected to ${selection.providerName}. ${messageParts.join(", ")}`,
|
|
2748
|
+
{
|
|
2749
|
+
accounts: svmPublicKey ? [svmPublicKey, ...evmAddress ? [evmAddress] : []] : evmAddress ? [evmAddress] : [],
|
|
2750
|
+
chainFamily: "svm",
|
|
2751
|
+
...svmPublicKey ? { userSolanaWalletPubkey: svmPublicKey } : {},
|
|
2752
|
+
...evmAddress ? { evmAddress } : {}
|
|
2753
|
+
}
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2368
2756
|
const account = getAccount(wagmiConfig2);
|
|
2369
2757
|
const targetId = action.metadata?.wagmiConnectorId;
|
|
2370
2758
|
const connector = resolveWalletConnector(connectors, { wagmiConnectorId: targetId });
|
|
@@ -2490,9 +2878,16 @@ async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsyn
|
|
|
2490
2878
|
{ accounts: [...result.accounts], chainId: hexChainId }
|
|
2491
2879
|
);
|
|
2492
2880
|
} catch (err) {
|
|
2881
|
+
const msg = err instanceof Error ? err.message : "Failed to connect wallet";
|
|
2882
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
2883
|
+
return actionError(
|
|
2884
|
+
action,
|
|
2885
|
+
isUserRejection(msg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : msg
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2493
2888
|
return actionError(
|
|
2494
2889
|
action,
|
|
2495
|
-
|
|
2890
|
+
msg
|
|
2496
2891
|
);
|
|
2497
2892
|
}
|
|
2498
2893
|
}
|
|
@@ -2570,6 +2965,334 @@ async function executeSwitchChain(action, wagmiConfig2, switchChainAsync) {
|
|
|
2570
2965
|
);
|
|
2571
2966
|
}
|
|
2572
2967
|
}
|
|
2968
|
+
async function executeApproveSplSolana(action, options) {
|
|
2969
|
+
try {
|
|
2970
|
+
const selection = getSolanaWalletSelection(action);
|
|
2971
|
+
const ownerPubkey = action.metadata?.ownerPubkey;
|
|
2972
|
+
const unsignedApproveTxBase64 = action.metadata?.unsignedApproveTxBase64;
|
|
2973
|
+
const tokenSymbol = action.metadata?.tokenSymbol;
|
|
2974
|
+
const clientRpcUrl = action.metadata?.clientRpcUrl;
|
|
2975
|
+
if (action.metadata?.awaitingLimit) {
|
|
2976
|
+
return actionError(
|
|
2977
|
+
action,
|
|
2978
|
+
"APPROVE_SPL action is still waiting for a One-Tap allowance amount."
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
2982
|
+
return actionError(action, "APPROVE_SPL metadata is missing ownerPubkey.");
|
|
2983
|
+
}
|
|
2984
|
+
if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
|
|
2985
|
+
return actionError(
|
|
2986
|
+
action,
|
|
2987
|
+
"APPROVE_SPL metadata is missing unsignedApproveTxBase64."
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
appendDebug("info", "APPROVE_SPL: entry", {
|
|
2991
|
+
actionId: action.id,
|
|
2992
|
+
ownerPubkey,
|
|
2993
|
+
tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
|
|
2994
|
+
});
|
|
2995
|
+
if (typeof clientRpcUrl === "string" && clientRpcUrl.trim() !== "") {
|
|
2996
|
+
const sendLabel = "APPROVE_SPL signAndSendApproveSplViaWallet";
|
|
2997
|
+
const sent = await withWatchdog(
|
|
2998
|
+
withTimeout(
|
|
2999
|
+
signAndSendApproveSplViaWallet({
|
|
3000
|
+
selection,
|
|
3001
|
+
unsignedTxBase64: unsignedApproveTxBase64,
|
|
3002
|
+
expectedOwnerPubkey: ownerPubkey,
|
|
3003
|
+
rpcUrl: clientRpcUrl
|
|
3004
|
+
}),
|
|
3005
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3006
|
+
sendLabel
|
|
3007
|
+
),
|
|
3008
|
+
sendLabel
|
|
3009
|
+
);
|
|
3010
|
+
appendDebug("info", "APPROVE_SPL: tx submitted via wallet", {
|
|
3011
|
+
actionId: action.id,
|
|
3012
|
+
signature: sent.signature
|
|
3013
|
+
});
|
|
3014
|
+
try {
|
|
3015
|
+
options?.onConfirming?.(action);
|
|
3016
|
+
} catch (callbackErr) {
|
|
3017
|
+
appendDebug("warn", "APPROVE_SPL: onConfirming callback threw", {
|
|
3018
|
+
actionId: action.id,
|
|
3019
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
const confirmLabel = "APPROVE_SPL confirmApproveSplViaPolling";
|
|
3023
|
+
let slot;
|
|
3024
|
+
try {
|
|
3025
|
+
const confirmation = await withWatchdog(
|
|
3026
|
+
confirmApproveSplViaPolling({
|
|
3027
|
+
rpcUrl: clientRpcUrl,
|
|
3028
|
+
signature: sent.signature
|
|
3029
|
+
}),
|
|
3030
|
+
confirmLabel
|
|
3031
|
+
);
|
|
3032
|
+
slot = confirmation.slot;
|
|
3033
|
+
appendDebug("info", "APPROVE_SPL: settled", {
|
|
3034
|
+
actionId: action.id,
|
|
3035
|
+
signature: sent.signature,
|
|
3036
|
+
slot
|
|
3037
|
+
});
|
|
3038
|
+
} catch (pollErr) {
|
|
3039
|
+
const pollMsg = pollErr instanceof Error ? pollErr.message : String(pollErr);
|
|
3040
|
+
if (pollMsg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE) {
|
|
3041
|
+
throw pollErr;
|
|
3042
|
+
}
|
|
3043
|
+
if (pollMsg.startsWith(APPROVE_SPL_ONCHAIN_FAILURE_PREFIX)) {
|
|
3044
|
+
throw pollErr;
|
|
3045
|
+
}
|
|
3046
|
+
appendDebug("warn", "APPROVE_SPL: confirmation poll failed; relying on server verification", {
|
|
3047
|
+
actionId: action.id,
|
|
3048
|
+
signature: sent.signature,
|
|
3049
|
+
error: pollMsg
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
return actionSuccess(
|
|
3053
|
+
action,
|
|
3054
|
+
`Confirmed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3055
|
+
{
|
|
3056
|
+
approveSignature: sent.signature
|
|
3057
|
+
}
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
const signLabel = "APPROVE_SPL signSolanaTransaction";
|
|
3061
|
+
const signed = await withWatchdog(
|
|
3062
|
+
withTimeout(
|
|
3063
|
+
signSolanaTransaction(
|
|
3064
|
+
selection,
|
|
3065
|
+
unsignedApproveTxBase64,
|
|
3066
|
+
ownerPubkey
|
|
3067
|
+
),
|
|
3068
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3069
|
+
signLabel
|
|
3070
|
+
),
|
|
3071
|
+
signLabel
|
|
3072
|
+
);
|
|
3073
|
+
appendDebug("info", "APPROVE_SPL: signed (legacy path)", {
|
|
3074
|
+
actionId: action.id
|
|
3075
|
+
});
|
|
3076
|
+
return actionSuccess(
|
|
3077
|
+
action,
|
|
3078
|
+
`Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3079
|
+
{ signedTxBase64: signed.signedTxBase64 }
|
|
3080
|
+
);
|
|
3081
|
+
} catch (err) {
|
|
3082
|
+
const msg = err instanceof Error ? err.message : "Failed to sign SPL approval";
|
|
3083
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3084
|
+
const confirmTimedOut = msg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE;
|
|
3085
|
+
appendDebug(timedOut || confirmTimedOut ? "warn" : "error", "APPROVE_SPL: threw", {
|
|
3086
|
+
actionId: action.id,
|
|
3087
|
+
error: msg,
|
|
3088
|
+
userRejected: isUserRejection(msg),
|
|
3089
|
+
timedOut,
|
|
3090
|
+
confirmTimedOut
|
|
3091
|
+
});
|
|
3092
|
+
if (timedOut) {
|
|
3093
|
+
return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
|
|
3094
|
+
}
|
|
3095
|
+
if (confirmTimedOut) {
|
|
3096
|
+
return actionError(
|
|
3097
|
+
action,
|
|
3098
|
+
"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."
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
return actionError(
|
|
3102
|
+
action,
|
|
3103
|
+
isUserRejection(msg) ? "You rejected the SPL approval transaction. Please approve the transaction in Phantom to continue." : msg
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
async function executeExecuteBridgeSolana(action) {
|
|
3108
|
+
try {
|
|
3109
|
+
const selection = getSolanaWalletSelection(action);
|
|
3110
|
+
const ownerPubkey = action.metadata?.ownerPubkey ?? action.metadata?.senderAddress;
|
|
3111
|
+
const unsignedTxBase64 = action.metadata?.unsignedTxBase64;
|
|
3112
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
3113
|
+
return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing ownerPubkey.");
|
|
3114
|
+
}
|
|
3115
|
+
if (typeof unsignedTxBase64 !== "string" || unsignedTxBase64.trim() === "") {
|
|
3116
|
+
return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64.");
|
|
3117
|
+
}
|
|
3118
|
+
appendDebug("info", "EXECUTE_BRIDGE: svm entry", {
|
|
3119
|
+
actionId: action.id,
|
|
3120
|
+
ownerPubkey
|
|
3121
|
+
});
|
|
3122
|
+
const label = "EXECUTE_BRIDGE signSolanaTransaction";
|
|
3123
|
+
const signed = await withWatchdog(
|
|
3124
|
+
withTimeout(
|
|
3125
|
+
signSolanaTransaction(
|
|
3126
|
+
selection,
|
|
3127
|
+
unsignedTxBase64,
|
|
3128
|
+
ownerPubkey
|
|
3129
|
+
),
|
|
3130
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3131
|
+
label
|
|
3132
|
+
),
|
|
3133
|
+
label
|
|
3134
|
+
);
|
|
3135
|
+
appendDebug("info", "EXECUTE_BRIDGE: svm signed", {
|
|
3136
|
+
actionId: action.id
|
|
3137
|
+
});
|
|
3138
|
+
return actionSuccess(
|
|
3139
|
+
action,
|
|
3140
|
+
"Signed Solana bridge transfer in Phantom.",
|
|
3141
|
+
{ signedTxBase64: signed.signedTxBase64, ownerPubkey: signed.ownerPubkey }
|
|
3142
|
+
);
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
const msg = err instanceof Error ? err.message : "Failed to sign Solana bridge transfer";
|
|
3145
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3146
|
+
appendDebug(timedOut ? "warn" : "error", "EXECUTE_BRIDGE: svm threw", {
|
|
3147
|
+
actionId: action.id,
|
|
3148
|
+
error: msg,
|
|
3149
|
+
userRejected: isUserRejection(msg),
|
|
3150
|
+
timedOut
|
|
3151
|
+
});
|
|
3152
|
+
if (timedOut) {
|
|
3153
|
+
return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
|
|
3154
|
+
}
|
|
3155
|
+
return actionError(
|
|
3156
|
+
action,
|
|
3157
|
+
isUserRejection(msg) ? "You rejected the Solana transfer transaction. Please approve the transaction in Phantom to continue." : msg
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
async function executeSvmCombinedFirstTransfer(actions, options) {
|
|
3162
|
+
if (actions.length !== 2) {
|
|
3163
|
+
throw new Error(
|
|
3164
|
+
`executeSvmCombinedFirstTransfer requires exactly 2 actions, received ${actions.length}.`
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
const approveAction = actions[0].action;
|
|
3168
|
+
const bridgeAction = actions[1].action;
|
|
3169
|
+
if (approveAction.type !== "APPROVE_SPL") {
|
|
3170
|
+
throw new Error(
|
|
3171
|
+
`executeSvmCombinedFirstTransfer expects APPROVE_SPL first, got ${approveAction.type}.`
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
if (bridgeAction.type !== "EXECUTE_BRIDGE" || bridgeAction.metadata?.chainFamily !== "svm") {
|
|
3175
|
+
throw new Error(
|
|
3176
|
+
`executeSvmCombinedFirstTransfer expects SVM EXECUTE_BRIDGE second, got ${bridgeAction.type}/${bridgeAction.metadata?.chainFamily ?? "unknown"}.`
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
const ownerPubkey = approveAction.metadata?.ownerPubkey;
|
|
3180
|
+
const unsignedApproveTxBase64 = approveAction.metadata?.unsignedApproveTxBase64;
|
|
3181
|
+
const tokenSymbol = approveAction.metadata?.tokenSymbol;
|
|
3182
|
+
const unsignedBridgeTxBase64 = bridgeAction.metadata?.unsignedTxBase64;
|
|
3183
|
+
if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
|
|
3184
|
+
return failBothActions(
|
|
3185
|
+
approveAction,
|
|
3186
|
+
bridgeAction,
|
|
3187
|
+
"APPROVE_SPL metadata is missing ownerPubkey."
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
|
|
3191
|
+
return failBothActions(
|
|
3192
|
+
approveAction,
|
|
3193
|
+
bridgeAction,
|
|
3194
|
+
"APPROVE_SPL metadata is missing unsignedApproveTxBase64."
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
if (typeof unsignedBridgeTxBase64 !== "string" || unsignedBridgeTxBase64.trim() === "") {
|
|
3198
|
+
return failBothActions(
|
|
3199
|
+
approveAction,
|
|
3200
|
+
bridgeAction,
|
|
3201
|
+
"SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64."
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
const selection = getSolanaWalletSelection(approveAction);
|
|
3205
|
+
appendDebug("info", "SVM_COMBINED: entry", {
|
|
3206
|
+
approveActionId: approveAction.id,
|
|
3207
|
+
bridgeActionId: bridgeAction.id,
|
|
3208
|
+
ownerPubkey,
|
|
3209
|
+
tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
|
|
3210
|
+
});
|
|
3211
|
+
const signLabel = "SVM_COMBINED signAllSolanaTransactions";
|
|
3212
|
+
let signed;
|
|
3213
|
+
try {
|
|
3214
|
+
signed = await withWatchdog(
|
|
3215
|
+
withTimeout(
|
|
3216
|
+
signAllSolanaTransactions(
|
|
3217
|
+
selection,
|
|
3218
|
+
[unsignedApproveTxBase64, unsignedBridgeTxBase64],
|
|
3219
|
+
ownerPubkey
|
|
3220
|
+
),
|
|
3221
|
+
WALLET_PROMPT_TIMEOUT_MS,
|
|
3222
|
+
signLabel
|
|
3223
|
+
),
|
|
3224
|
+
signLabel
|
|
3225
|
+
);
|
|
3226
|
+
} catch (err) {
|
|
3227
|
+
const msg = err instanceof Error ? err.message : "Failed to sign Solana transactions";
|
|
3228
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
3229
|
+
appendDebug(timedOut ? "warn" : "error", "SVM_COMBINED: signAllTransactions threw", {
|
|
3230
|
+
approveActionId: approveAction.id,
|
|
3231
|
+
bridgeActionId: bridgeAction.id,
|
|
3232
|
+
error: msg,
|
|
3233
|
+
userRejected: isUserRejection(msg),
|
|
3234
|
+
timedOut
|
|
3235
|
+
});
|
|
3236
|
+
if (timedOut) {
|
|
3237
|
+
return failBothActions(
|
|
3238
|
+
approveAction,
|
|
3239
|
+
bridgeAction,
|
|
3240
|
+
WALLET_PROMPT_TIMEOUT_MESSAGE
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
if (isUserRejection(msg)) {
|
|
3244
|
+
return failBothActions(
|
|
3245
|
+
approveAction,
|
|
3246
|
+
bridgeAction,
|
|
3247
|
+
"You rejected the Solana transactions. Please approve the prompt in Phantom to continue."
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
return failBothActions(approveAction, bridgeAction, msg);
|
|
3251
|
+
}
|
|
3252
|
+
const [approveSignedB64, bridgeSignedB64] = signed.signedTxsBase64;
|
|
3253
|
+
appendDebug("info", "SVM_COMBINED: signAllTransactions returned", {
|
|
3254
|
+
approveActionId: approveAction.id,
|
|
3255
|
+
bridgeActionId: bridgeAction.id
|
|
3256
|
+
});
|
|
3257
|
+
try {
|
|
3258
|
+
options?.onApproveSplConfirming?.(approveAction);
|
|
3259
|
+
} catch (callbackErr) {
|
|
3260
|
+
appendDebug("warn", "SVM_COMBINED: onApproveSplConfirming callback threw", {
|
|
3261
|
+
approveActionId: approveAction.id,
|
|
3262
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
return {
|
|
3266
|
+
txHash: void 0,
|
|
3267
|
+
actionResults: [
|
|
3268
|
+
{
|
|
3269
|
+
action: approveAction,
|
|
3270
|
+
result: actionSuccess(
|
|
3271
|
+
approveAction,
|
|
3272
|
+
`Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
|
|
3273
|
+
{ signedTxBase64: approveSignedB64, ownerPubkey: signed.ownerPubkey }
|
|
3274
|
+
)
|
|
3275
|
+
},
|
|
3276
|
+
{
|
|
3277
|
+
action: bridgeAction,
|
|
3278
|
+
result: actionSuccess(
|
|
3279
|
+
bridgeAction,
|
|
3280
|
+
"Signed Solana bridge transfer in Phantom.",
|
|
3281
|
+
{ signedTxBase64: bridgeSignedB64, ownerPubkey: signed.ownerPubkey }
|
|
3282
|
+
)
|
|
3283
|
+
}
|
|
3284
|
+
]
|
|
3285
|
+
};
|
|
3286
|
+
}
|
|
3287
|
+
function failBothActions(approveAction, bridgeAction, message) {
|
|
3288
|
+
return {
|
|
3289
|
+
txHash: void 0,
|
|
3290
|
+
actionResults: [
|
|
3291
|
+
{ action: approveAction, result: actionError(approveAction, message) },
|
|
3292
|
+
{ action: bridgeAction, result: actionError(bridgeAction, message) }
|
|
3293
|
+
]
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
2573
3296
|
async function executeApprovePermit2(action, wagmiConfig2) {
|
|
2574
3297
|
let walletClient = null;
|
|
2575
3298
|
let sender = null;
|
|
@@ -3215,6 +3938,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3215
3938
|
const [results, setResults] = useState([]);
|
|
3216
3939
|
const [error, setError] = useState(null);
|
|
3217
3940
|
const [currentAction, setCurrentAction] = useState(null);
|
|
3941
|
+
const [approveSplConfirming, setApproveSplConfirming] = useState(null);
|
|
3218
3942
|
const executingRef = useRef(false);
|
|
3219
3943
|
const walletCapabilitiesRef = useRef({});
|
|
3220
3944
|
const walletCapabilitiesRefreshedRef = useRef(false);
|
|
@@ -3253,16 +3977,17 @@ function useAuthorizationExecutor(options) {
|
|
|
3253
3977
|
}
|
|
3254
3978
|
setError(null);
|
|
3255
3979
|
setCurrentAction(null);
|
|
3980
|
+
setApproveSplConfirming(null);
|
|
3256
3981
|
setExecuting(false);
|
|
3257
3982
|
executingRef.current = false;
|
|
3258
3983
|
}, []);
|
|
3259
3984
|
const dispatchAction = useCallback(
|
|
3260
|
-
async (action) => {
|
|
3985
|
+
async (action, dispatchOptions) => {
|
|
3261
3986
|
setCurrentAction(action);
|
|
3262
3987
|
switch (action.type) {
|
|
3263
3988
|
case "OPEN_PROVIDER": {
|
|
3264
3989
|
const result = await executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
|
|
3265
|
-
if (result.status === "success") {
|
|
3990
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3266
3991
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3267
3992
|
wagmiConfig2,
|
|
3268
3993
|
"open-provider"
|
|
@@ -3274,7 +3999,7 @@ function useAuthorizationExecutor(options) {
|
|
|
3274
3999
|
return executeSelectSource(action, waitForSelection);
|
|
3275
4000
|
case "SWITCH_CHAIN": {
|
|
3276
4001
|
const result = await executeSwitchChain(action, wagmiConfig2, switchChainAsync);
|
|
3277
|
-
if (result.status === "success") {
|
|
4002
|
+
if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
|
|
3278
4003
|
walletCapabilitiesRef.current = await refreshWalletCapabilities(
|
|
3279
4004
|
wagmiConfig2,
|
|
3280
4005
|
"switch-chain"
|
|
@@ -3294,7 +4019,33 @@ function useAuthorizationExecutor(options) {
|
|
|
3294
4019
|
}
|
|
3295
4020
|
return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
|
|
3296
4021
|
}
|
|
4022
|
+
case "APPROVE_SPL": {
|
|
4023
|
+
if (action.metadata?.awaitingLimit) {
|
|
4024
|
+
throw new Error(
|
|
4025
|
+
"APPROVE_SPL action has awaitingLimit. The orchestrator must handle one-tap setup before executing this action."
|
|
4026
|
+
);
|
|
4027
|
+
}
|
|
4028
|
+
const externalConfirmingCallback = dispatchOptions?.onApproveSplConfirming;
|
|
4029
|
+
return executeApproveSplSolana(action, {
|
|
4030
|
+
onConfirming: (a) => {
|
|
4031
|
+
setApproveSplConfirming(a);
|
|
4032
|
+
if (externalConfirmingCallback) {
|
|
4033
|
+
try {
|
|
4034
|
+
externalConfirmingCallback(a);
|
|
4035
|
+
} catch (callbackErr) {
|
|
4036
|
+
appendDebug("warn", "APPROVE_SPL: orchestrator onApproveSplConfirming threw", {
|
|
4037
|
+
actionId: a.id,
|
|
4038
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
3297
4045
|
case "EXECUTE_BRIDGE":
|
|
4046
|
+
if (action.metadata?.chainFamily === "svm") {
|
|
4047
|
+
return executeExecuteBridgeSolana(action);
|
|
4048
|
+
}
|
|
3298
4049
|
return executeExecuteBridge(action, wagmiConfig2);
|
|
3299
4050
|
default:
|
|
3300
4051
|
return actionError(action, `Unsupported action type: ${action.type}`);
|
|
@@ -3307,7 +4058,11 @@ function useAuthorizationExecutor(options) {
|
|
|
3307
4058
|
if (options2?.sessionId) {
|
|
3308
4059
|
sessionIdRef.current = options2.sessionId;
|
|
3309
4060
|
}
|
|
3310
|
-
|
|
4061
|
+
try {
|
|
4062
|
+
return await dispatchAction(action, options2);
|
|
4063
|
+
} finally {
|
|
4064
|
+
setApproveSplConfirming(null);
|
|
4065
|
+
}
|
|
3311
4066
|
},
|
|
3312
4067
|
[dispatchAction]
|
|
3313
4068
|
);
|
|
@@ -3328,6 +4083,43 @@ function useAuthorizationExecutor(options) {
|
|
|
3328
4083
|
},
|
|
3329
4084
|
[apiBaseUrl, wagmiConfig2]
|
|
3330
4085
|
);
|
|
4086
|
+
const executeSvmCombinedFirstTransferImpl = useCallback(
|
|
4087
|
+
async (actions, options2) => {
|
|
4088
|
+
setCurrentAction(actions[0]?.action ?? null);
|
|
4089
|
+
const externalConfirmingCallback = options2?.onApproveSplConfirming;
|
|
4090
|
+
return executeSvmCombinedFirstTransfer(actions, {
|
|
4091
|
+
onApproveSplConfirming: (a) => {
|
|
4092
|
+
setApproveSplConfirming(a);
|
|
4093
|
+
if (externalConfirmingCallback) {
|
|
4094
|
+
try {
|
|
4095
|
+
externalConfirmingCallback(a);
|
|
4096
|
+
} catch (callbackErr) {
|
|
4097
|
+
appendDebug("warn", "SVM_COMBINED: orchestrator onApproveSplConfirming threw", {
|
|
4098
|
+
actionId: a.id,
|
|
4099
|
+
error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
});
|
|
4105
|
+
},
|
|
4106
|
+
[]
|
|
4107
|
+
);
|
|
4108
|
+
const canSignAllSolanaTransactions = useCallback(
|
|
4109
|
+
async (action) => {
|
|
4110
|
+
try {
|
|
4111
|
+
const selection = getSolanaWalletSelection(action);
|
|
4112
|
+
return await supportsSignAllSolanaTransactions(selection);
|
|
4113
|
+
} catch (err) {
|
|
4114
|
+
appendDebug("warn", "SVM_COMBINED: signAllTransactions feature-detect failed", {
|
|
4115
|
+
actionId: action.id,
|
|
4116
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4117
|
+
});
|
|
4118
|
+
return false;
|
|
4119
|
+
}
|
|
4120
|
+
},
|
|
4121
|
+
[]
|
|
4122
|
+
);
|
|
3331
4123
|
const canBatch = useCallback(async () => {
|
|
3332
4124
|
const cacheKey = getBatchCapabilityCacheKey(wagmiConfig2);
|
|
3333
4125
|
const cachedDecision = batchCapabilityDecisionRef.current;
|
|
@@ -3371,11 +4163,13 @@ function useAuthorizationExecutor(options) {
|
|
|
3371
4163
|
setResults([]);
|
|
3372
4164
|
setError(null);
|
|
3373
4165
|
setBatchTxHash(null);
|
|
4166
|
+
setApproveSplConfirming(null);
|
|
3374
4167
|
return true;
|
|
3375
4168
|
}, []);
|
|
3376
4169
|
const endExecution = useCallback(() => {
|
|
3377
4170
|
sessionIdRef.current = null;
|
|
3378
4171
|
setCurrentAction(null);
|
|
4172
|
+
setApproveSplConfirming(null);
|
|
3379
4173
|
setExecuting(false);
|
|
3380
4174
|
executingRef.current = false;
|
|
3381
4175
|
}, []);
|
|
@@ -3468,12 +4262,15 @@ function useAuthorizationExecutor(options) {
|
|
|
3468
4262
|
results,
|
|
3469
4263
|
error,
|
|
3470
4264
|
currentAction,
|
|
4265
|
+
approveSplConfirming,
|
|
3471
4266
|
pendingSelectSource,
|
|
3472
4267
|
resolveSelectSource,
|
|
3473
4268
|
cancelPendingExecution,
|
|
3474
4269
|
batchTxHash,
|
|
3475
4270
|
executeAction,
|
|
3476
4271
|
executeBatch,
|
|
4272
|
+
executeSvmCombinedFirstTransfer: executeSvmCombinedFirstTransferImpl,
|
|
4273
|
+
canSignAllSolanaTransactions,
|
|
3477
4274
|
canBatch,
|
|
3478
4275
|
checkPaymasterSupport,
|
|
3479
4276
|
getCapabilitySnapshot,
|
|
@@ -3502,6 +4299,8 @@ function isWebAuthnPasskeyDismissalError(err) {
|
|
|
3502
4299
|
|
|
3503
4300
|
// src/hooks/useTransferSigning.ts
|
|
3504
4301
|
var TRANSFER_SIGN_MAX_POLLS = 60;
|
|
4302
|
+
var FAST_POLL_COUNT2 = 6;
|
|
4303
|
+
var FAST_POLL_INTERVAL_MS2 = 500;
|
|
3505
4304
|
function waitForDocumentFocus2(timeoutMs = 5e3, intervalMs = 100) {
|
|
3506
4305
|
return new Promise((resolve, reject) => {
|
|
3507
4306
|
if (typeof document === "undefined") {
|
|
@@ -3529,9 +4328,23 @@ function hexToBytes(hex) {
|
|
|
3529
4328
|
const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
|
|
3530
4329
|
return new Uint8Array(bytes);
|
|
3531
4330
|
}
|
|
4331
|
+
function base64ToBytes2(base64) {
|
|
4332
|
+
const binary = atob(base64);
|
|
4333
|
+
const bytes = new Uint8Array(binary.length);
|
|
4334
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
4335
|
+
bytes[i] = binary.charCodeAt(i);
|
|
4336
|
+
}
|
|
4337
|
+
return bytes;
|
|
4338
|
+
}
|
|
3532
4339
|
function toBase642(buffer) {
|
|
3533
4340
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
3534
4341
|
}
|
|
4342
|
+
function getSigningChallenge(payload) {
|
|
4343
|
+
if (payload.chainFamily === "svm") {
|
|
4344
|
+
return base64ToBytes2(payload.message);
|
|
4345
|
+
}
|
|
4346
|
+
return hexToBytes(payload.userOpHash);
|
|
4347
|
+
}
|
|
3535
4348
|
function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
3536
4349
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3537
4350
|
const apiBaseUrl = options?.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3548,7 +4361,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3548
4361
|
const [error, setError] = useState(null);
|
|
3549
4362
|
const [passkeyDismissed, setPasskeyDismissed] = useState(false);
|
|
3550
4363
|
const signTransfer2 = useCallback(
|
|
3551
|
-
async (transferId) => {
|
|
4364
|
+
async (transferId, opts) => {
|
|
3552
4365
|
setSigning(true);
|
|
3553
4366
|
setError(null);
|
|
3554
4367
|
setPasskeyDismissed(false);
|
|
@@ -3565,22 +4378,28 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3565
4378
|
throw new Error("Could not get access token");
|
|
3566
4379
|
}
|
|
3567
4380
|
let payload = null;
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
4381
|
+
if (opts?.knownSignPayload) {
|
|
4382
|
+
payload = opts.knownSignPayload;
|
|
4383
|
+
setSignPayload(payload);
|
|
4384
|
+
} else {
|
|
4385
|
+
for (let i = 0; i < TRANSFER_SIGN_MAX_POLLS; i++) {
|
|
4386
|
+
const transfer = await fetchTransfer(
|
|
4387
|
+
apiBaseUrl,
|
|
4388
|
+
token ?? "",
|
|
4389
|
+
transferId,
|
|
4390
|
+
authorizationSessionToken
|
|
4391
|
+
);
|
|
4392
|
+
if (transfer.signPayload) {
|
|
4393
|
+
payload = transfer.signPayload;
|
|
4394
|
+
setSignPayload(payload);
|
|
4395
|
+
break;
|
|
4396
|
+
}
|
|
4397
|
+
if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
|
|
4398
|
+
throw new Error(`Unexpected transfer status: ${transfer.status}`);
|
|
4399
|
+
}
|
|
4400
|
+
const intervalMs = i < FAST_POLL_COUNT2 ? Math.min(FAST_POLL_INTERVAL_MS2, pollIntervalMs) : pollIntervalMs;
|
|
4401
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
3582
4402
|
}
|
|
3583
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
3584
4403
|
}
|
|
3585
4404
|
if (!payload) {
|
|
3586
4405
|
throw new Error("Timed out waiting for sign payload. Please try again.");
|
|
@@ -3591,8 +4410,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3591
4410
|
"Sign payload is missing passkeyCredentialId. Cannot request passkey signing without it."
|
|
3592
4411
|
);
|
|
3593
4412
|
}
|
|
3594
|
-
const
|
|
3595
|
-
let signedUserOp;
|
|
4413
|
+
const challengeBytes = getSigningChallenge(payload);
|
|
3596
4414
|
const allowCredentials = [{
|
|
3597
4415
|
type: "public-key",
|
|
3598
4416
|
id: credentialIdBase64ToBytes(passkeyCredentialId)
|
|
@@ -3611,7 +4429,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3611
4429
|
try {
|
|
3612
4430
|
assertion = await navigator.credentials.get({
|
|
3613
4431
|
publicKey: {
|
|
3614
|
-
challenge:
|
|
4432
|
+
challenge: challengeBytes,
|
|
3615
4433
|
rpId: signingRpId,
|
|
3616
4434
|
allowCredentials,
|
|
3617
4435
|
userVerification: "required",
|
|
@@ -3628,18 +4446,24 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
|
|
|
3628
4446
|
throw new TransferSigningPasskeyDismissedError();
|
|
3629
4447
|
}
|
|
3630
4448
|
const response = assertion.response;
|
|
3631
|
-
|
|
3632
|
-
...payload.userOp,
|
|
4449
|
+
const webauthnAssertion = {
|
|
3633
4450
|
credentialId: toBase642(assertion.rawId),
|
|
3634
4451
|
signature: toBase642(response.signature),
|
|
3635
4452
|
authenticatorData: toBase642(response.authenticatorData),
|
|
3636
4453
|
clientDataJSON: toBase642(response.clientDataJSON)
|
|
3637
4454
|
};
|
|
4455
|
+
const signedTransfer = payload.chainFamily === "svm" ? {
|
|
4456
|
+
chainFamily: "svm",
|
|
4457
|
+
webauthnAssertion
|
|
4458
|
+
} : {
|
|
4459
|
+
...payload.userOp,
|
|
4460
|
+
...webauthnAssertion
|
|
4461
|
+
};
|
|
3638
4462
|
return await signTransfer(
|
|
3639
4463
|
apiBaseUrl,
|
|
3640
4464
|
token ?? "",
|
|
3641
4465
|
transferId,
|
|
3642
|
-
|
|
4466
|
+
signedTransfer,
|
|
3643
4467
|
authorizationSessionToken
|
|
3644
4468
|
);
|
|
3645
4469
|
} catch (err) {
|
|
@@ -3754,6 +4578,8 @@ function assertLinkedTransferBridgeExecuted(params) {
|
|
|
3754
4578
|
// src/hooks/useAuthorizationOrchestrator.ts
|
|
3755
4579
|
var ACTION_POLL_INTERVAL_MS2 = 500;
|
|
3756
4580
|
var ACTION_POLL_MAX_RETRIES2 = 20;
|
|
4581
|
+
var REPORT_COMPLETION_TIMEOUT_MS = 9e4;
|
|
4582
|
+
var REPORT_COMPLETION_TIMEOUT_MESSAGE = "REPORT_COMPLETION_TIMEOUT";
|
|
3757
4583
|
function useAuthorizationOrchestrator(deps) {
|
|
3758
4584
|
const blinkConfig = useOptionalBlinkConfig();
|
|
3759
4585
|
const resolvedApiBaseUrl = deps.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
|
|
@@ -3881,6 +4707,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3881
4707
|
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
3882
4708
|
});
|
|
3883
4709
|
let cachedBatchSupport = null;
|
|
4710
|
+
let cachedSvmSignAllSupport = null;
|
|
3884
4711
|
while (mergedPending.length > 0) {
|
|
3885
4712
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
3886
4713
|
mergedPending = getMergedPending(sessions, completedIds);
|
|
@@ -3968,6 +4795,112 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3968
4795
|
});
|
|
3969
4796
|
continue;
|
|
3970
4797
|
}
|
|
4798
|
+
if (action.type === "APPROVE_SPL" && (action.metadata?.awaitingLimit || options?.alwaysPauseForOneTap) && !oneTapCompletedActionIds.has(action.id)) {
|
|
4799
|
+
if (options?.onAwaitingOneTap) {
|
|
4800
|
+
console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup via callback.", {
|
|
4801
|
+
actionId: action.id,
|
|
4802
|
+
ownerSessionId
|
|
4803
|
+
});
|
|
4804
|
+
await options.onAwaitingOneTap(action);
|
|
4805
|
+
console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed via callback.", {
|
|
4806
|
+
actionId: action.id,
|
|
4807
|
+
ownerSessionId
|
|
4808
|
+
});
|
|
4809
|
+
} else {
|
|
4810
|
+
console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup.", {
|
|
4811
|
+
actionId: action.id,
|
|
4812
|
+
ownerSessionId
|
|
4813
|
+
});
|
|
4814
|
+
await waitForOneTapPause(action);
|
|
4815
|
+
console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed.", {
|
|
4816
|
+
actionId: action.id,
|
|
4817
|
+
ownerSessionId
|
|
4818
|
+
});
|
|
4819
|
+
}
|
|
4820
|
+
oneTapCompletedActionIds.add(action.id);
|
|
4821
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4822
|
+
await refreshAllSessions(apiBaseUrl, sessions, actionSessionMap);
|
|
4823
|
+
queueLinkedTransferSessions({
|
|
4824
|
+
sessions,
|
|
4825
|
+
linkedTransferSessionIds,
|
|
4826
|
+
pendingSessionIdsRef
|
|
4827
|
+
});
|
|
4828
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4829
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4830
|
+
mergedPending = await waitForPendingActions({
|
|
4831
|
+
apiBaseUrl,
|
|
4832
|
+
sessions,
|
|
4833
|
+
completedIds,
|
|
4834
|
+
actionSessionMap,
|
|
4835
|
+
pendingSessionIdsRef,
|
|
4836
|
+
linkedTransferSessionIds,
|
|
4837
|
+
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
4838
|
+
});
|
|
4839
|
+
continue;
|
|
4840
|
+
}
|
|
4841
|
+
if (isSvmCombinedFirstTransferPair(mergedPending)) {
|
|
4842
|
+
const pair = mergedPending.slice(0, 2);
|
|
4843
|
+
const [svmApproveAction, svmBridgeAction] = pair;
|
|
4844
|
+
if (cachedSvmSignAllSupport == null) {
|
|
4845
|
+
cachedSvmSignAllSupport = await authExecutor.canSignAllSolanaTransactions(
|
|
4846
|
+
svmApproveAction
|
|
4847
|
+
);
|
|
4848
|
+
}
|
|
4849
|
+
appendDebug("info", "orchestrator:svm-combined evaluation", {
|
|
4850
|
+
approveActionId: svmApproveAction.id,
|
|
4851
|
+
bridgeActionId: svmBridgeAction.id,
|
|
4852
|
+
ownerSessionId,
|
|
4853
|
+
signAllSupported: cachedSvmSignAllSupport
|
|
4854
|
+
});
|
|
4855
|
+
if (cachedSvmSignAllSupport) {
|
|
4856
|
+
const svmInputs = pair.map((candidate) => ({
|
|
4857
|
+
action: candidate,
|
|
4858
|
+
sessionId: actionSessionMap.get(candidate.id) ?? sessionId
|
|
4859
|
+
}));
|
|
4860
|
+
const svmBatchResult = await authExecutor.executeSvmCombinedFirstTransfer(
|
|
4861
|
+
svmInputs,
|
|
4862
|
+
{
|
|
4863
|
+
onApproveSplConfirming: options?.onApproveSplConfirming
|
|
4864
|
+
}
|
|
4865
|
+
);
|
|
4866
|
+
const errorResult = svmBatchResult.actionResults.find(
|
|
4867
|
+
({ result: result2 }) => result2.status === "error"
|
|
4868
|
+
);
|
|
4869
|
+
if (errorResult) {
|
|
4870
|
+
authExecutor.addResult(errorResult.result);
|
|
4871
|
+
throw new Error(errorResult.result.message);
|
|
4872
|
+
}
|
|
4873
|
+
for (const { action: svmAction, result: result2 } of svmBatchResult.actionResults) {
|
|
4874
|
+
completedIds.add(svmAction.id);
|
|
4875
|
+
oneTapCompletedActionIds.delete(svmAction.id);
|
|
4876
|
+
authExecutor.addResult(result2);
|
|
4877
|
+
const svmOwnerSessionId = actionSessionMap.get(svmAction.id) ?? ownerSessionId;
|
|
4878
|
+
const reportedSession2 = await reportActionCompletionWithLogging(
|
|
4879
|
+
apiBaseUrl,
|
|
4880
|
+
svmAction,
|
|
4881
|
+
svmOwnerSessionId,
|
|
4882
|
+
result2
|
|
4883
|
+
);
|
|
4884
|
+
updateTrackedSession(sessions, svmOwnerSessionId, reportedSession2, actionSessionMap);
|
|
4885
|
+
}
|
|
4886
|
+
queueLinkedTransferSessions({
|
|
4887
|
+
sessions,
|
|
4888
|
+
linkedTransferSessionIds,
|
|
4889
|
+
pendingSessionIdsRef
|
|
4890
|
+
});
|
|
4891
|
+
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4892
|
+
mergedPending = await waitForPendingActions({
|
|
4893
|
+
apiBaseUrl,
|
|
4894
|
+
sessions,
|
|
4895
|
+
completedIds,
|
|
4896
|
+
actionSessionMap,
|
|
4897
|
+
pendingSessionIdsRef,
|
|
4898
|
+
linkedTransferSessionIds,
|
|
4899
|
+
waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
|
|
4900
|
+
});
|
|
4901
|
+
continue;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
3971
4904
|
if (isBatchableAction(action)) {
|
|
3972
4905
|
const batchable = getLeadingBatchableActions(
|
|
3973
4906
|
mergedPending
|
|
@@ -3994,7 +4927,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
3994
4927
|
pendingSessionIdsRef
|
|
3995
4928
|
});
|
|
3996
4929
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
3997
|
-
|
|
4930
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
3998
4931
|
mergedPending = await waitForPendingActions({
|
|
3999
4932
|
apiBaseUrl,
|
|
4000
4933
|
sessions,
|
|
@@ -4025,7 +4958,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4025
4958
|
pendingSessionIdsRef
|
|
4026
4959
|
});
|
|
4027
4960
|
await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
|
|
4028
|
-
|
|
4961
|
+
markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
|
|
4029
4962
|
mergedPending = await waitForPendingActions({
|
|
4030
4963
|
apiBaseUrl,
|
|
4031
4964
|
sessions,
|
|
@@ -4097,7 +5030,7 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4097
5030
|
continue;
|
|
4098
5031
|
}
|
|
4099
5032
|
}
|
|
4100
|
-
const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2";
|
|
5033
|
+
const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2" || action.type === "APPROVE_SPL";
|
|
4101
5034
|
if (isPrePromptProbableActionType && probeBeforePrompt && !preProbedIds.has(action.id)) {
|
|
4102
5035
|
preProbedIds.add(action.id);
|
|
4103
5036
|
appendDebug("info", `${action.type}: pre-prompt probe start`, {
|
|
@@ -4142,7 +5075,10 @@ function useAuthorizationOrchestrator(deps) {
|
|
|
4142
5075
|
actionId: action.id,
|
|
4143
5076
|
ownerSessionId
|
|
4144
5077
|
});
|
|
4145
|
-
const result = await authExecutor.executeAction(action, {
|
|
5078
|
+
const result = await authExecutor.executeAction(action, {
|
|
5079
|
+
sessionId: ownerSessionId,
|
|
5080
|
+
onApproveSplConfirming: options?.onApproveSplConfirming
|
|
5081
|
+
});
|
|
4146
5082
|
if (result.status === "success" && (action.type === "OPEN_PROVIDER" || action.type === "SWITCH_CHAIN")) {
|
|
4147
5083
|
cachedBatchSupport = null;
|
|
4148
5084
|
}
|
|
@@ -4382,7 +5318,24 @@ function getMergedPending(sessions, completedIds) {
|
|
|
4382
5318
|
for (const { session } of sessions) {
|
|
4383
5319
|
allActions.push(...getPendingActions(session, completedIds));
|
|
4384
5320
|
}
|
|
4385
|
-
|
|
5321
|
+
const hasPendingSvmSetupAction = allActions.some((action) => isSvmSetupAction(action));
|
|
5322
|
+
return allActions.sort((a, b) => {
|
|
5323
|
+
const leftIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(a);
|
|
5324
|
+
const rightIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(b);
|
|
5325
|
+
if (leftIsDeferredSvmBridge !== rightIsDeferredSvmBridge) {
|
|
5326
|
+
return leftIsDeferredSvmBridge ? 1 : -1;
|
|
5327
|
+
}
|
|
5328
|
+
return a.orderIndex - b.orderIndex;
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
function isSvmSetupAction(action) {
|
|
5332
|
+
if (action.type === "APPROVE_SPL") {
|
|
5333
|
+
return true;
|
|
5334
|
+
}
|
|
5335
|
+
return action.type === "OPEN_PROVIDER" && action.metadata?.chainFamily === "svm";
|
|
5336
|
+
}
|
|
5337
|
+
function isSvmTransferBridgeAction(action) {
|
|
5338
|
+
return action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm";
|
|
4386
5339
|
}
|
|
4387
5340
|
async function waitForPendingActions(params) {
|
|
4388
5341
|
const {
|
|
@@ -4456,14 +5409,19 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4456
5409
|
actionId: action.id,
|
|
4457
5410
|
ownerSessionId
|
|
4458
5411
|
});
|
|
5412
|
+
const label = `reportActionCompletion ${action.type}`;
|
|
4459
5413
|
try {
|
|
4460
5414
|
const reportedSession = await withWatchdog(
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
5415
|
+
withTimeout(
|
|
5416
|
+
reportActionCompletion(
|
|
5417
|
+
apiBaseUrl,
|
|
5418
|
+
action.id,
|
|
5419
|
+
result.data ?? {}
|
|
5420
|
+
),
|
|
5421
|
+
REPORT_COMPLETION_TIMEOUT_MS,
|
|
5422
|
+
label
|
|
4465
5423
|
),
|
|
4466
|
-
|
|
5424
|
+
label
|
|
4467
5425
|
);
|
|
4468
5426
|
console.info("[blink-sdk][orchestrator] Action completion reported.", {
|
|
4469
5427
|
actionId: action.id,
|
|
@@ -4479,16 +5437,22 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
|
|
|
4479
5437
|
});
|
|
4480
5438
|
return reportedSession;
|
|
4481
5439
|
} catch (err) {
|
|
5440
|
+
const timedOut = err instanceof PromiseTimeoutError;
|
|
4482
5441
|
console.warn("[blink-sdk][orchestrator] Failed to report action completion.", {
|
|
4483
5442
|
actionId: action.id,
|
|
4484
5443
|
actionType: action.type,
|
|
4485
5444
|
ownerSessionId,
|
|
5445
|
+
timedOut,
|
|
4486
5446
|
error: err instanceof Error ? err.message : String(err)
|
|
4487
5447
|
});
|
|
4488
|
-
appendDebug("error", `orchestrator:reportActionCompletion failed ${action.type}`, {
|
|
5448
|
+
appendDebug(timedOut ? "warn" : "error", `orchestrator:reportActionCompletion ${timedOut ? "timed out" : "failed"} ${action.type}`, {
|
|
4489
5449
|
actionId: action.id,
|
|
5450
|
+
timedOut,
|
|
4490
5451
|
error: err instanceof Error ? err.message : String(err)
|
|
4491
5452
|
});
|
|
5453
|
+
if (timedOut) {
|
|
5454
|
+
throw new Error(REPORT_COMPLETION_TIMEOUT_MESSAGE);
|
|
5455
|
+
}
|
|
4492
5456
|
throw err;
|
|
4493
5457
|
}
|
|
4494
5458
|
}
|
|
@@ -4521,10 +5485,10 @@ function updateTrackedSession(sessions, ownerSessionId, reportedSession, actionS
|
|
|
4521
5485
|
}
|
|
4522
5486
|
}
|
|
4523
5487
|
}
|
|
4524
|
-
function
|
|
5488
|
+
function markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds) {
|
|
4525
5489
|
for (const { session } of sessions) {
|
|
4526
5490
|
for (const action of session.actions) {
|
|
4527
|
-
if (action.type === "SIGN_PERMIT2" && action.status === "PENDING") {
|
|
5491
|
+
if ((action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL") && action.status === "PENDING") {
|
|
4528
5492
|
oneTapCompletedActionIds.add(action.id);
|
|
4529
5493
|
}
|
|
4530
5494
|
}
|
|
@@ -4577,6 +5541,8 @@ function screenForPhase(phase) {
|
|
|
4577
5541
|
case "completed":
|
|
4578
5542
|
case "failed":
|
|
4579
5543
|
return "success";
|
|
5544
|
+
case "amount-too-low":
|
|
5545
|
+
return "amount-too-low";
|
|
4580
5546
|
}
|
|
4581
5547
|
}
|
|
4582
5548
|
|
|
@@ -4626,6 +5592,12 @@ function hasActiveWallet(accounts) {
|
|
|
4626
5592
|
return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
|
|
4627
5593
|
}
|
|
4628
5594
|
function resolveTerminalPhase(state) {
|
|
5595
|
+
if (state.amountTooLow != null) {
|
|
5596
|
+
return {
|
|
5597
|
+
step: "amount-too-low",
|
|
5598
|
+
minAmountUsd: state.amountTooLow.minAmountUsd
|
|
5599
|
+
};
|
|
5600
|
+
}
|
|
4629
5601
|
const transferCompleted = state.transfer?.status === "COMPLETED";
|
|
4630
5602
|
const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
|
|
4631
5603
|
if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
|
|
@@ -4779,7 +5751,8 @@ function createInitialState(config) {
|
|
|
4779
5751
|
setupDepositToken: null,
|
|
4780
5752
|
setupDepositConfirmed: false,
|
|
4781
5753
|
guestWalletPrepared: null,
|
|
4782
|
-
guestWalletDeeplinksPreparing: false
|
|
5754
|
+
guestWalletDeeplinksPreparing: false,
|
|
5755
|
+
amountTooLow: null
|
|
4783
5756
|
};
|
|
4784
5757
|
}
|
|
4785
5758
|
function clearAuthenticatedSessionState(state) {
|
|
@@ -4816,7 +5789,8 @@ function clearAuthenticatedSessionState(state) {
|
|
|
4816
5789
|
setupDepositToken: null,
|
|
4817
5790
|
setupDepositConfirmed: false,
|
|
4818
5791
|
guestWalletPrepared: null,
|
|
4819
|
-
guestWalletDeeplinksPreparing: false
|
|
5792
|
+
guestWalletDeeplinksPreparing: false,
|
|
5793
|
+
amountTooLow: null
|
|
4820
5794
|
};
|
|
4821
5795
|
}
|
|
4822
5796
|
function paymentReducer(state, action) {
|
|
@@ -5155,6 +6129,13 @@ function applyAction(state, action) {
|
|
|
5155
6129
|
};
|
|
5156
6130
|
case "SET_ERROR":
|
|
5157
6131
|
return { ...state, error: action.error };
|
|
6132
|
+
case "AMOUNT_TOO_LOW":
|
|
6133
|
+
return {
|
|
6134
|
+
...state,
|
|
6135
|
+
amountTooLow: { minAmountUsd: action.minAmountUsd },
|
|
6136
|
+
creatingTransfer: false,
|
|
6137
|
+
error: null
|
|
6138
|
+
};
|
|
5158
6139
|
case "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP":
|
|
5159
6140
|
return { ...state, oneTapLimitSavedDuringSetup: action.saved };
|
|
5160
6141
|
case "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET":
|
|
@@ -5188,7 +6169,8 @@ function applyAction(state, action) {
|
|
|
5188
6169
|
setupDepositConfirmed: false,
|
|
5189
6170
|
setupFlowScreen: null,
|
|
5190
6171
|
guestWalletPrepared: null,
|
|
5191
|
-
guestWalletDeeplinksPreparing: false
|
|
6172
|
+
guestWalletDeeplinksPreparing: false,
|
|
6173
|
+
amountTooLow: null
|
|
5192
6174
|
};
|
|
5193
6175
|
case "LOGOUT":
|
|
5194
6176
|
return {
|
|
@@ -5535,6 +6517,17 @@ var RABBY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="70 88 386 334"
|
|
|
5535
6517
|
</linearGradient>
|
|
5536
6518
|
</defs>
|
|
5537
6519
|
</svg>`;
|
|
6520
|
+
var PHANTOM_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200" fill="none">
|
|
6521
|
+
<g clip-path="url(#clip0_2596_138580)">
|
|
6522
|
+
<rect y="-0.000976562" width="1200" height="1200" fill="#AB9FF2"/>
|
|
6523
|
+
<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"/>
|
|
6524
|
+
</g>
|
|
6525
|
+
<defs>
|
|
6526
|
+
<clipPath id="clip0_2596_138580">
|
|
6527
|
+
<rect y="-0.000976562" width="1200" height="1200" rx="600" fill="white"/>
|
|
6528
|
+
</clipPath>
|
|
6529
|
+
</defs>
|
|
6530
|
+
</svg>`;
|
|
5538
6531
|
var BLINK_SVG = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
5539
6532
|
<g clip-path="url(#clip0_125_480)">
|
|
5540
6533
|
<rect width="100" height="100" rx="18.5874" fill="black"/>
|
|
@@ -5577,11 +6570,13 @@ var POLYGON_CHAIN_LOGO = svgToDataUri(POLYGON_CHAIN_SVG);
|
|
|
5577
6570
|
var ETHEREUM_CHAIN_LOGO = "https://assets.relay.link/icons/1/light.png";
|
|
5578
6571
|
var MEGAETH_CHAIN_LOGO = svgToDataUri(MEGAETH_CHAIN_SVG);
|
|
5579
6572
|
var MONAD_CHAIN_LOGO = svgToDataUri(MONAD_CHAIN_SVG);
|
|
6573
|
+
var SOLANA_CHAIN_LOGO = "https://assets.relay.link/icons/792703809/light.png";
|
|
5580
6574
|
var USDM_LOGO = svgToDataUri(USDM_TOKEN_SVG);
|
|
5581
6575
|
var METAMASK_LOGO = svgToDataUri(METAMASK_SVG);
|
|
5582
6576
|
var TRUST_WALLET_LOGO = svgToDataUri(TRUST_WALLET_SVG);
|
|
5583
6577
|
var OKX_WALLET_LOGO = svgToDataUri(OKX_WALLET_SVG);
|
|
5584
6578
|
var RABBY_LOGO = svgToDataUri(RABBY_SVG);
|
|
6579
|
+
var PHANTOM_LOGO = svgToDataUri(PHANTOM_SVG);
|
|
5585
6580
|
var USDC_LOGO = svgToDataUri(USDC_SVG);
|
|
5586
6581
|
var TETHER_LOGO = svgToDataUri(TETHER_SVG);
|
|
5587
6582
|
var KNOWN_LOGOS = {
|
|
@@ -5590,6 +6585,7 @@ var KNOWN_LOGOS = {
|
|
|
5590
6585
|
"base account": BASE_LOGO,
|
|
5591
6586
|
"base app": BASE_LOGO,
|
|
5592
6587
|
"okx wallet": OKX_WALLET_LOGO,
|
|
6588
|
+
phantom: PHANTOM_LOGO,
|
|
5593
6589
|
rabby: RABBY_LOGO,
|
|
5594
6590
|
"trust wallet": TRUST_WALLET_LOGO
|
|
5595
6591
|
};
|
|
@@ -5602,15 +6598,20 @@ var TOKEN_LOGOS = {
|
|
|
5602
6598
|
var CHAIN_LOGOS = {
|
|
5603
6599
|
base: BASE_CHAIN_LOGO,
|
|
5604
6600
|
ethereum: ETHEREUM_CHAIN_LOGO,
|
|
6601
|
+
"ethereum mainnet": ETHEREUM_CHAIN_LOGO,
|
|
5605
6602
|
polygon: POLYGON_CHAIN_LOGO,
|
|
6603
|
+
"polygon mainnet": POLYGON_CHAIN_LOGO,
|
|
5606
6604
|
arbitrum: ARBITRUM_CHAIN_LOGO,
|
|
5607
6605
|
"arbitrum one": ARBITRUM_CHAIN_LOGO,
|
|
6606
|
+
"arbitrum mainnet": ARBITRUM_CHAIN_LOGO,
|
|
5608
6607
|
bnb: BNB_CHAIN_LOGO,
|
|
5609
6608
|
"bnb chain": BNB_CHAIN_LOGO,
|
|
5610
6609
|
"bnb smart chain": BNB_CHAIN_LOGO,
|
|
5611
6610
|
bsc: BNB_CHAIN_LOGO,
|
|
5612
6611
|
megaeth: MEGAETH_CHAIN_LOGO,
|
|
5613
|
-
monad: MONAD_CHAIN_LOGO
|
|
6612
|
+
monad: MONAD_CHAIN_LOGO,
|
|
6613
|
+
solana: SOLANA_CHAIN_LOGO,
|
|
6614
|
+
"solana mainnet": SOLANA_CHAIN_LOGO
|
|
5614
6615
|
};
|
|
5615
6616
|
function ScreenLayout({ children, footer }) {
|
|
5616
6617
|
const { tokens, theme } = useBlinkConfig();
|
|
@@ -5858,6 +6859,8 @@ function PrimaryButton({
|
|
|
5858
6859
|
children,
|
|
5859
6860
|
onClick,
|
|
5860
6861
|
href,
|
|
6862
|
+
target,
|
|
6863
|
+
rel,
|
|
5861
6864
|
disabled,
|
|
5862
6865
|
loading,
|
|
5863
6866
|
loadingText = "Please wait...",
|
|
@@ -5910,6 +6913,8 @@ function PrimaryButton({
|
|
|
5910
6913
|
"a",
|
|
5911
6914
|
{
|
|
5912
6915
|
href,
|
|
6916
|
+
target,
|
|
6917
|
+
rel,
|
|
5913
6918
|
onClick,
|
|
5914
6919
|
style: {
|
|
5915
6920
|
...buttonStyle2(tokens, { disabled: false, loading: false }),
|
|
@@ -6666,6 +7671,8 @@ function SourceRow(props) {
|
|
|
6666
7671
|
"a",
|
|
6667
7672
|
{
|
|
6668
7673
|
href: props.href,
|
|
7674
|
+
target: props.target,
|
|
7675
|
+
rel: props.rel,
|
|
6669
7676
|
onClick: props.onClick,
|
|
6670
7677
|
onMouseEnter: () => setHovered(true),
|
|
6671
7678
|
onMouseLeave: () => setHovered(false),
|
|
@@ -7189,6 +8196,22 @@ var closeButtonStyle2 = (tokens) => ({
|
|
|
7189
8196
|
cursor: "pointer",
|
|
7190
8197
|
padding: 0
|
|
7191
8198
|
});
|
|
8199
|
+
function AmountTooLowScreen({
|
|
8200
|
+
minAmountUsd,
|
|
8201
|
+
onRetry,
|
|
8202
|
+
onClose
|
|
8203
|
+
}) {
|
|
8204
|
+
return /* @__PURE__ */ jsx(
|
|
8205
|
+
BlinkErrorScreen,
|
|
8206
|
+
{
|
|
8207
|
+
title: "Amount too low",
|
|
8208
|
+
message: `The minimum payment amount is $${minAmountUsd.toFixed(2)}.`,
|
|
8209
|
+
retryLabel: onRetry ? "Try Again" : void 0,
|
|
8210
|
+
onRetry,
|
|
8211
|
+
onClose
|
|
8212
|
+
}
|
|
8213
|
+
);
|
|
8214
|
+
}
|
|
7192
8215
|
var RESEND_COOLDOWN_SECONDS = 30;
|
|
7193
8216
|
function OtpVerifyScreen({
|
|
7194
8217
|
maskedIdentifier,
|
|
@@ -7464,19 +8487,15 @@ var contentStyle5 = {
|
|
|
7464
8487
|
gap: 16
|
|
7465
8488
|
};
|
|
7466
8489
|
var illustrationFrameStyle = {
|
|
7467
|
-
flex:
|
|
7468
|
-
minHeight: 0,
|
|
8490
|
+
flex: "0 0 auto",
|
|
7469
8491
|
width: "100%",
|
|
7470
|
-
maxWidth: 329,
|
|
7471
8492
|
display: "flex",
|
|
7472
8493
|
alignItems: "center",
|
|
7473
8494
|
justifyContent: "center"
|
|
7474
8495
|
};
|
|
7475
8496
|
var illustrationImgStyle = {
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
width: "auto",
|
|
7479
|
-
height: "auto",
|
|
8497
|
+
width: 200,
|
|
8498
|
+
height: 200,
|
|
7480
8499
|
objectFit: "contain",
|
|
7481
8500
|
display: "block",
|
|
7482
8501
|
pointerEvents: "none",
|
|
@@ -7484,7 +8503,7 @@ var illustrationImgStyle = {
|
|
|
7484
8503
|
};
|
|
7485
8504
|
var footerButtonStyle = {
|
|
7486
8505
|
width: "100%",
|
|
7487
|
-
paddingTop:
|
|
8506
|
+
paddingTop: 48,
|
|
7488
8507
|
paddingBottom: 36
|
|
7489
8508
|
};
|
|
7490
8509
|
var errorBannerStyle3 = (tokens) => ({
|
|
@@ -7558,19 +8577,17 @@ var contentStyle6 = {
|
|
|
7558
8577
|
gap: 16
|
|
7559
8578
|
};
|
|
7560
8579
|
var illustrationFrameStyle2 = {
|
|
7561
|
-
flex: 1,
|
|
7562
|
-
minHeight: 0,
|
|
8580
|
+
flex: "0 1 auto",
|
|
7563
8581
|
width: "100%",
|
|
7564
|
-
maxWidth:
|
|
8582
|
+
maxWidth: 280,
|
|
8583
|
+
height: "clamp(140px, 34vh, 248px)",
|
|
7565
8584
|
display: "flex",
|
|
7566
8585
|
alignItems: "center",
|
|
7567
8586
|
justifyContent: "center"
|
|
7568
8587
|
};
|
|
7569
8588
|
var illustrationImgStyle2 = {
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
width: "auto",
|
|
7573
|
-
height: "auto",
|
|
8589
|
+
width: "100%",
|
|
8590
|
+
height: "100%",
|
|
7574
8591
|
objectFit: "contain",
|
|
7575
8592
|
display: "block",
|
|
7576
8593
|
pointerEvents: "none",
|
|
@@ -7714,6 +8731,36 @@ function triggerDeeplink(uri) {
|
|
|
7714
8731
|
}
|
|
7715
8732
|
}
|
|
7716
8733
|
|
|
8734
|
+
// src/walletDeeplinks.ts
|
|
8735
|
+
function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
|
|
8736
|
+
const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
|
|
8737
|
+
return matchedUri ?? fallbackUri;
|
|
8738
|
+
}
|
|
8739
|
+
function classifyWalletDeeplinkNavigation(uri) {
|
|
8740
|
+
if (isCustomSchemeUri(uri)) {
|
|
8741
|
+
return { navigationClass: "javascript" };
|
|
8742
|
+
}
|
|
8743
|
+
try {
|
|
8744
|
+
const parsed = new URL(uri);
|
|
8745
|
+
if (isBrowseUniversalLink(parsed)) {
|
|
8746
|
+
return {
|
|
8747
|
+
navigationClass: "iframe-escaping-native-anchor",
|
|
8748
|
+
anchorTarget: "_blank",
|
|
8749
|
+
anchorRel: "noopener noreferrer"
|
|
8750
|
+
};
|
|
8751
|
+
}
|
|
8752
|
+
} catch {
|
|
8753
|
+
return { navigationClass: "javascript" };
|
|
8754
|
+
}
|
|
8755
|
+
return { navigationClass: "javascript" };
|
|
8756
|
+
}
|
|
8757
|
+
function shouldOpenWithJavaScript(navigation) {
|
|
8758
|
+
return navigation.navigationClass === "javascript";
|
|
8759
|
+
}
|
|
8760
|
+
function isBrowseUniversalLink(parsed) {
|
|
8761
|
+
return parsed.protocol === "https:" && parsed.pathname.startsWith("/ul/browse/") && parsed.searchParams.has("ref");
|
|
8762
|
+
}
|
|
8763
|
+
|
|
7717
8764
|
// src/sentry.ts
|
|
7718
8765
|
var _mod;
|
|
7719
8766
|
function captureException(error) {
|
|
@@ -7788,14 +8835,20 @@ function WalletPickerScreen({
|
|
|
7788
8835
|
const rowLoader = isRowPreparing ? /* @__PURE__ */ jsx(Spinner, { size: 20 }) : void 0;
|
|
7789
8836
|
if (usesDirectLinkCards) {
|
|
7790
8837
|
if (directPreparedSession?.uri) {
|
|
8838
|
+
const navigation = classifyWalletDeeplinkNavigation(directPreparedSession.uri);
|
|
8839
|
+
const openWithJavaScript = shouldOpenWithJavaScript(navigation);
|
|
7791
8840
|
return /* @__PURE__ */ jsx(
|
|
7792
8841
|
SourceRow,
|
|
7793
8842
|
{
|
|
7794
8843
|
logo: logoSrc,
|
|
7795
8844
|
name: provider.name,
|
|
7796
8845
|
href: directPreparedSession.uri,
|
|
8846
|
+
target: navigation.anchorTarget,
|
|
8847
|
+
rel: navigation.anchorRel,
|
|
7797
8848
|
onClick: (e) => {
|
|
7798
|
-
|
|
8849
|
+
if (openWithJavaScript) {
|
|
8850
|
+
e.preventDefault();
|
|
8851
|
+
}
|
|
7799
8852
|
setSelectedProviderId(provider.id);
|
|
7800
8853
|
if (directPreparedSession.sessionId) {
|
|
7801
8854
|
void setAuthorizationSessionProvider(
|
|
@@ -7806,7 +8859,9 @@ function WalletPickerScreen({
|
|
|
7806
8859
|
captureException(err);
|
|
7807
8860
|
});
|
|
7808
8861
|
}
|
|
7809
|
-
|
|
8862
|
+
if (openWithJavaScript) {
|
|
8863
|
+
openDeeplink(directPreparedSession.uri);
|
|
8864
|
+
}
|
|
7810
8865
|
void onSelectProvider(provider.id, directPreparedSession);
|
|
7811
8866
|
}
|
|
7812
8867
|
},
|
|
@@ -8664,6 +9719,7 @@ function SelectDepositSourceScreen({
|
|
|
8664
9719
|
const rowAccountId = (opt) => opt.accountId ?? fallbackAccountId;
|
|
8665
9720
|
const isSelected = (opt) => !!selectedTokenSymbol && !!selectedChainName && !!selectedWalletId && opt.symbol === selectedTokenSymbol && opt.chainName === selectedChainName && opt.walletId === selectedWalletId;
|
|
8666
9721
|
const tokenOptionKey = (opt) => `${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`;
|
|
9722
|
+
const hasPendingMobileSelection = pendingMobileSelection != null;
|
|
8667
9723
|
const handleAuthorizedPick = (opt) => {
|
|
8668
9724
|
onPickToken(opt.symbol, opt.chainName, opt.walletId);
|
|
8669
9725
|
onDone();
|
|
@@ -8693,14 +9749,20 @@ function SelectDepositSourceScreen({
|
|
|
8693
9749
|
handleAuthorizedPick(opt);
|
|
8694
9750
|
};
|
|
8695
9751
|
const footerProviderName = preparedAuthorization?.providerName ?? pendingMobileSelection?.providerName ?? "your wallet";
|
|
9752
|
+
const preparedAuthorizationNavigation = preparedAuthorization ? classifyWalletDeeplinkNavigation(preparedAuthorization.deeplinkUri) : null;
|
|
9753
|
+
const preparedAuthorizationOpensWithJavaScript = preparedAuthorizationNavigation ? shouldOpenWithJavaScript(preparedAuthorizationNavigation) : false;
|
|
8696
9754
|
const footer = pendingMobileSelection ? preparedAuthorization ? /* @__PURE__ */ jsx(
|
|
8697
9755
|
PrimaryButton,
|
|
8698
9756
|
{
|
|
8699
9757
|
href: preparedAuthorization.deeplinkUri,
|
|
9758
|
+
target: preparedAuthorizationNavigation?.anchorTarget,
|
|
9759
|
+
rel: preparedAuthorizationNavigation?.anchorRel,
|
|
8700
9760
|
onClick: (event) => {
|
|
8701
|
-
event.preventDefault();
|
|
8702
9761
|
onCommitTokenAuthorization?.(preparedAuthorization);
|
|
8703
|
-
|
|
9762
|
+
if (preparedAuthorizationOpensWithJavaScript) {
|
|
9763
|
+
event.preventDefault();
|
|
9764
|
+
openDeeplink(preparedAuthorization.deeplinkUri);
|
|
9765
|
+
}
|
|
8704
9766
|
},
|
|
8705
9767
|
children: `Continue in ${footerProviderName}`
|
|
8706
9768
|
}
|
|
@@ -8750,7 +9812,7 @@ function SelectDepositSourceScreen({
|
|
|
8750
9812
|
symbol: opt.symbol,
|
|
8751
9813
|
chainName: opt.chainName,
|
|
8752
9814
|
balance: opt.balance,
|
|
8753
|
-
selected: isSelected(opt),
|
|
9815
|
+
selected: !hasPendingMobileSelection && isSelected(opt),
|
|
8754
9816
|
onClick: () => handleAuthorizedPick(opt)
|
|
8755
9817
|
},
|
|
8756
9818
|
`auth-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -8764,7 +9826,7 @@ function SelectDepositSourceScreen({
|
|
|
8764
9826
|
chainName: opt.chainName,
|
|
8765
9827
|
balance: opt.balance,
|
|
8766
9828
|
requiresAuth: true,
|
|
8767
|
-
selected:
|
|
9829
|
+
selected: hasPendingMobileSelection ? pendingMobileSelection?.key === tokenOptionKey(opt) : isSelected(opt),
|
|
8768
9830
|
onClick: () => handleRequiresAuthPick(account, opt)
|
|
8769
9831
|
},
|
|
8770
9832
|
`req-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
|
|
@@ -9949,11 +11011,13 @@ function OpenWalletScreen({
|
|
|
9949
11011
|
!loading && onRetryStatus != null && error == null,
|
|
9950
11012
|
deeplinkUri
|
|
9951
11013
|
);
|
|
11014
|
+
const deeplinkNavigation = classifyWalletDeeplinkNavigation(deeplinkUri);
|
|
11015
|
+
const openWithJavaScript = shouldOpenWithJavaScript(deeplinkNavigation);
|
|
9952
11016
|
useEffect(() => {
|
|
9953
|
-
if (!useDeeplink || loading || !deeplinkUri || autoOpenedRef.current === deeplinkUri) return;
|
|
11017
|
+
if (!useDeeplink || loading || !deeplinkUri || !openWithJavaScript || autoOpenedRef.current === deeplinkUri) return;
|
|
9954
11018
|
autoOpenedRef.current = deeplinkUri;
|
|
9955
11019
|
triggerDeeplink(deeplinkUri);
|
|
9956
|
-
}, [useDeeplink, loading, deeplinkUri]);
|
|
11020
|
+
}, [useDeeplink, loading, deeplinkUri, openWithJavaScript]);
|
|
9957
11021
|
const handleOpen = useCallback(() => {
|
|
9958
11022
|
openDeeplink(deeplinkUri);
|
|
9959
11023
|
}, [deeplinkUri]);
|
|
@@ -9993,7 +11057,10 @@ function OpenWalletScreen({
|
|
|
9993
11057
|
/* @__PURE__ */ jsxs(
|
|
9994
11058
|
PrimaryButton,
|
|
9995
11059
|
{
|
|
9996
|
-
|
|
11060
|
+
href: !openWithJavaScript ? deeplinkUri : void 0,
|
|
11061
|
+
target: deeplinkNavigation.anchorTarget,
|
|
11062
|
+
rel: deeplinkNavigation.anchorRel,
|
|
11063
|
+
onClick: openWithJavaScript ? handleOpen : void 0,
|
|
9997
11064
|
loading,
|
|
9998
11065
|
loadingText: "Preparing authorization\u2026",
|
|
9999
11066
|
children: [
|
|
@@ -10085,6 +11152,7 @@ var inlineWaitStyle = (color) => ({
|
|
|
10085
11152
|
});
|
|
10086
11153
|
function ConfirmSignScreen({
|
|
10087
11154
|
walletName,
|
|
11155
|
+
chainFamily,
|
|
10088
11156
|
signing,
|
|
10089
11157
|
error,
|
|
10090
11158
|
onSign,
|
|
@@ -10093,26 +11161,28 @@ function ConfirmSignScreen({
|
|
|
10093
11161
|
const { tokens } = useBlinkConfig();
|
|
10094
11162
|
const displayName = walletName ?? "your wallet";
|
|
10095
11163
|
const logoSrc = walletName ? KNOWN_LOGOS[walletName.toLowerCase()] : void 0;
|
|
11164
|
+
const isSvmTransfer = chainFamily === "svm";
|
|
11165
|
+
const heading = isSvmTransfer ? "Ready for passkey approval" : "Wallet authorized";
|
|
11166
|
+
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.`;
|
|
11167
|
+
const badge = isSvmTransfer ? "Wallet setup complete" : "Authorization complete";
|
|
11168
|
+
const hint = isSvmTransfer ? "No wallet pop-up is needed for this transfer" : "You may be prompted for biometric verification";
|
|
10096
11169
|
return /* @__PURE__ */ jsxs(
|
|
10097
11170
|
ScreenLayout,
|
|
10098
11171
|
{
|
|
10099
11172
|
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10100
11173
|
/* @__PURE__ */ jsx(PrimaryButton, { onClick: onSign, disabled: signing, children: "Confirm payment" }),
|
|
10101
11174
|
error && /* @__PURE__ */ jsx("p", { style: errorStyle2(tokens.textMuted), children: error }),
|
|
10102
|
-
!error && /* @__PURE__ */ jsx("p", { style: hintStyle(tokens.textMuted), children:
|
|
11175
|
+
!error && /* @__PURE__ */ jsx("p", { style: hintStyle(tokens.textMuted), children: hint })
|
|
10103
11176
|
] }),
|
|
10104
11177
|
children: [
|
|
10105
11178
|
/* @__PURE__ */ jsx(ScreenHeader, { onLogout }),
|
|
10106
11179
|
/* @__PURE__ */ jsxs("div", { style: contentStyle15, children: [
|
|
10107
11180
|
logoSrc ? /* @__PURE__ */ jsx("img", { src: logoSrc, alt: displayName, style: logoStyle3 }) : /* @__PURE__ */ jsx(Spinner, { size: 48 }),
|
|
10108
|
-
/* @__PURE__ */ jsx("h2", { style: headingStyle14(tokens.text), children:
|
|
10109
|
-
/* @__PURE__ */
|
|
10110
|
-
displayName,
|
|
10111
|
-
" approved the connection. Tap below to confirm your payment."
|
|
10112
|
-
] }),
|
|
11181
|
+
/* @__PURE__ */ jsx("h2", { style: headingStyle14(tokens.text), children: heading }),
|
|
11182
|
+
/* @__PURE__ */ jsx("p", { style: subtitleStyle11(tokens.textSecondary), children: subtitle }),
|
|
10113
11183
|
/* @__PURE__ */ jsxs("div", { style: successBadgeStyle(tokens), children: [
|
|
10114
11184
|
/* @__PURE__ */ jsx("span", { style: checkmarkStyle, children: "\u2713" }),
|
|
10115
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
11185
|
+
/* @__PURE__ */ jsx("span", { children: badge })
|
|
10116
11186
|
] })
|
|
10117
11187
|
] })
|
|
10118
11188
|
]
|
|
@@ -10855,6 +11925,7 @@ function buildConfirmSignScreenProps({
|
|
|
10855
11925
|
}) {
|
|
10856
11926
|
return {
|
|
10857
11927
|
walletName: flow.state.providers.find((p) => p.id === flow.state.selectedProviderId)?.name ?? null,
|
|
11928
|
+
chainFamily: remote.pollingTransfer?.signPayload?.chainFamily ?? null,
|
|
10858
11929
|
signing: remote.transferSigningSigning,
|
|
10859
11930
|
error: flow.state.error || remote.transferSigningError,
|
|
10860
11931
|
onSign: handlers.onConfirmSign,
|
|
@@ -11075,6 +12146,19 @@ function buildSuccessScreenProps({
|
|
|
11075
12146
|
onLogout: authenticated ? handlers.onLogout : void 0
|
|
11076
12147
|
};
|
|
11077
12148
|
}
|
|
12149
|
+
function buildAmountTooLowScreenProps({
|
|
12150
|
+
flow,
|
|
12151
|
+
handlers
|
|
12152
|
+
}) {
|
|
12153
|
+
const { state, onDismiss } = flow;
|
|
12154
|
+
const minAmountUsd = state.phase.step === "amount-too-low" ? state.phase.minAmountUsd : state.amountTooLow?.minAmountUsd ?? flow.minTransferAmountUsd;
|
|
12155
|
+
const canRetry = flow.depositAmount == null;
|
|
12156
|
+
return {
|
|
12157
|
+
minAmountUsd,
|
|
12158
|
+
onRetry: canRetry ? handlers.onNewPayment : void 0,
|
|
12159
|
+
onClose: onDismiss
|
|
12160
|
+
};
|
|
12161
|
+
}
|
|
11078
12162
|
function StepRenderer(props) {
|
|
11079
12163
|
const screen = screenForPhase(props.flow.state.phase);
|
|
11080
12164
|
return /* @__PURE__ */ jsx(StepRendererContent, { ...props, screen });
|
|
@@ -11142,6 +12226,8 @@ function StepRendererContent({
|
|
|
11142
12226
|
return /* @__PURE__ */ jsx(SelectSourceScreen, { ...buildSelectSourceScreenProps(input) });
|
|
11143
12227
|
case "success":
|
|
11144
12228
|
return /* @__PURE__ */ jsx(SuccessScreen, { ...buildSuccessScreenProps(input) });
|
|
12229
|
+
case "amount-too-low":
|
|
12230
|
+
return /* @__PURE__ */ jsx(AmountTooLowScreen, { ...buildAmountTooLowScreenProps(input) });
|
|
11145
12231
|
default: {
|
|
11146
12232
|
const _exhaustive = screen;
|
|
11147
12233
|
throw new Error(`Unhandled screen: ${_exhaustive}`);
|
|
@@ -11571,9 +12657,8 @@ function useTransferHandlers(deps) {
|
|
|
11571
12657
|
destination
|
|
11572
12658
|
]);
|
|
11573
12659
|
const handlePay = useCallback(async (payAmount, sourceOverrides) => {
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
dispatch({ type: "SET_ERROR", error: `Minimum amount is $${minUsd.toFixed(2)}.` });
|
|
12660
|
+
if (isNaN(payAmount) || payAmount < minTransferAmountUsd) {
|
|
12661
|
+
dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
|
|
11577
12662
|
return;
|
|
11578
12663
|
}
|
|
11579
12664
|
if (!sourceOverrides?.sourceId && !sourceId) {
|
|
@@ -11624,7 +12709,10 @@ function useTransferHandlers(deps) {
|
|
|
11624
12709
|
);
|
|
11625
12710
|
clearPendingTransferState();
|
|
11626
12711
|
dispatch({ type: "TRANSFER_CREATED", transfer: stagedTransfer });
|
|
11627
|
-
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12712
|
+
const signedTransfer2 = await transferSigning.signTransfer(
|
|
12713
|
+
stagedTransfer.id,
|
|
12714
|
+
{ knownSignPayload: stagedTransfer.signPayload }
|
|
12715
|
+
);
|
|
11628
12716
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
|
|
11629
12717
|
polling.startPolling(stagedTransfer.id);
|
|
11630
12718
|
return;
|
|
@@ -11655,7 +12743,10 @@ function useTransferHandlers(deps) {
|
|
|
11655
12743
|
polling.startPolling(t.id);
|
|
11656
12744
|
return;
|
|
11657
12745
|
}
|
|
11658
|
-
const signedTransfer = await transferSigning.signTransfer(
|
|
12746
|
+
const signedTransfer = await transferSigning.signTransfer(
|
|
12747
|
+
t.id,
|
|
12748
|
+
{ knownSignPayload: t.signPayload }
|
|
12749
|
+
);
|
|
11659
12750
|
dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
|
|
11660
12751
|
polling.startPolling(t.id);
|
|
11661
12752
|
} catch (err) {
|
|
@@ -11862,12 +12953,6 @@ function useMobileFlowHandlers(dispatch, polling, reloadAccounts, pollingTransfe
|
|
|
11862
12953
|
};
|
|
11863
12954
|
}
|
|
11864
12955
|
|
|
11865
|
-
// src/walletDeeplinks.ts
|
|
11866
|
-
function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
|
|
11867
|
-
const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
|
|
11868
|
-
return matchedUri ?? fallbackUri;
|
|
11869
|
-
}
|
|
11870
|
-
|
|
11871
12956
|
// src/hooks/providerSelectionGuards.ts
|
|
11872
12957
|
function resolveSetupFlowDepositAmount({
|
|
11873
12958
|
hasActiveWallet: hasActiveWallet2,
|
|
@@ -11934,9 +13019,10 @@ function buildDesktopDirectDuringSetupRunOptions() {
|
|
|
11934
13019
|
alwaysPauseForOneTap: true
|
|
11935
13020
|
};
|
|
11936
13021
|
}
|
|
11937
|
-
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol) {
|
|
13022
|
+
function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol, _chainFamily = "evm") {
|
|
13023
|
+
const options = buildDesktopDirectDuringSetupRunOptions();
|
|
11938
13024
|
return {
|
|
11939
|
-
...
|
|
13025
|
+
...options,
|
|
11940
13026
|
// Token authorization should batch like the first-time desktop setup flow:
|
|
11941
13027
|
// auto-resolve SELECT_SOURCE now, then inject the transfer session during one-tap setup.
|
|
11942
13028
|
autoResolveSource: { chainName, tokenSymbol }
|
|
@@ -12605,10 +13691,10 @@ function useProviderHandlers(deps) {
|
|
|
12605
13691
|
dispatch({ type: "SET_ERROR", error: null });
|
|
12606
13692
|
dispatch({ type: "SET_INCREASING_LIMIT", value: true });
|
|
12607
13693
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: depositAmount ?? 5 });
|
|
12608
|
-
let
|
|
13694
|
+
let desktopChain = null;
|
|
12609
13695
|
if (!isMobile) {
|
|
12610
|
-
|
|
12611
|
-
if (!
|
|
13696
|
+
desktopChain = chains.find((chain) => chain.commonId === chainId) ?? null;
|
|
13697
|
+
if (!desktopChain) {
|
|
12612
13698
|
dispatch({ type: "SET_INCREASING_LIMIT", value: false });
|
|
12613
13699
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
12614
13700
|
dispatch({ type: "SET_ERROR", error: `No chain found for chainId ${chainId}` });
|
|
@@ -12617,7 +13703,7 @@ function useProviderHandlers(deps) {
|
|
|
12617
13703
|
dispatch({
|
|
12618
13704
|
type: "SET_SETUP_DEPOSIT_TOKEN",
|
|
12619
13705
|
symbol: tokenSymbol,
|
|
12620
|
-
chainName:
|
|
13706
|
+
chainName: desktopChain.name,
|
|
12621
13707
|
walletId: _walletId,
|
|
12622
13708
|
tokenAddress,
|
|
12623
13709
|
chainId
|
|
@@ -12651,7 +13737,11 @@ function useProviderHandlers(deps) {
|
|
|
12651
13737
|
dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
|
|
12652
13738
|
const result = await orchestrator.run(
|
|
12653
13739
|
session.id,
|
|
12654
|
-
buildDesktopTokenAuthorizationRunOptions(
|
|
13740
|
+
buildDesktopTokenAuthorizationRunOptions(
|
|
13741
|
+
desktopChain.name,
|
|
13742
|
+
tokenSymbol,
|
|
13743
|
+
desktopChain.chainFamily
|
|
13744
|
+
)
|
|
12655
13745
|
);
|
|
12656
13746
|
if (result.status === "cancelled") {
|
|
12657
13747
|
dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
|
|
@@ -14057,6 +15147,11 @@ function BlinkPaymentInner({
|
|
|
14057
15147
|
dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
|
|
14058
15148
|
}
|
|
14059
15149
|
}, [depositAmount, dispatch]);
|
|
15150
|
+
useEffect(() => {
|
|
15151
|
+
if (depositAmount != null && depositAmount < minTransferAmountUsd) {
|
|
15152
|
+
dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
|
|
15153
|
+
}
|
|
15154
|
+
}, [depositAmount, minTransferAmountUsd, dispatch]);
|
|
14060
15155
|
useEffect(() => {
|
|
14061
15156
|
if (!ready || effectiveAuthenticated) return;
|
|
14062
15157
|
clearLocalSessionArtifacts();
|