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