@swype-org/react-sdk 0.2.174 → 0.2.185

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Buffer } from 'buffer';
1
2
  import { createContext, forwardRef, useContext, useRef, useState, useCallback, useMemo, useEffect, useSyncExternalStore, useReducer, Component } from 'react';
2
3
  import { PrivyProvider, usePrivy, useLoginWithPasskey, useSignupWithPasskey } from '@privy-io/react-auth';
3
4
  import { createConfig, http, WagmiProvider, useConfig, useConnect, useSwitchChain } from 'wagmi';
@@ -14,6 +15,10 @@ var __export = (target, all) => {
14
15
  for (var name in all)
15
16
  __defProp(target, name, { get: all[name], enumerable: true });
16
17
  };
18
+ var g = globalThis;
19
+ if (typeof g.Buffer === "undefined") {
20
+ g.Buffer = Buffer;
21
+ }
17
22
 
18
23
  // src/theme.ts
19
24
  var darkTheme = {
@@ -1198,7 +1203,7 @@ async function fetchTransfer(apiBaseUrl, token, transferId, authorizationSession
1198
1203
  if (!res.ok) await throwApiError(res);
1199
1204
  return await res.json();
1200
1205
  }
1201
- async function signTransfer(apiBaseUrl, token, transferId, signedUserOp, authorizationSessionToken) {
1206
+ async function signTransfer(apiBaseUrl, token, transferId, signedTransfer, authorizationSessionToken) {
1202
1207
  if (!token && !authorizationSessionToken) {
1203
1208
  throw new Error("Missing auth credentials for transfer signing.");
1204
1209
  }
@@ -1209,7 +1214,9 @@ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp, authori
1209
1214
  ...token ? { Authorization: `Bearer ${token}` } : {},
1210
1215
  ...authorizationSessionToken ? { "x-authorization-session-token": authorizationSessionToken } : {}
1211
1216
  },
1212
- body: JSON.stringify({ signedUserOp })
1217
+ body: JSON.stringify(
1218
+ signedTransfer.chainFamily === "svm" ? { signedTransfer } : { signedUserOp: signedTransfer }
1219
+ )
1213
1220
  });
1214
1221
  if (!res.ok) await throwApiError(res);
1215
1222
  return await res.json();
@@ -1323,7 +1330,14 @@ async function probeActionCompletion(apiBaseUrl, actionId) {
1323
1330
  if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
1324
1331
  return { detected: false, reason: "not-found", status: res.status, code, message };
1325
1332
  }
1326
- if (res.status === 422 && code === "APPROVE_NOT_DETECTED") {
1333
+ const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
1334
+ "APPROVE_NOT_DETECTED",
1335
+ "APPROVE_SPL_NOT_DETECTED",
1336
+ "SPL_DELEGATE_MISSING",
1337
+ "SPL_DELEGATE_INSUFFICIENT",
1338
+ "SPL_DELEGATE_WRONG_OWNER"
1339
+ ]);
1340
+ if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
1327
1341
  return { detected: false, reason: "not-found", status: res.status, code, message };
1328
1342
  }
1329
1343
  if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
@@ -1351,6 +1365,8 @@ async function pollTransferTick(params) {
1351
1365
  }
1352
1366
 
1353
1367
  // src/hooks/useTransferPolling.ts
1368
+ var FAST_POLL_COUNT = 8;
1369
+ var FAST_POLL_INTERVAL_MS = 1e3;
1354
1370
  function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
1355
1371
  const { apiBaseUrl } = useBlinkConfig();
1356
1372
  const { getAccessToken: privyGetAccessToken } = usePrivy();
@@ -1358,12 +1374,15 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
1358
1374
  const [transfer, setTransfer] = useState(null);
1359
1375
  const [error, setError] = useState(null);
1360
1376
  const [isPolling, setIsPolling] = useState(false);
1361
- const intervalRef = useRef(null);
1377
+ const timeoutRef = useRef(null);
1362
1378
  const transferIdRef = useRef(null);
1379
+ const tickCountRef = useRef(0);
1380
+ const runningRef = useRef(false);
1363
1381
  const stopPolling = useCallback(() => {
1364
- if (intervalRef.current) {
1365
- clearInterval(intervalRef.current);
1366
- intervalRef.current = null;
1382
+ runningRef.current = false;
1383
+ if (timeoutRef.current) {
1384
+ clearTimeout(timeoutRef.current);
1385
+ timeoutRef.current = null;
1367
1386
  }
1368
1387
  setIsPolling(false);
1369
1388
  }, []);
@@ -1388,16 +1407,27 @@ function useTransferPolling(intervalMs = 3e3, overrideGetAccessToken) {
1388
1407
  stopPolling();
1389
1408
  }
1390
1409
  }, [apiBaseUrl, getAccessToken, stopPolling]);
1410
+ const scheduleNext = useCallback(() => {
1411
+ if (!runningRef.current) return;
1412
+ const next = tickCountRef.current < FAST_POLL_COUNT ? Math.min(FAST_POLL_INTERVAL_MS, intervalMs) : intervalMs;
1413
+ timeoutRef.current = setTimeout(async () => {
1414
+ tickCountRef.current += 1;
1415
+ await poll();
1416
+ scheduleNext();
1417
+ }, next);
1418
+ }, [intervalMs, poll]);
1391
1419
  const startPolling = useCallback(
1392
1420
  (transferId) => {
1393
1421
  stopPolling();
1394
1422
  transferIdRef.current = transferId;
1423
+ tickCountRef.current = 0;
1424
+ runningRef.current = true;
1395
1425
  setIsPolling(true);
1396
1426
  setError(null);
1397
1427
  poll();
1398
- intervalRef.current = setInterval(poll, intervalMs);
1428
+ scheduleNext();
1399
1429
  },
1400
- [poll, intervalMs, stopPolling]
1430
+ [poll, scheduleNext, stopPolling]
1401
1431
  );
1402
1432
  useEffect(() => () => stopPolling(), [stopPolling]);
1403
1433
  return { transfer, error, isPolling, startPolling, stopPolling };
@@ -1650,7 +1680,7 @@ async function resolvePermit2BatchMetadata(options) {
1650
1680
  approveAction = null,
1651
1681
  signAction,
1652
1682
  fetchAuthorizationSession: fetchAuthorizationSession2 = fetchAuthorizationSession,
1653
- sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
1683
+ sleep: sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
1654
1684
  logPrefix = "[blink-sdk][permit2-batch]",
1655
1685
  waitForAwaitingLimit = false
1656
1686
  } = options;
@@ -1690,7 +1720,7 @@ async function resolvePermit2BatchMetadata(options) {
1690
1720
  `SIGN_PERMIT2 metadata is incomplete for batch execution. Missing: ${missingFields.join(", ")}.`
1691
1721
  );
1692
1722
  }
1693
- await sleep(BATCH_SIGN_PERMIT2_POLL_MS);
1723
+ await sleep2(BATCH_SIGN_PERMIT2_POLL_MS);
1694
1724
  const refreshedSession = await fetchAuthorizationSession2(apiBaseUrl, sessionId);
1695
1725
  const refreshedSignAction = refreshedSession.actions.find((action) => action.id === signAction.id);
1696
1726
  if (!refreshedSignAction) {
@@ -1744,7 +1774,7 @@ function isTxSettlementTimeoutError(err) {
1744
1774
  async function waitForTransactionReceipt(walletClient, txHash, logContext, options = {}) {
1745
1775
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
1746
1776
  const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
1747
- const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1777
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1748
1778
  const info = options.logger?.info ?? ((message) => console.info(message));
1749
1779
  const warn = options.logger?.warn ?? ((message) => console.warn(message));
1750
1780
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
@@ -1764,7 +1794,7 @@ async function waitForTransactionReceipt(walletClient, txHash, logContext, optio
1764
1794
  `[blink-sdk][tx-receipt] eth_getTransactionReceipt errored; will retry. context=${logContext}, txHash=${txHash}, error=${err instanceof Error ? err.message : String(err)}`
1765
1795
  );
1766
1796
  }
1767
- await sleep(pollIntervalMs);
1797
+ await sleep2(pollIntervalMs);
1768
1798
  }
1769
1799
  const waitedMs = pollIntervalMs * maxAttempts;
1770
1800
  if (options.strict) {
@@ -1807,7 +1837,7 @@ function parseNonce(raw) {
1807
1837
  async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logContext, options = {}) {
1808
1838
  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_TX_RECEIPT_POLL_INTERVAL_MS;
1809
1839
  const maxAttempts = options.maxAttempts ?? DEFAULT_TX_RECEIPT_MAX_ATTEMPTS;
1810
- const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1840
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1811
1841
  const info = options.logger?.info ?? ((message) => console.info(message));
1812
1842
  const warn = options.logger?.warn ?? ((message) => console.warn(message));
1813
1843
  let lastObservedNonce = null;
@@ -1826,7 +1856,7 @@ async function waitForNonceAdvance(walletClient, sender, expectedMinNonce, logCo
1826
1856
  `[blink-sdk][tx-nonce] eth_getTransactionCount errored; will retry. context=${logContext}, sender=${sender}, error=${err instanceof Error ? err.message : String(err)}`
1827
1857
  );
1828
1858
  }
1829
- await sleep(pollIntervalMs);
1859
+ await sleep2(pollIntervalMs);
1830
1860
  }
1831
1861
  const waitedMs = pollIntervalMs * maxAttempts;
1832
1862
  throw new TxSettlementTimeoutError({
@@ -1849,7 +1879,7 @@ async function waitForTransactionSettled(params) {
1849
1879
  logContext,
1850
1880
  pollIntervalMs,
1851
1881
  maxAttempts,
1852
- sleep,
1882
+ sleep: sleep2,
1853
1883
  logger
1854
1884
  } = params;
1855
1885
  const expectedMinNonce = preSendNonce + 1;
@@ -1864,7 +1894,7 @@ async function waitForTransactionSettled(params) {
1864
1894
  await waitForTransactionReceipt(walletClient, txHash, logContext, {
1865
1895
  pollIntervalMs: resolvedPollIntervalMs,
1866
1896
  maxAttempts: resolvedMaxAttempts,
1867
- sleep,
1897
+ sleep: sleep2,
1868
1898
  logger: sharedLogger,
1869
1899
  strict: true
1870
1900
  });
@@ -1886,7 +1916,7 @@ async function waitForTransactionSettled(params) {
1886
1916
  {
1887
1917
  pollIntervalMs: resolvedPollIntervalMs,
1888
1918
  maxAttempts: resolvedMaxAttempts,
1889
- sleep,
1919
+ sleep: sleep2,
1890
1920
  logger: sharedLogger
1891
1921
  }
1892
1922
  );
@@ -1924,13 +1954,13 @@ async function waitForWalletNonceAgreement(params) {
1924
1954
  pollIntervalMs,
1925
1955
  maxAttempts,
1926
1956
  minWaitMs,
1927
- sleep,
1957
+ sleep: sleep2,
1928
1958
  logger
1929
1959
  } = params;
1930
1960
  const resolvedPollIntervalMs = pollIntervalMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_POLL_INTERVAL_MS;
1931
1961
  const resolvedMaxAttempts = maxAttempts ?? DEFAULT_WALLET_NONCE_AGREEMENT_MAX_ATTEMPTS;
1932
1962
  const resolvedMinWaitMs = minWaitMs ?? DEFAULT_WALLET_NONCE_AGREEMENT_MIN_WAIT_MS;
1933
- const resolvedSleep = sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1963
+ const resolvedSleep = sleep2 ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1934
1964
  const info = logger?.info ?? ((message) => console.info(message));
1935
1965
  const warn = logger?.warn ?? ((message) => console.warn(message));
1936
1966
  const startedAt = Date.now();
@@ -2016,7 +2046,7 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
2016
2046
  const maxConsecutiveErrors = options.maxConsecutiveErrors ?? DEFAULT_MAX_CONSECUTIVE_ERRORS;
2017
2047
  const errorGracePeriodMs = options.errorGracePeriodMs ?? DEFAULT_ERROR_GRACE_PERIOD_MS;
2018
2048
  const logEveryN = Math.max(1, options.logEveryNAttempts ?? DEFAULT_LOG_EVERY_N_ATTEMPTS);
2019
- const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2049
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2020
2050
  const now = options.now ?? (() => Date.now());
2021
2051
  const info = options.logger?.info ?? ((m) => console.info(m));
2022
2052
  const warn = options.logger?.warn ?? ((m) => console.warn(m));
@@ -2025,7 +2055,7 @@ async function pollWalletCallsStatus(walletClient, callsId, options = {}) {
2025
2055
  let consecutiveErrors = 0;
2026
2056
  let lastStatus;
2027
2057
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2028
- await sleep(pollIntervalMs);
2058
+ await sleep2(pollIntervalMs);
2029
2059
  let status;
2030
2060
  try {
2031
2061
  status = await withTimeout(
@@ -2207,6 +2237,10 @@ function buildTargetMatchers(target) {
2207
2237
  aliases.add("rabby");
2208
2238
  aliases.add("io.rabby");
2209
2239
  }
2240
+ if (value.includes("phantom")) {
2241
+ aliases.add("phantom");
2242
+ aliases.add("app.phantom");
2243
+ }
2210
2244
  if (value.includes("injected")) {
2211
2245
  aliases.add("injected");
2212
2246
  }
@@ -2244,6 +2278,252 @@ function resolveWalletConnector(connectors, target) {
2244
2278
  return metaMaskConnector ?? connectors[0];
2245
2279
  }
2246
2280
 
2281
+ // src/solanaWalletRuntime.ts
2282
+ function asSigner(adapter) {
2283
+ return adapter;
2284
+ }
2285
+ var PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE = "PHANTOM_SOLANA_CONNECT_TIMEOUT";
2286
+ var APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE = "APPROVE_SPL_CONFIRMATION_TIMEOUT";
2287
+ var APPROVE_SPL_ONCHAIN_FAILURE_PREFIX = "Solana transaction failed:";
2288
+ var DEFAULT_CONFIRM_TIMEOUT_MS = 45e3;
2289
+ var DEFAULT_POLL_INTERVAL_MS2 = 1e3;
2290
+ var DEFAULT_COMMITMENT = "confirmed";
2291
+ var cachedAdapter = null;
2292
+ var cachedProviderKey = null;
2293
+ function providerKey(selection) {
2294
+ const providerName = selection.providerName?.trim();
2295
+ if (!providerName) {
2296
+ throw new Error("Solana wallet metadata is missing providerName.");
2297
+ }
2298
+ return `${selection.providerId ?? ""}:${providerName.toLowerCase()}`;
2299
+ }
2300
+ function assertSupportedProvider(selection) {
2301
+ const name = selection.providerName?.trim().toLowerCase();
2302
+ if (name !== "phantom") {
2303
+ throw new Error(`Unsupported Solana wallet provider: ${selection.providerName ?? "unknown"}.`);
2304
+ }
2305
+ }
2306
+ async function createAdapter(selection) {
2307
+ assertSupportedProvider(selection);
2308
+ const { PhantomWalletAdapter } = await import('@solana/wallet-adapter-phantom');
2309
+ return new PhantomWalletAdapter();
2310
+ }
2311
+ function readPublicKey(adapter) {
2312
+ const publicKey = adapter.publicKey?.toBase58();
2313
+ if (!publicKey) {
2314
+ throw new Error("Solana wallet did not return a public key.");
2315
+ }
2316
+ return publicKey;
2317
+ }
2318
+ async function connectSolanaWallet(selection, options) {
2319
+ const key = providerKey(selection);
2320
+ if (!cachedAdapter || cachedProviderKey !== key) {
2321
+ cachedAdapter = await createAdapter(selection);
2322
+ cachedProviderKey = key;
2323
+ }
2324
+ if (!cachedAdapter.connected) {
2325
+ const adapter = cachedAdapter;
2326
+ const connectPromise = adapter.connect();
2327
+ const timeoutMs = options?.timeoutMs;
2328
+ if (typeof timeoutMs === "number" && timeoutMs > 0) {
2329
+ let timeoutHandle = null;
2330
+ const timeoutPromise = new Promise((_, reject) => {
2331
+ timeoutHandle = setTimeout(() => {
2332
+ reject(new Error(PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE));
2333
+ }, timeoutMs);
2334
+ });
2335
+ try {
2336
+ await Promise.race([connectPromise, timeoutPromise]);
2337
+ } finally {
2338
+ if (timeoutHandle) clearTimeout(timeoutHandle);
2339
+ }
2340
+ } else {
2341
+ await connectPromise;
2342
+ }
2343
+ }
2344
+ return {
2345
+ adapter: cachedAdapter,
2346
+ publicKey: readPublicKey(cachedAdapter)
2347
+ };
2348
+ }
2349
+ async function signSolanaTransaction(selection, unsignedTxBase64, expectedOwnerPubkey) {
2350
+ const { adapter, publicKey } = await connectSolanaWallet(selection);
2351
+ if (publicKey !== expectedOwnerPubkey) {
2352
+ throw new Error(
2353
+ `Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
2354
+ );
2355
+ }
2356
+ const signer = asSigner(adapter);
2357
+ if (typeof signer.signTransaction !== "function") {
2358
+ throw new Error("Selected Solana wallet does not support transaction signing.");
2359
+ }
2360
+ const transaction = await deserializeTransaction(unsignedTxBase64);
2361
+ const signedTransaction = await signer.signTransaction(transaction);
2362
+ return {
2363
+ ownerPubkey: publicKey,
2364
+ signedTxBase64: bytesToBase64(serializeTransaction(signedTransaction))
2365
+ };
2366
+ }
2367
+ async function supportsSignAllSolanaTransactions(selection) {
2368
+ const { adapter } = await connectSolanaWallet(selection);
2369
+ return typeof asSigner(adapter).signAllTransactions === "function";
2370
+ }
2371
+ async function signAllSolanaTransactions(selection, unsignedTxsBase64, expectedOwnerPubkey) {
2372
+ if (!Array.isArray(unsignedTxsBase64) || unsignedTxsBase64.length === 0) {
2373
+ throw new Error("signAllSolanaTransactions requires at least one unsigned transaction.");
2374
+ }
2375
+ const { adapter, publicKey } = await connectSolanaWallet(selection);
2376
+ if (publicKey !== expectedOwnerPubkey) {
2377
+ throw new Error(
2378
+ `Connected Solana wallet ${publicKey} does not match the required source wallet ${expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
2379
+ );
2380
+ }
2381
+ const signer = asSigner(adapter);
2382
+ if (typeof signer.signAllTransactions !== "function") {
2383
+ throw new Error("Selected Solana wallet does not support signAllTransactions.");
2384
+ }
2385
+ const transactions = [];
2386
+ for (const txBase64 of unsignedTxsBase64) {
2387
+ transactions.push(await deserializeTransaction(txBase64));
2388
+ }
2389
+ const signed = await signer.signAllTransactions(transactions);
2390
+ if (!Array.isArray(signed) || signed.length !== transactions.length) {
2391
+ throw new Error(
2392
+ `signAllTransactions returned ${Array.isArray(signed) ? signed.length : "non-array"} transactions; expected ${transactions.length}.`
2393
+ );
2394
+ }
2395
+ return {
2396
+ ownerPubkey: publicKey,
2397
+ signedTxsBase64: signed.map((tx) => bytesToBase64(serializeTransaction(tx)))
2398
+ };
2399
+ }
2400
+ async function deserializeTransaction(unsignedTxBase64) {
2401
+ const { Transaction, VersionedTransaction } = await import('@solana/web3.js');
2402
+ const bytes = base64ToBytes(unsignedTxBase64);
2403
+ try {
2404
+ return Transaction.from(bytes);
2405
+ } catch {
2406
+ return VersionedTransaction.deserialize(bytes);
2407
+ }
2408
+ }
2409
+ function serializeTransaction(transaction) {
2410
+ if ("version" in transaction) {
2411
+ return transaction.serialize();
2412
+ }
2413
+ return transaction.serialize({
2414
+ requireAllSignatures: false,
2415
+ verifySignatures: false
2416
+ });
2417
+ }
2418
+ function base64ToBytes(value) {
2419
+ if (typeof globalThis.atob === "function") {
2420
+ const binary = globalThis.atob(value);
2421
+ const bytes = new Uint8Array(binary.length);
2422
+ for (let i = 0; i < binary.length; i++) {
2423
+ bytes[i] = binary.charCodeAt(i);
2424
+ }
2425
+ return bytes;
2426
+ }
2427
+ const bufferCtor = globalThis.Buffer;
2428
+ if (bufferCtor) {
2429
+ return bufferCtor.from(value, "base64");
2430
+ }
2431
+ throw new Error("Base64 decoding is not available in this environment.");
2432
+ }
2433
+ function bytesToBase64(bytes) {
2434
+ if (typeof globalThis.btoa === "function") {
2435
+ let binary = "";
2436
+ for (const byte of bytes) {
2437
+ binary += String.fromCharCode(byte);
2438
+ }
2439
+ return globalThis.btoa(binary);
2440
+ }
2441
+ const bufferCtor = globalThis.Buffer;
2442
+ if (bufferCtor) {
2443
+ return bufferCtor.from(bytes).toString("base64");
2444
+ }
2445
+ throw new Error("Base64 encoding is not available in this environment.");
2446
+ }
2447
+ async function signAndSendApproveSplViaWallet(params) {
2448
+ const rpcUrl = params.rpcUrl?.trim();
2449
+ if (!rpcUrl) {
2450
+ throw new Error("signAndSendApproveSplViaWallet requires an rpcUrl.");
2451
+ }
2452
+ const unsignedTxBase64 = params.unsignedTxBase64?.trim();
2453
+ if (!unsignedTxBase64) {
2454
+ throw new Error("signAndSendApproveSplViaWallet requires unsignedTxBase64.");
2455
+ }
2456
+ const commitment = params.commitment ?? DEFAULT_COMMITMENT;
2457
+ const { adapter, publicKey } = await connectSolanaWallet(params.selection);
2458
+ if (publicKey !== params.expectedOwnerPubkey) {
2459
+ throw new Error(
2460
+ `Connected Solana wallet ${publicKey} does not match the required source wallet ${params.expectedOwnerPubkey}. Please switch accounts in Phantom and retry.`
2461
+ );
2462
+ }
2463
+ if (!adapter.sendTransaction) {
2464
+ throw new Error("Selected Solana wallet does not support sendTransaction.");
2465
+ }
2466
+ const transaction = await deserializeTransaction(unsignedTxBase64);
2467
+ const connection = await defaultConnectionFactory(rpcUrl, commitment);
2468
+ const signature = await adapter.sendTransaction(
2469
+ transaction,
2470
+ connection,
2471
+ {
2472
+ preflightCommitment: commitment,
2473
+ maxRetries: 3
2474
+ }
2475
+ );
2476
+ return { ownerPubkey: publicKey, signature };
2477
+ }
2478
+ async function confirmApproveSplViaPolling(params) {
2479
+ const rpcUrl = params.rpcUrl?.trim();
2480
+ if (!rpcUrl) {
2481
+ throw new Error("confirmApproveSplViaPolling requires an rpcUrl.");
2482
+ }
2483
+ const signature = params.signature?.trim();
2484
+ if (!signature) {
2485
+ throw new Error("confirmApproveSplViaPolling requires a signature.");
2486
+ }
2487
+ const confirmTimeoutMs = params.confirmTimeoutMs ?? DEFAULT_CONFIRM_TIMEOUT_MS;
2488
+ const pollIntervalMs = params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
2489
+ const commitment = params.commitment ?? DEFAULT_COMMITMENT;
2490
+ const connection = await defaultConnectionFactory(rpcUrl, commitment);
2491
+ const slot = await pollForConfirmation({
2492
+ connection,
2493
+ signature,
2494
+ confirmTimeoutMs,
2495
+ pollIntervalMs
2496
+ });
2497
+ return { signature, slot };
2498
+ }
2499
+ async function defaultConnectionFactory(rpcUrl, commitment) {
2500
+ const { Connection } = await import('@solana/web3.js');
2501
+ return new Connection(rpcUrl, commitment);
2502
+ }
2503
+ async function pollForConfirmation(args) {
2504
+ const { connection, signature, confirmTimeoutMs, pollIntervalMs } = args;
2505
+ const expiresAt = Date.now() + confirmTimeoutMs;
2506
+ while (Date.now() <= expiresAt) {
2507
+ const result = await connection.getSignatureStatuses(
2508
+ [signature],
2509
+ { searchTransactionHistory: true }
2510
+ );
2511
+ const status = result.value[0];
2512
+ if (status?.err) {
2513
+ throw new Error(`${APPROVE_SPL_ONCHAIN_FAILURE_PREFIX} ${JSON.stringify(status.err)}`);
2514
+ }
2515
+ const confirmationStatus = status?.confirmationStatus;
2516
+ if (confirmationStatus === "confirmed" || confirmationStatus === "finalized") {
2517
+ return status?.slot;
2518
+ }
2519
+ await sleep(pollIntervalMs);
2520
+ }
2521
+ throw new Error(APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE);
2522
+ }
2523
+ function sleep(ms) {
2524
+ return new Promise((resolve) => setTimeout(resolve, ms));
2525
+ }
2526
+
2247
2527
  // src/hooks/authorizationExecutor.ts
2248
2528
  var WALLET_CLIENT_MAX_ATTEMPTS = 25;
2249
2529
  var WALLET_CLIENT_POLL_MS = 400;
@@ -2255,6 +2535,7 @@ var EXECUTE_BRIDGE_TX_TIMEOUT_MS = 12e4;
2255
2535
  var EXECUTE_BRIDGE_TX_TIMEOUT_MESSAGE = "EXECUTE_BRIDGE_TX_TIMEOUT";
2256
2536
  var WALLET_PROMPT_TIMEOUT_MS = 9e4;
2257
2537
  var WALLET_PROMPT_TIMEOUT_MESSAGE = "WALLET_PROMPT_TIMEOUT";
2538
+ var PHANTOM_SOLANA_CONNECT_TIMEOUT_MS = 8e3;
2258
2539
  var BATCH_RECEIPT_POLL_INTERVAL_MS = 1500;
2259
2540
  var BATCH_RECEIPT_MAX_ATTEMPTS = 20;
2260
2541
  var BATCHABLE_ACTION_TYPES = /* @__PURE__ */ new Set(["APPROVE_PERMIT2", "SIGN_PERMIT2", "EXECUTE_BRIDGE"]);
@@ -2368,13 +2649,110 @@ function parseSignTypedDataPayload(typedData) {
2368
2649
  };
2369
2650
  }
2370
2651
  function isBatchableAction(action) {
2652
+ if (action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm") {
2653
+ return false;
2654
+ }
2371
2655
  return BATCHABLE_ACTION_TYPES.has(action.type);
2372
2656
  }
2657
+ function isSvmCombinedFirstTransferPair(pending) {
2658
+ if (pending.length < 2) return false;
2659
+ const [first, second] = pending;
2660
+ if (first.type !== "APPROVE_SPL") return false;
2661
+ if (second.type !== "EXECUTE_BRIDGE") return false;
2662
+ if (second.metadata?.chainFamily !== "svm") return false;
2663
+ if (first.metadata?.awaitingLimit) return false;
2664
+ const approveTx = first.metadata?.unsignedApproveTxBase64;
2665
+ const bridgeTx = second.metadata?.unsignedTxBase64;
2666
+ if (typeof approveTx !== "string" || approveTx.trim() === "") return false;
2667
+ if (typeof bridgeTx !== "string" || bridgeTx.trim() === "") return false;
2668
+ const approveOwner = first.metadata?.ownerPubkey;
2669
+ const bridgeOwner = second.metadata?.ownerPubkey ?? second.metadata?.senderAddress;
2670
+ if (typeof approveOwner !== "string" || approveOwner.trim() === "") return false;
2671
+ if (typeof bridgeOwner !== "string" || bridgeOwner.trim() === "") return false;
2672
+ if (approveOwner !== bridgeOwner) return false;
2673
+ return true;
2674
+ }
2675
+ function getSolanaWalletSelection(action) {
2676
+ const providerName = action.metadata?.providerName;
2677
+ if (typeof providerName !== "string" || providerName.trim() === "") {
2678
+ throw new Error(`${action.type} metadata is missing providerName.`);
2679
+ }
2680
+ const providerId = action.metadata?.providerId;
2681
+ return {
2682
+ ...typeof providerId === "string" && providerId.trim() !== "" ? { providerId } : {},
2683
+ providerName
2684
+ };
2685
+ }
2686
+ function isPhantomEvmProviderAvailable() {
2687
+ if (typeof window === "undefined") return false;
2688
+ const phantom = window.phantom;
2689
+ return Boolean(phantom?.ethereum?.isPhantom);
2690
+ }
2691
+ async function phantomEvmHasAuthorizedAccount() {
2692
+ if (typeof window === "undefined") return false;
2693
+ const provider = window.phantom?.ethereum;
2694
+ if (!provider?.isPhantom) return false;
2695
+ try {
2696
+ const accounts = await provider.request({ method: "eth_accounts" });
2697
+ return Array.isArray(accounts) && accounts.length > 0;
2698
+ } catch {
2699
+ return false;
2700
+ }
2701
+ }
2702
+ async function attemptPhantomEvmConnect(connectors, connectAsync) {
2703
+ let connector = resolveWalletConnector(connectors, { providerName: "Phantom" });
2704
+ if (!connector) {
2705
+ const ethereum = typeof window !== "undefined" ? window.ethereum : void 0;
2706
+ if (ethereum?.isPhantom) {
2707
+ connector = resolveWalletConnector(connectors, { wagmiConnectorId: "injected" });
2708
+ }
2709
+ }
2710
+ if (!connector) return null;
2711
+ const result = await connectAsync({ connector });
2712
+ const address = result.accounts[0];
2713
+ if (!address) return null;
2714
+ return { address, chainId: result.chainId };
2715
+ }
2373
2716
  function getPendingActions(session, completedIds) {
2374
2717
  return session.actions.filter((a) => a.status === "PENDING" && !completedIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
2375
2718
  }
2376
2719
  async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsync) {
2377
2720
  try {
2721
+ if (action.metadata?.chainFamily === "svm") {
2722
+ const selection = getSolanaWalletSelection(action);
2723
+ const evmProbed = isPhantomEvmProviderAvailable() && await phantomEvmHasAuthorizedAccount();
2724
+ const svmPromise = connectSolanaWallet(selection, {
2725
+ timeoutMs: evmProbed ? PHANTOM_SOLANA_CONNECT_TIMEOUT_MS : void 0
2726
+ });
2727
+ const evmPromise = evmProbed ? attemptPhantomEvmConnect(connectors, connectAsync) : Promise.resolve(null);
2728
+ const [svmSettled, evmSettled] = await Promise.allSettled([svmPromise, evmPromise]);
2729
+ const svmPublicKey = svmSettled.status === "fulfilled" ? svmSettled.value.publicKey : null;
2730
+ const evmAddress = evmSettled.status === "fulfilled" && evmSettled.value ? evmSettled.value.address : null;
2731
+ if (!svmPublicKey && !evmAddress) {
2732
+ const svmReason = svmSettled.status === "rejected" ? svmSettled.reason instanceof Error ? svmSettled.reason.message : String(svmSettled.reason) : "Failed to connect Phantom.";
2733
+ const friendlyMsg = svmReason === PHANTOM_SOLANA_CONNECT_TIMEOUT_MESSAGE ? "Phantom did not respond. Please try again." : svmReason;
2734
+ return actionError(
2735
+ action,
2736
+ isUserRejection(friendlyMsg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : friendlyMsg
2737
+ );
2738
+ }
2739
+ if (evmAddress) {
2740
+ await refreshWalletCapabilities(wagmiConfig2, "open-provider:phantom-dual");
2741
+ }
2742
+ const messageParts = [];
2743
+ if (svmPublicKey) messageParts.push(`SVM: ${svmPublicKey}`);
2744
+ if (evmAddress) messageParts.push(`EVM: ${evmAddress}`);
2745
+ return actionSuccess(
2746
+ action,
2747
+ `Connected to ${selection.providerName}. ${messageParts.join(", ")}`,
2748
+ {
2749
+ accounts: svmPublicKey ? [svmPublicKey, ...evmAddress ? [evmAddress] : []] : evmAddress ? [evmAddress] : [],
2750
+ chainFamily: "svm",
2751
+ ...svmPublicKey ? { userSolanaWalletPubkey: svmPublicKey } : {},
2752
+ ...evmAddress ? { evmAddress } : {}
2753
+ }
2754
+ );
2755
+ }
2378
2756
  const account = getAccount(wagmiConfig2);
2379
2757
  const targetId = action.metadata?.wagmiConnectorId;
2380
2758
  const connector = resolveWalletConnector(connectors, { wagmiConnectorId: targetId });
@@ -2500,9 +2878,16 @@ async function executeOpenProvider(action, wagmiConfig2, connectors, connectAsyn
2500
2878
  { accounts: [...result.accounts], chainId: hexChainId }
2501
2879
  );
2502
2880
  } catch (err) {
2881
+ const msg = err instanceof Error ? err.message : "Failed to connect wallet";
2882
+ if (action.metadata?.chainFamily === "svm") {
2883
+ return actionError(
2884
+ action,
2885
+ isUserRejection(msg) ? "You rejected the Solana wallet connection request. Please connect Phantom to continue." : msg
2886
+ );
2887
+ }
2503
2888
  return actionError(
2504
2889
  action,
2505
- err instanceof Error ? err.message : "Failed to connect wallet"
2890
+ msg
2506
2891
  );
2507
2892
  }
2508
2893
  }
@@ -2580,6 +2965,334 @@ async function executeSwitchChain(action, wagmiConfig2, switchChainAsync) {
2580
2965
  );
2581
2966
  }
2582
2967
  }
2968
+ async function executeApproveSplSolana(action, options) {
2969
+ try {
2970
+ const selection = getSolanaWalletSelection(action);
2971
+ const ownerPubkey = action.metadata?.ownerPubkey;
2972
+ const unsignedApproveTxBase64 = action.metadata?.unsignedApproveTxBase64;
2973
+ const tokenSymbol = action.metadata?.tokenSymbol;
2974
+ const clientRpcUrl = action.metadata?.clientRpcUrl;
2975
+ if (action.metadata?.awaitingLimit) {
2976
+ return actionError(
2977
+ action,
2978
+ "APPROVE_SPL action is still waiting for a One-Tap allowance amount."
2979
+ );
2980
+ }
2981
+ if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
2982
+ return actionError(action, "APPROVE_SPL metadata is missing ownerPubkey.");
2983
+ }
2984
+ if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
2985
+ return actionError(
2986
+ action,
2987
+ "APPROVE_SPL metadata is missing unsignedApproveTxBase64."
2988
+ );
2989
+ }
2990
+ appendDebug("info", "APPROVE_SPL: entry", {
2991
+ actionId: action.id,
2992
+ ownerPubkey,
2993
+ tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
2994
+ });
2995
+ if (typeof clientRpcUrl === "string" && clientRpcUrl.trim() !== "") {
2996
+ const sendLabel = "APPROVE_SPL signAndSendApproveSplViaWallet";
2997
+ const sent = await withWatchdog(
2998
+ withTimeout(
2999
+ signAndSendApproveSplViaWallet({
3000
+ selection,
3001
+ unsignedTxBase64: unsignedApproveTxBase64,
3002
+ expectedOwnerPubkey: ownerPubkey,
3003
+ rpcUrl: clientRpcUrl
3004
+ }),
3005
+ WALLET_PROMPT_TIMEOUT_MS,
3006
+ sendLabel
3007
+ ),
3008
+ sendLabel
3009
+ );
3010
+ appendDebug("info", "APPROVE_SPL: tx submitted via wallet", {
3011
+ actionId: action.id,
3012
+ signature: sent.signature
3013
+ });
3014
+ try {
3015
+ options?.onConfirming?.(action);
3016
+ } catch (callbackErr) {
3017
+ appendDebug("warn", "APPROVE_SPL: onConfirming callback threw", {
3018
+ actionId: action.id,
3019
+ error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
3020
+ });
3021
+ }
3022
+ const confirmLabel = "APPROVE_SPL confirmApproveSplViaPolling";
3023
+ let slot;
3024
+ try {
3025
+ const confirmation = await withWatchdog(
3026
+ confirmApproveSplViaPolling({
3027
+ rpcUrl: clientRpcUrl,
3028
+ signature: sent.signature
3029
+ }),
3030
+ confirmLabel
3031
+ );
3032
+ slot = confirmation.slot;
3033
+ appendDebug("info", "APPROVE_SPL: settled", {
3034
+ actionId: action.id,
3035
+ signature: sent.signature,
3036
+ slot
3037
+ });
3038
+ } catch (pollErr) {
3039
+ const pollMsg = pollErr instanceof Error ? pollErr.message : String(pollErr);
3040
+ if (pollMsg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE) {
3041
+ throw pollErr;
3042
+ }
3043
+ if (pollMsg.startsWith(APPROVE_SPL_ONCHAIN_FAILURE_PREFIX)) {
3044
+ throw pollErr;
3045
+ }
3046
+ appendDebug("warn", "APPROVE_SPL: confirmation poll failed; relying on server verification", {
3047
+ actionId: action.id,
3048
+ signature: sent.signature,
3049
+ error: pollMsg
3050
+ });
3051
+ }
3052
+ return actionSuccess(
3053
+ action,
3054
+ `Confirmed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
3055
+ {
3056
+ approveSignature: sent.signature
3057
+ }
3058
+ );
3059
+ }
3060
+ const signLabel = "APPROVE_SPL signSolanaTransaction";
3061
+ const signed = await withWatchdog(
3062
+ withTimeout(
3063
+ signSolanaTransaction(
3064
+ selection,
3065
+ unsignedApproveTxBase64,
3066
+ ownerPubkey
3067
+ ),
3068
+ WALLET_PROMPT_TIMEOUT_MS,
3069
+ signLabel
3070
+ ),
3071
+ signLabel
3072
+ );
3073
+ appendDebug("info", "APPROVE_SPL: signed (legacy path)", {
3074
+ actionId: action.id
3075
+ });
3076
+ return actionSuccess(
3077
+ action,
3078
+ `Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
3079
+ { signedTxBase64: signed.signedTxBase64 }
3080
+ );
3081
+ } catch (err) {
3082
+ const msg = err instanceof Error ? err.message : "Failed to sign SPL approval";
3083
+ const timedOut = err instanceof PromiseTimeoutError;
3084
+ const confirmTimedOut = msg === APPROVE_SPL_CONFIRMATION_TIMEOUT_MESSAGE;
3085
+ appendDebug(timedOut || confirmTimedOut ? "warn" : "error", "APPROVE_SPL: threw", {
3086
+ actionId: action.id,
3087
+ error: msg,
3088
+ userRejected: isUserRejection(msg),
3089
+ timedOut,
3090
+ confirmTimedOut
3091
+ });
3092
+ if (timedOut) {
3093
+ return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
3094
+ }
3095
+ if (confirmTimedOut) {
3096
+ return actionError(
3097
+ action,
3098
+ "Your SPL approval was submitted but did not confirm in time. Please wait a moment and retry \u2014 do not re-approve in Phantom yet, as that would create a duplicate transaction."
3099
+ );
3100
+ }
3101
+ return actionError(
3102
+ action,
3103
+ isUserRejection(msg) ? "You rejected the SPL approval transaction. Please approve the transaction in Phantom to continue." : msg
3104
+ );
3105
+ }
3106
+ }
3107
+ async function executeExecuteBridgeSolana(action) {
3108
+ try {
3109
+ const selection = getSolanaWalletSelection(action);
3110
+ const ownerPubkey = action.metadata?.ownerPubkey ?? action.metadata?.senderAddress;
3111
+ const unsignedTxBase64 = action.metadata?.unsignedTxBase64;
3112
+ if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
3113
+ return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing ownerPubkey.");
3114
+ }
3115
+ if (typeof unsignedTxBase64 !== "string" || unsignedTxBase64.trim() === "") {
3116
+ return actionError(action, "SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64.");
3117
+ }
3118
+ appendDebug("info", "EXECUTE_BRIDGE: svm entry", {
3119
+ actionId: action.id,
3120
+ ownerPubkey
3121
+ });
3122
+ const label = "EXECUTE_BRIDGE signSolanaTransaction";
3123
+ const signed = await withWatchdog(
3124
+ withTimeout(
3125
+ signSolanaTransaction(
3126
+ selection,
3127
+ unsignedTxBase64,
3128
+ ownerPubkey
3129
+ ),
3130
+ WALLET_PROMPT_TIMEOUT_MS,
3131
+ label
3132
+ ),
3133
+ label
3134
+ );
3135
+ appendDebug("info", "EXECUTE_BRIDGE: svm signed", {
3136
+ actionId: action.id
3137
+ });
3138
+ return actionSuccess(
3139
+ action,
3140
+ "Signed Solana bridge transfer in Phantom.",
3141
+ { signedTxBase64: signed.signedTxBase64, ownerPubkey: signed.ownerPubkey }
3142
+ );
3143
+ } catch (err) {
3144
+ const msg = err instanceof Error ? err.message : "Failed to sign Solana bridge transfer";
3145
+ const timedOut = err instanceof PromiseTimeoutError;
3146
+ appendDebug(timedOut ? "warn" : "error", "EXECUTE_BRIDGE: svm threw", {
3147
+ actionId: action.id,
3148
+ error: msg,
3149
+ userRejected: isUserRejection(msg),
3150
+ timedOut
3151
+ });
3152
+ if (timedOut) {
3153
+ return actionError(action, WALLET_PROMPT_TIMEOUT_MESSAGE);
3154
+ }
3155
+ return actionError(
3156
+ action,
3157
+ isUserRejection(msg) ? "You rejected the Solana transfer transaction. Please approve the transaction in Phantom to continue." : msg
3158
+ );
3159
+ }
3160
+ }
3161
+ async function executeSvmCombinedFirstTransfer(actions, options) {
3162
+ if (actions.length !== 2) {
3163
+ throw new Error(
3164
+ `executeSvmCombinedFirstTransfer requires exactly 2 actions, received ${actions.length}.`
3165
+ );
3166
+ }
3167
+ const approveAction = actions[0].action;
3168
+ const bridgeAction = actions[1].action;
3169
+ if (approveAction.type !== "APPROVE_SPL") {
3170
+ throw new Error(
3171
+ `executeSvmCombinedFirstTransfer expects APPROVE_SPL first, got ${approveAction.type}.`
3172
+ );
3173
+ }
3174
+ if (bridgeAction.type !== "EXECUTE_BRIDGE" || bridgeAction.metadata?.chainFamily !== "svm") {
3175
+ throw new Error(
3176
+ `executeSvmCombinedFirstTransfer expects SVM EXECUTE_BRIDGE second, got ${bridgeAction.type}/${bridgeAction.metadata?.chainFamily ?? "unknown"}.`
3177
+ );
3178
+ }
3179
+ const ownerPubkey = approveAction.metadata?.ownerPubkey;
3180
+ const unsignedApproveTxBase64 = approveAction.metadata?.unsignedApproveTxBase64;
3181
+ const tokenSymbol = approveAction.metadata?.tokenSymbol;
3182
+ const unsignedBridgeTxBase64 = bridgeAction.metadata?.unsignedTxBase64;
3183
+ if (typeof ownerPubkey !== "string" || ownerPubkey.trim() === "") {
3184
+ return failBothActions(
3185
+ approveAction,
3186
+ bridgeAction,
3187
+ "APPROVE_SPL metadata is missing ownerPubkey."
3188
+ );
3189
+ }
3190
+ if (typeof unsignedApproveTxBase64 !== "string" || unsignedApproveTxBase64.trim() === "") {
3191
+ return failBothActions(
3192
+ approveAction,
3193
+ bridgeAction,
3194
+ "APPROVE_SPL metadata is missing unsignedApproveTxBase64."
3195
+ );
3196
+ }
3197
+ if (typeof unsignedBridgeTxBase64 !== "string" || unsignedBridgeTxBase64.trim() === "") {
3198
+ return failBothActions(
3199
+ approveAction,
3200
+ bridgeAction,
3201
+ "SVM EXECUTE_BRIDGE metadata is missing unsignedTxBase64."
3202
+ );
3203
+ }
3204
+ const selection = getSolanaWalletSelection(approveAction);
3205
+ appendDebug("info", "SVM_COMBINED: entry", {
3206
+ approveActionId: approveAction.id,
3207
+ bridgeActionId: bridgeAction.id,
3208
+ ownerPubkey,
3209
+ tokenSymbol: typeof tokenSymbol === "string" ? tokenSymbol : null
3210
+ });
3211
+ const signLabel = "SVM_COMBINED signAllSolanaTransactions";
3212
+ let signed;
3213
+ try {
3214
+ signed = await withWatchdog(
3215
+ withTimeout(
3216
+ signAllSolanaTransactions(
3217
+ selection,
3218
+ [unsignedApproveTxBase64, unsignedBridgeTxBase64],
3219
+ ownerPubkey
3220
+ ),
3221
+ WALLET_PROMPT_TIMEOUT_MS,
3222
+ signLabel
3223
+ ),
3224
+ signLabel
3225
+ );
3226
+ } catch (err) {
3227
+ const msg = err instanceof Error ? err.message : "Failed to sign Solana transactions";
3228
+ const timedOut = err instanceof PromiseTimeoutError;
3229
+ appendDebug(timedOut ? "warn" : "error", "SVM_COMBINED: signAllTransactions threw", {
3230
+ approveActionId: approveAction.id,
3231
+ bridgeActionId: bridgeAction.id,
3232
+ error: msg,
3233
+ userRejected: isUserRejection(msg),
3234
+ timedOut
3235
+ });
3236
+ if (timedOut) {
3237
+ return failBothActions(
3238
+ approveAction,
3239
+ bridgeAction,
3240
+ WALLET_PROMPT_TIMEOUT_MESSAGE
3241
+ );
3242
+ }
3243
+ if (isUserRejection(msg)) {
3244
+ return failBothActions(
3245
+ approveAction,
3246
+ bridgeAction,
3247
+ "You rejected the Solana transactions. Please approve the prompt in Phantom to continue."
3248
+ );
3249
+ }
3250
+ return failBothActions(approveAction, bridgeAction, msg);
3251
+ }
3252
+ const [approveSignedB64, bridgeSignedB64] = signed.signedTxsBase64;
3253
+ appendDebug("info", "SVM_COMBINED: signAllTransactions returned", {
3254
+ approveActionId: approveAction.id,
3255
+ bridgeActionId: bridgeAction.id
3256
+ });
3257
+ try {
3258
+ options?.onApproveSplConfirming?.(approveAction);
3259
+ } catch (callbackErr) {
3260
+ appendDebug("warn", "SVM_COMBINED: onApproveSplConfirming callback threw", {
3261
+ approveActionId: approveAction.id,
3262
+ error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
3263
+ });
3264
+ }
3265
+ return {
3266
+ txHash: void 0,
3267
+ actionResults: [
3268
+ {
3269
+ action: approveAction,
3270
+ result: actionSuccess(
3271
+ approveAction,
3272
+ `Signed SPL approval for ${typeof tokenSymbol === "string" ? tokenSymbol : "tokens"}.`,
3273
+ { signedTxBase64: approveSignedB64, ownerPubkey: signed.ownerPubkey }
3274
+ )
3275
+ },
3276
+ {
3277
+ action: bridgeAction,
3278
+ result: actionSuccess(
3279
+ bridgeAction,
3280
+ "Signed Solana bridge transfer in Phantom.",
3281
+ { signedTxBase64: bridgeSignedB64, ownerPubkey: signed.ownerPubkey }
3282
+ )
3283
+ }
3284
+ ]
3285
+ };
3286
+ }
3287
+ function failBothActions(approveAction, bridgeAction, message) {
3288
+ return {
3289
+ txHash: void 0,
3290
+ actionResults: [
3291
+ { action: approveAction, result: actionError(approveAction, message) },
3292
+ { action: bridgeAction, result: actionError(bridgeAction, message) }
3293
+ ]
3294
+ };
3295
+ }
2583
3296
  async function executeApprovePermit2(action, wagmiConfig2) {
2584
3297
  let walletClient = null;
2585
3298
  let sender = null;
@@ -3225,6 +3938,7 @@ function useAuthorizationExecutor(options) {
3225
3938
  const [results, setResults] = useState([]);
3226
3939
  const [error, setError] = useState(null);
3227
3940
  const [currentAction, setCurrentAction] = useState(null);
3941
+ const [approveSplConfirming, setApproveSplConfirming] = useState(null);
3228
3942
  const executingRef = useRef(false);
3229
3943
  const walletCapabilitiesRef = useRef({});
3230
3944
  const walletCapabilitiesRefreshedRef = useRef(false);
@@ -3263,16 +3977,17 @@ function useAuthorizationExecutor(options) {
3263
3977
  }
3264
3978
  setError(null);
3265
3979
  setCurrentAction(null);
3980
+ setApproveSplConfirming(null);
3266
3981
  setExecuting(false);
3267
3982
  executingRef.current = false;
3268
3983
  }, []);
3269
3984
  const dispatchAction = useCallback(
3270
- async (action) => {
3985
+ async (action, dispatchOptions) => {
3271
3986
  setCurrentAction(action);
3272
3987
  switch (action.type) {
3273
3988
  case "OPEN_PROVIDER": {
3274
3989
  const result = await executeOpenProvider(action, wagmiConfig2, connectors, connectAsync);
3275
- if (result.status === "success") {
3990
+ if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
3276
3991
  walletCapabilitiesRef.current = await refreshWalletCapabilities(
3277
3992
  wagmiConfig2,
3278
3993
  "open-provider"
@@ -3284,7 +3999,7 @@ function useAuthorizationExecutor(options) {
3284
3999
  return executeSelectSource(action, waitForSelection);
3285
4000
  case "SWITCH_CHAIN": {
3286
4001
  const result = await executeSwitchChain(action, wagmiConfig2, switchChainAsync);
3287
- if (result.status === "success") {
4002
+ if (result.status === "success" && action.metadata?.chainFamily !== "svm") {
3288
4003
  walletCapabilitiesRef.current = await refreshWalletCapabilities(
3289
4004
  wagmiConfig2,
3290
4005
  "switch-chain"
@@ -3304,7 +4019,33 @@ function useAuthorizationExecutor(options) {
3304
4019
  }
3305
4020
  return executeSignPermit2(action, wagmiConfig2, apiBaseUrl ?? "", sessionIdRef.current);
3306
4021
  }
4022
+ case "APPROVE_SPL": {
4023
+ if (action.metadata?.awaitingLimit) {
4024
+ throw new Error(
4025
+ "APPROVE_SPL action has awaitingLimit. The orchestrator must handle one-tap setup before executing this action."
4026
+ );
4027
+ }
4028
+ const externalConfirmingCallback = dispatchOptions?.onApproveSplConfirming;
4029
+ return executeApproveSplSolana(action, {
4030
+ onConfirming: (a) => {
4031
+ setApproveSplConfirming(a);
4032
+ if (externalConfirmingCallback) {
4033
+ try {
4034
+ externalConfirmingCallback(a);
4035
+ } catch (callbackErr) {
4036
+ appendDebug("warn", "APPROVE_SPL: orchestrator onApproveSplConfirming threw", {
4037
+ actionId: a.id,
4038
+ error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
4039
+ });
4040
+ }
4041
+ }
4042
+ }
4043
+ });
4044
+ }
3307
4045
  case "EXECUTE_BRIDGE":
4046
+ if (action.metadata?.chainFamily === "svm") {
4047
+ return executeExecuteBridgeSolana(action);
4048
+ }
3308
4049
  return executeExecuteBridge(action, wagmiConfig2);
3309
4050
  default:
3310
4051
  return actionError(action, `Unsupported action type: ${action.type}`);
@@ -3317,7 +4058,11 @@ function useAuthorizationExecutor(options) {
3317
4058
  if (options2?.sessionId) {
3318
4059
  sessionIdRef.current = options2.sessionId;
3319
4060
  }
3320
- return dispatchAction(action);
4061
+ try {
4062
+ return await dispatchAction(action, options2);
4063
+ } finally {
4064
+ setApproveSplConfirming(null);
4065
+ }
3321
4066
  },
3322
4067
  [dispatchAction]
3323
4068
  );
@@ -3338,6 +4083,43 @@ function useAuthorizationExecutor(options) {
3338
4083
  },
3339
4084
  [apiBaseUrl, wagmiConfig2]
3340
4085
  );
4086
+ const executeSvmCombinedFirstTransferImpl = useCallback(
4087
+ async (actions, options2) => {
4088
+ setCurrentAction(actions[0]?.action ?? null);
4089
+ const externalConfirmingCallback = options2?.onApproveSplConfirming;
4090
+ return executeSvmCombinedFirstTransfer(actions, {
4091
+ onApproveSplConfirming: (a) => {
4092
+ setApproveSplConfirming(a);
4093
+ if (externalConfirmingCallback) {
4094
+ try {
4095
+ externalConfirmingCallback(a);
4096
+ } catch (callbackErr) {
4097
+ appendDebug("warn", "SVM_COMBINED: orchestrator onApproveSplConfirming threw", {
4098
+ actionId: a.id,
4099
+ error: callbackErr instanceof Error ? callbackErr.message : String(callbackErr)
4100
+ });
4101
+ }
4102
+ }
4103
+ }
4104
+ });
4105
+ },
4106
+ []
4107
+ );
4108
+ const canSignAllSolanaTransactions = useCallback(
4109
+ async (action) => {
4110
+ try {
4111
+ const selection = getSolanaWalletSelection(action);
4112
+ return await supportsSignAllSolanaTransactions(selection);
4113
+ } catch (err) {
4114
+ appendDebug("warn", "SVM_COMBINED: signAllTransactions feature-detect failed", {
4115
+ actionId: action.id,
4116
+ error: err instanceof Error ? err.message : String(err)
4117
+ });
4118
+ return false;
4119
+ }
4120
+ },
4121
+ []
4122
+ );
3341
4123
  const canBatch = useCallback(async () => {
3342
4124
  const cacheKey = getBatchCapabilityCacheKey(wagmiConfig2);
3343
4125
  const cachedDecision = batchCapabilityDecisionRef.current;
@@ -3381,11 +4163,13 @@ function useAuthorizationExecutor(options) {
3381
4163
  setResults([]);
3382
4164
  setError(null);
3383
4165
  setBatchTxHash(null);
4166
+ setApproveSplConfirming(null);
3384
4167
  return true;
3385
4168
  }, []);
3386
4169
  const endExecution = useCallback(() => {
3387
4170
  sessionIdRef.current = null;
3388
4171
  setCurrentAction(null);
4172
+ setApproveSplConfirming(null);
3389
4173
  setExecuting(false);
3390
4174
  executingRef.current = false;
3391
4175
  }, []);
@@ -3478,12 +4262,15 @@ function useAuthorizationExecutor(options) {
3478
4262
  results,
3479
4263
  error,
3480
4264
  currentAction,
4265
+ approveSplConfirming,
3481
4266
  pendingSelectSource,
3482
4267
  resolveSelectSource,
3483
4268
  cancelPendingExecution,
3484
4269
  batchTxHash,
3485
4270
  executeAction,
3486
4271
  executeBatch,
4272
+ executeSvmCombinedFirstTransfer: executeSvmCombinedFirstTransferImpl,
4273
+ canSignAllSolanaTransactions,
3487
4274
  canBatch,
3488
4275
  checkPaymasterSupport,
3489
4276
  getCapabilitySnapshot,
@@ -3512,6 +4299,8 @@ function isWebAuthnPasskeyDismissalError(err) {
3512
4299
 
3513
4300
  // src/hooks/useTransferSigning.ts
3514
4301
  var TRANSFER_SIGN_MAX_POLLS = 60;
4302
+ var FAST_POLL_COUNT2 = 6;
4303
+ var FAST_POLL_INTERVAL_MS2 = 500;
3515
4304
  function waitForDocumentFocus2(timeoutMs = 5e3, intervalMs = 100) {
3516
4305
  return new Promise((resolve, reject) => {
3517
4306
  if (typeof document === "undefined") {
@@ -3539,9 +4328,23 @@ function hexToBytes(hex) {
3539
4328
  const bytes = clean.match(/.{1,2}/g).map((b) => parseInt(b, 16));
3540
4329
  return new Uint8Array(bytes);
3541
4330
  }
4331
+ function base64ToBytes2(base64) {
4332
+ const binary = atob(base64);
4333
+ const bytes = new Uint8Array(binary.length);
4334
+ for (let i = 0; i < binary.length; i += 1) {
4335
+ bytes[i] = binary.charCodeAt(i);
4336
+ }
4337
+ return bytes;
4338
+ }
3542
4339
  function toBase642(buffer) {
3543
4340
  return btoa(String.fromCharCode(...new Uint8Array(buffer)));
3544
4341
  }
4342
+ function getSigningChallenge(payload) {
4343
+ if (payload.chainFamily === "svm") {
4344
+ return base64ToBytes2(payload.message);
4345
+ }
4346
+ return hexToBytes(payload.userOpHash);
4347
+ }
3545
4348
  function useTransferSigning(pollIntervalMs = 2e3, options) {
3546
4349
  const blinkConfig = useOptionalBlinkConfig();
3547
4350
  const apiBaseUrl = options?.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
@@ -3558,7 +4361,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
3558
4361
  const [error, setError] = useState(null);
3559
4362
  const [passkeyDismissed, setPasskeyDismissed] = useState(false);
3560
4363
  const signTransfer2 = useCallback(
3561
- async (transferId) => {
4364
+ async (transferId, opts) => {
3562
4365
  setSigning(true);
3563
4366
  setError(null);
3564
4367
  setPasskeyDismissed(false);
@@ -3575,22 +4378,28 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
3575
4378
  throw new Error("Could not get access token");
3576
4379
  }
3577
4380
  let payload = null;
3578
- for (let i = 0; i < TRANSFER_SIGN_MAX_POLLS; i++) {
3579
- const transfer = await fetchTransfer(
3580
- apiBaseUrl,
3581
- token ?? "",
3582
- transferId,
3583
- authorizationSessionToken
3584
- );
3585
- if (transfer.signPayload) {
3586
- payload = transfer.signPayload;
3587
- setSignPayload(payload);
3588
- break;
3589
- }
3590
- if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
3591
- throw new Error(`Unexpected transfer status: ${transfer.status}`);
4381
+ if (opts?.knownSignPayload) {
4382
+ payload = opts.knownSignPayload;
4383
+ setSignPayload(payload);
4384
+ } else {
4385
+ for (let i = 0; i < TRANSFER_SIGN_MAX_POLLS; i++) {
4386
+ const transfer = await fetchTransfer(
4387
+ apiBaseUrl,
4388
+ token ?? "",
4389
+ transferId,
4390
+ authorizationSessionToken
4391
+ );
4392
+ if (transfer.signPayload) {
4393
+ payload = transfer.signPayload;
4394
+ setSignPayload(payload);
4395
+ break;
4396
+ }
4397
+ if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
4398
+ throw new Error(`Unexpected transfer status: ${transfer.status}`);
4399
+ }
4400
+ const intervalMs = i < FAST_POLL_COUNT2 ? Math.min(FAST_POLL_INTERVAL_MS2, pollIntervalMs) : pollIntervalMs;
4401
+ await new Promise((r) => setTimeout(r, intervalMs));
3592
4402
  }
3593
- await new Promise((r) => setTimeout(r, pollIntervalMs));
3594
4403
  }
3595
4404
  if (!payload) {
3596
4405
  throw new Error("Timed out waiting for sign payload. Please try again.");
@@ -3601,8 +4410,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
3601
4410
  "Sign payload is missing passkeyCredentialId. Cannot request passkey signing without it."
3602
4411
  );
3603
4412
  }
3604
- const hashBytes = hexToBytes(payload.userOpHash);
3605
- let signedUserOp;
4413
+ const challengeBytes = getSigningChallenge(payload);
3606
4414
  const allowCredentials = [{
3607
4415
  type: "public-key",
3608
4416
  id: credentialIdBase64ToBytes(passkeyCredentialId)
@@ -3621,7 +4429,7 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
3621
4429
  try {
3622
4430
  assertion = await navigator.credentials.get({
3623
4431
  publicKey: {
3624
- challenge: hashBytes,
4432
+ challenge: challengeBytes,
3625
4433
  rpId: signingRpId,
3626
4434
  allowCredentials,
3627
4435
  userVerification: "required",
@@ -3638,18 +4446,24 @@ function useTransferSigning(pollIntervalMs = 2e3, options) {
3638
4446
  throw new TransferSigningPasskeyDismissedError();
3639
4447
  }
3640
4448
  const response = assertion.response;
3641
- signedUserOp = {
3642
- ...payload.userOp,
4449
+ const webauthnAssertion = {
3643
4450
  credentialId: toBase642(assertion.rawId),
3644
4451
  signature: toBase642(response.signature),
3645
4452
  authenticatorData: toBase642(response.authenticatorData),
3646
4453
  clientDataJSON: toBase642(response.clientDataJSON)
3647
4454
  };
4455
+ const signedTransfer = payload.chainFamily === "svm" ? {
4456
+ chainFamily: "svm",
4457
+ webauthnAssertion
4458
+ } : {
4459
+ ...payload.userOp,
4460
+ ...webauthnAssertion
4461
+ };
3648
4462
  return await signTransfer(
3649
4463
  apiBaseUrl,
3650
4464
  token ?? "",
3651
4465
  transferId,
3652
- signedUserOp,
4466
+ signedTransfer,
3653
4467
  authorizationSessionToken
3654
4468
  );
3655
4469
  } catch (err) {
@@ -3764,6 +4578,8 @@ function assertLinkedTransferBridgeExecuted(params) {
3764
4578
  // src/hooks/useAuthorizationOrchestrator.ts
3765
4579
  var ACTION_POLL_INTERVAL_MS2 = 500;
3766
4580
  var ACTION_POLL_MAX_RETRIES2 = 20;
4581
+ var REPORT_COMPLETION_TIMEOUT_MS = 9e4;
4582
+ var REPORT_COMPLETION_TIMEOUT_MESSAGE = "REPORT_COMPLETION_TIMEOUT";
3767
4583
  function useAuthorizationOrchestrator(deps) {
3768
4584
  const blinkConfig = useOptionalBlinkConfig();
3769
4585
  const resolvedApiBaseUrl = deps.apiBaseUrl ?? blinkConfig?.apiBaseUrl;
@@ -3891,6 +4707,7 @@ function useAuthorizationOrchestrator(deps) {
3891
4707
  waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
3892
4708
  });
3893
4709
  let cachedBatchSupport = null;
4710
+ let cachedSvmSignAllSupport = null;
3894
4711
  while (mergedPending.length > 0) {
3895
4712
  await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
3896
4713
  mergedPending = getMergedPending(sessions, completedIds);
@@ -3978,6 +4795,112 @@ function useAuthorizationOrchestrator(deps) {
3978
4795
  });
3979
4796
  continue;
3980
4797
  }
4798
+ if (action.type === "APPROVE_SPL" && (action.metadata?.awaitingLimit || options?.alwaysPauseForOneTap) && !oneTapCompletedActionIds.has(action.id)) {
4799
+ if (options?.onAwaitingOneTap) {
4800
+ console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup via callback.", {
4801
+ actionId: action.id,
4802
+ ownerSessionId
4803
+ });
4804
+ await options.onAwaitingOneTap(action);
4805
+ console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed via callback.", {
4806
+ actionId: action.id,
4807
+ ownerSessionId
4808
+ });
4809
+ } else {
4810
+ console.info("[blink-sdk][orchestrator] Awaiting Solana one-tap setup.", {
4811
+ actionId: action.id,
4812
+ ownerSessionId
4813
+ });
4814
+ await waitForOneTapPause(action);
4815
+ console.info("[blink-sdk][orchestrator] Solana one-tap setup resumed.", {
4816
+ actionId: action.id,
4817
+ ownerSessionId
4818
+ });
4819
+ }
4820
+ oneTapCompletedActionIds.add(action.id);
4821
+ await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
4822
+ await refreshAllSessions(apiBaseUrl, sessions, actionSessionMap);
4823
+ queueLinkedTransferSessions({
4824
+ sessions,
4825
+ linkedTransferSessionIds,
4826
+ pendingSessionIdsRef
4827
+ });
4828
+ await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
4829
+ markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
4830
+ mergedPending = await waitForPendingActions({
4831
+ apiBaseUrl,
4832
+ sessions,
4833
+ completedIds,
4834
+ actionSessionMap,
4835
+ pendingSessionIdsRef,
4836
+ linkedTransferSessionIds,
4837
+ waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
4838
+ });
4839
+ continue;
4840
+ }
4841
+ if (isSvmCombinedFirstTransferPair(mergedPending)) {
4842
+ const pair = mergedPending.slice(0, 2);
4843
+ const [svmApproveAction, svmBridgeAction] = pair;
4844
+ if (cachedSvmSignAllSupport == null) {
4845
+ cachedSvmSignAllSupport = await authExecutor.canSignAllSolanaTransactions(
4846
+ svmApproveAction
4847
+ );
4848
+ }
4849
+ appendDebug("info", "orchestrator:svm-combined evaluation", {
4850
+ approveActionId: svmApproveAction.id,
4851
+ bridgeActionId: svmBridgeAction.id,
4852
+ ownerSessionId,
4853
+ signAllSupported: cachedSvmSignAllSupport
4854
+ });
4855
+ if (cachedSvmSignAllSupport) {
4856
+ const svmInputs = pair.map((candidate) => ({
4857
+ action: candidate,
4858
+ sessionId: actionSessionMap.get(candidate.id) ?? sessionId
4859
+ }));
4860
+ const svmBatchResult = await authExecutor.executeSvmCombinedFirstTransfer(
4861
+ svmInputs,
4862
+ {
4863
+ onApproveSplConfirming: options?.onApproveSplConfirming
4864
+ }
4865
+ );
4866
+ const errorResult = svmBatchResult.actionResults.find(
4867
+ ({ result: result2 }) => result2.status === "error"
4868
+ );
4869
+ if (errorResult) {
4870
+ authExecutor.addResult(errorResult.result);
4871
+ throw new Error(errorResult.result.message);
4872
+ }
4873
+ for (const { action: svmAction, result: result2 } of svmBatchResult.actionResults) {
4874
+ completedIds.add(svmAction.id);
4875
+ oneTapCompletedActionIds.delete(svmAction.id);
4876
+ authExecutor.addResult(result2);
4877
+ const svmOwnerSessionId = actionSessionMap.get(svmAction.id) ?? ownerSessionId;
4878
+ const reportedSession2 = await reportActionCompletionWithLogging(
4879
+ apiBaseUrl,
4880
+ svmAction,
4881
+ svmOwnerSessionId,
4882
+ result2
4883
+ );
4884
+ updateTrackedSession(sessions, svmOwnerSessionId, reportedSession2, actionSessionMap);
4885
+ }
4886
+ queueLinkedTransferSessions({
4887
+ sessions,
4888
+ linkedTransferSessionIds,
4889
+ pendingSessionIdsRef
4890
+ });
4891
+ await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
4892
+ mergedPending = await waitForPendingActions({
4893
+ apiBaseUrl,
4894
+ sessions,
4895
+ completedIds,
4896
+ actionSessionMap,
4897
+ pendingSessionIdsRef,
4898
+ linkedTransferSessionIds,
4899
+ waitForLinkedTransferSession: options?.waitForLinkedTransferSession ?? false
4900
+ });
4901
+ continue;
4902
+ }
4903
+ }
3981
4904
  if (isBatchableAction(action)) {
3982
4905
  const batchable = getLeadingBatchableActions(
3983
4906
  mergedPending
@@ -4004,7 +4927,7 @@ function useAuthorizationOrchestrator(deps) {
4004
4927
  pendingSessionIdsRef
4005
4928
  });
4006
4929
  await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
4007
- markPendingPermit2AsOneTapCompleted(sessions, oneTapCompletedActionIds);
4930
+ markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
4008
4931
  mergedPending = await waitForPendingActions({
4009
4932
  apiBaseUrl,
4010
4933
  sessions,
@@ -4035,7 +4958,7 @@ function useAuthorizationOrchestrator(deps) {
4035
4958
  pendingSessionIdsRef
4036
4959
  });
4037
4960
  await ingestPendingSessions(apiBaseUrl, sessions, actionSessionMap, pendingSessionIdsRef);
4038
- markPendingPermit2AsOneTapCompleted(sessions, oneTapCompletedActionIds);
4961
+ markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds);
4039
4962
  mergedPending = await waitForPendingActions({
4040
4963
  apiBaseUrl,
4041
4964
  sessions,
@@ -4107,7 +5030,7 @@ function useAuthorizationOrchestrator(deps) {
4107
5030
  continue;
4108
5031
  }
4109
5032
  }
4110
- const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2";
5033
+ const isPrePromptProbableActionType = action.type === "SIGN_PERMIT2" || action.type === "APPROVE_PERMIT2" || action.type === "APPROVE_SPL";
4111
5034
  if (isPrePromptProbableActionType && probeBeforePrompt && !preProbedIds.has(action.id)) {
4112
5035
  preProbedIds.add(action.id);
4113
5036
  appendDebug("info", `${action.type}: pre-prompt probe start`, {
@@ -4152,7 +5075,10 @@ function useAuthorizationOrchestrator(deps) {
4152
5075
  actionId: action.id,
4153
5076
  ownerSessionId
4154
5077
  });
4155
- const result = await authExecutor.executeAction(action, { sessionId: ownerSessionId });
5078
+ const result = await authExecutor.executeAction(action, {
5079
+ sessionId: ownerSessionId,
5080
+ onApproveSplConfirming: options?.onApproveSplConfirming
5081
+ });
4156
5082
  if (result.status === "success" && (action.type === "OPEN_PROVIDER" || action.type === "SWITCH_CHAIN")) {
4157
5083
  cachedBatchSupport = null;
4158
5084
  }
@@ -4392,7 +5318,24 @@ function getMergedPending(sessions, completedIds) {
4392
5318
  for (const { session } of sessions) {
4393
5319
  allActions.push(...getPendingActions(session, completedIds));
4394
5320
  }
4395
- return allActions.sort((a, b) => a.orderIndex - b.orderIndex);
5321
+ const hasPendingSvmSetupAction = allActions.some((action) => isSvmSetupAction(action));
5322
+ return allActions.sort((a, b) => {
5323
+ const leftIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(a);
5324
+ const rightIsDeferredSvmBridge = hasPendingSvmSetupAction && isSvmTransferBridgeAction(b);
5325
+ if (leftIsDeferredSvmBridge !== rightIsDeferredSvmBridge) {
5326
+ return leftIsDeferredSvmBridge ? 1 : -1;
5327
+ }
5328
+ return a.orderIndex - b.orderIndex;
5329
+ });
5330
+ }
5331
+ function isSvmSetupAction(action) {
5332
+ if (action.type === "APPROVE_SPL") {
5333
+ return true;
5334
+ }
5335
+ return action.type === "OPEN_PROVIDER" && action.metadata?.chainFamily === "svm";
5336
+ }
5337
+ function isSvmTransferBridgeAction(action) {
5338
+ return action.type === "EXECUTE_BRIDGE" && action.metadata?.chainFamily === "svm";
4396
5339
  }
4397
5340
  async function waitForPendingActions(params) {
4398
5341
  const {
@@ -4466,14 +5409,19 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
4466
5409
  actionId: action.id,
4467
5410
  ownerSessionId
4468
5411
  });
5412
+ const label = `reportActionCompletion ${action.type}`;
4469
5413
  try {
4470
5414
  const reportedSession = await withWatchdog(
4471
- reportActionCompletion(
4472
- apiBaseUrl,
4473
- action.id,
4474
- result.data ?? {}
5415
+ withTimeout(
5416
+ reportActionCompletion(
5417
+ apiBaseUrl,
5418
+ action.id,
5419
+ result.data ?? {}
5420
+ ),
5421
+ REPORT_COMPLETION_TIMEOUT_MS,
5422
+ label
4475
5423
  ),
4476
- `reportActionCompletion ${action.type}`
5424
+ label
4477
5425
  );
4478
5426
  console.info("[blink-sdk][orchestrator] Action completion reported.", {
4479
5427
  actionId: action.id,
@@ -4489,16 +5437,22 @@ async function reportActionCompletionWithLogging(apiBaseUrl, action, ownerSessio
4489
5437
  });
4490
5438
  return reportedSession;
4491
5439
  } catch (err) {
5440
+ const timedOut = err instanceof PromiseTimeoutError;
4492
5441
  console.warn("[blink-sdk][orchestrator] Failed to report action completion.", {
4493
5442
  actionId: action.id,
4494
5443
  actionType: action.type,
4495
5444
  ownerSessionId,
5445
+ timedOut,
4496
5446
  error: err instanceof Error ? err.message : String(err)
4497
5447
  });
4498
- appendDebug("error", `orchestrator:reportActionCompletion failed ${action.type}`, {
5448
+ appendDebug(timedOut ? "warn" : "error", `orchestrator:reportActionCompletion ${timedOut ? "timed out" : "failed"} ${action.type}`, {
4499
5449
  actionId: action.id,
5450
+ timedOut,
4500
5451
  error: err instanceof Error ? err.message : String(err)
4501
5452
  });
5453
+ if (timedOut) {
5454
+ throw new Error(REPORT_COMPLETION_TIMEOUT_MESSAGE);
5455
+ }
4502
5456
  throw err;
4503
5457
  }
4504
5458
  }
@@ -4531,10 +5485,10 @@ function updateTrackedSession(sessions, ownerSessionId, reportedSession, actionS
4531
5485
  }
4532
5486
  }
4533
5487
  }
4534
- function markPendingPermit2AsOneTapCompleted(sessions, oneTapCompletedActionIds) {
5488
+ function markPendingOneTapActionsCompleted(sessions, oneTapCompletedActionIds) {
4535
5489
  for (const { session } of sessions) {
4536
5490
  for (const action of session.actions) {
4537
- if (action.type === "SIGN_PERMIT2" && action.status === "PENDING") {
5491
+ if ((action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL") && action.status === "PENDING") {
4538
5492
  oneTapCompletedActionIds.add(action.id);
4539
5493
  }
4540
5494
  }
@@ -4587,6 +5541,8 @@ function screenForPhase(phase) {
4587
5541
  case "completed":
4588
5542
  case "failed":
4589
5543
  return "success";
5544
+ case "amount-too-low":
5545
+ return "amount-too-low";
4590
5546
  }
4591
5547
  }
4592
5548
 
@@ -4636,6 +5592,12 @@ function hasActiveWallet(accounts) {
4636
5592
  return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
4637
5593
  }
4638
5594
  function resolveTerminalPhase(state) {
5595
+ if (state.amountTooLow != null) {
5596
+ return {
5597
+ step: "amount-too-low",
5598
+ minAmountUsd: state.amountTooLow.minAmountUsd
5599
+ };
5600
+ }
4639
5601
  const transferCompleted = state.transfer?.status === "COMPLETED";
4640
5602
  const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
4641
5603
  if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
@@ -4789,7 +5751,8 @@ function createInitialState(config) {
4789
5751
  setupDepositToken: null,
4790
5752
  setupDepositConfirmed: false,
4791
5753
  guestWalletPrepared: null,
4792
- guestWalletDeeplinksPreparing: false
5754
+ guestWalletDeeplinksPreparing: false,
5755
+ amountTooLow: null
4793
5756
  };
4794
5757
  }
4795
5758
  function clearAuthenticatedSessionState(state) {
@@ -4826,7 +5789,8 @@ function clearAuthenticatedSessionState(state) {
4826
5789
  setupDepositToken: null,
4827
5790
  setupDepositConfirmed: false,
4828
5791
  guestWalletPrepared: null,
4829
- guestWalletDeeplinksPreparing: false
5792
+ guestWalletDeeplinksPreparing: false,
5793
+ amountTooLow: null
4830
5794
  };
4831
5795
  }
4832
5796
  function paymentReducer(state, action) {
@@ -5165,6 +6129,13 @@ function applyAction(state, action) {
5165
6129
  };
5166
6130
  case "SET_ERROR":
5167
6131
  return { ...state, error: action.error };
6132
+ case "AMOUNT_TOO_LOW":
6133
+ return {
6134
+ ...state,
6135
+ amountTooLow: { minAmountUsd: action.minAmountUsd },
6136
+ creatingTransfer: false,
6137
+ error: null
6138
+ };
5168
6139
  case "SET_ONE_TAP_LIMIT_SAVED_DURING_SETUP":
5169
6140
  return { ...state, oneTapLimitSavedDuringSetup: action.saved };
5170
6141
  case "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET":
@@ -5198,7 +6169,8 @@ function applyAction(state, action) {
5198
6169
  setupDepositConfirmed: false,
5199
6170
  setupFlowScreen: null,
5200
6171
  guestWalletPrepared: null,
5201
- guestWalletDeeplinksPreparing: false
6172
+ guestWalletDeeplinksPreparing: false,
6173
+ amountTooLow: null
5202
6174
  };
5203
6175
  case "LOGOUT":
5204
6176
  return {
@@ -5545,6 +6517,17 @@ var RABBY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="70 88 386 334"
5545
6517
  </linearGradient>
5546
6518
  </defs>
5547
6519
  </svg>`;
6520
+ var PHANTOM_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200" fill="none">
6521
+ <g clip-path="url(#clip0_2596_138580)">
6522
+ <rect y="-0.000976562" width="1200" height="1200" fill="#AB9FF2"/>
6523
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M522.218 764.813C475.101 837.011 396.147 928.377 291.089 928.377C241.425 928.377 193.671 907.932 193.671 819.121C193.671 592.942 502.479 242.812 789.003 242.812C952.003 242.812 1016.95 355.901 1016.95 484.325C1016.95 649.167 909.979 837.65 803.647 837.65C769.901 837.65 753.346 819.121 753.346 789.731C753.346 782.064 754.62 773.758 757.167 764.813C720.874 826.788 650.835 884.292 585.253 884.292C537.499 884.292 513.304 854.262 513.304 812.093C513.304 796.759 516.487 780.786 522.218 764.813ZM769.035 479.871C769.035 517.293 746.956 536.003 722.258 536.003C697.185 536.003 675.481 517.293 675.481 479.871C675.481 442.449 697.185 423.738 722.258 423.738C746.956 423.738 769.035 442.449 769.035 479.871ZM909.367 479.872C909.367 517.294 887.288 536.005 862.59 536.005C837.517 536.005 815.813 517.294 815.813 479.872C815.813 442.45 837.517 423.74 862.59 423.74C887.288 423.74 909.367 442.45 909.367 479.872Z" fill="#FFFDF8"/>
6524
+ </g>
6525
+ <defs>
6526
+ <clipPath id="clip0_2596_138580">
6527
+ <rect y="-0.000976562" width="1200" height="1200" rx="600" fill="white"/>
6528
+ </clipPath>
6529
+ </defs>
6530
+ </svg>`;
5548
6531
  var BLINK_SVG = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
5549
6532
  <g clip-path="url(#clip0_125_480)">
5550
6533
  <rect width="100" height="100" rx="18.5874" fill="black"/>
@@ -5587,11 +6570,13 @@ var POLYGON_CHAIN_LOGO = svgToDataUri(POLYGON_CHAIN_SVG);
5587
6570
  var ETHEREUM_CHAIN_LOGO = "https://assets.relay.link/icons/1/light.png";
5588
6571
  var MEGAETH_CHAIN_LOGO = svgToDataUri(MEGAETH_CHAIN_SVG);
5589
6572
  var MONAD_CHAIN_LOGO = svgToDataUri(MONAD_CHAIN_SVG);
6573
+ var SOLANA_CHAIN_LOGO = "https://assets.relay.link/icons/792703809/light.png";
5590
6574
  var USDM_LOGO = svgToDataUri(USDM_TOKEN_SVG);
5591
6575
  var METAMASK_LOGO = svgToDataUri(METAMASK_SVG);
5592
6576
  var TRUST_WALLET_LOGO = svgToDataUri(TRUST_WALLET_SVG);
5593
6577
  var OKX_WALLET_LOGO = svgToDataUri(OKX_WALLET_SVG);
5594
6578
  var RABBY_LOGO = svgToDataUri(RABBY_SVG);
6579
+ var PHANTOM_LOGO = svgToDataUri(PHANTOM_SVG);
5595
6580
  var USDC_LOGO = svgToDataUri(USDC_SVG);
5596
6581
  var TETHER_LOGO = svgToDataUri(TETHER_SVG);
5597
6582
  var KNOWN_LOGOS = {
@@ -5600,6 +6585,7 @@ var KNOWN_LOGOS = {
5600
6585
  "base account": BASE_LOGO,
5601
6586
  "base app": BASE_LOGO,
5602
6587
  "okx wallet": OKX_WALLET_LOGO,
6588
+ phantom: PHANTOM_LOGO,
5603
6589
  rabby: RABBY_LOGO,
5604
6590
  "trust wallet": TRUST_WALLET_LOGO
5605
6591
  };
@@ -5612,15 +6598,20 @@ var TOKEN_LOGOS = {
5612
6598
  var CHAIN_LOGOS = {
5613
6599
  base: BASE_CHAIN_LOGO,
5614
6600
  ethereum: ETHEREUM_CHAIN_LOGO,
6601
+ "ethereum mainnet": ETHEREUM_CHAIN_LOGO,
5615
6602
  polygon: POLYGON_CHAIN_LOGO,
6603
+ "polygon mainnet": POLYGON_CHAIN_LOGO,
5616
6604
  arbitrum: ARBITRUM_CHAIN_LOGO,
5617
6605
  "arbitrum one": ARBITRUM_CHAIN_LOGO,
6606
+ "arbitrum mainnet": ARBITRUM_CHAIN_LOGO,
5618
6607
  bnb: BNB_CHAIN_LOGO,
5619
6608
  "bnb chain": BNB_CHAIN_LOGO,
5620
6609
  "bnb smart chain": BNB_CHAIN_LOGO,
5621
6610
  bsc: BNB_CHAIN_LOGO,
5622
6611
  megaeth: MEGAETH_CHAIN_LOGO,
5623
- monad: MONAD_CHAIN_LOGO
6612
+ monad: MONAD_CHAIN_LOGO,
6613
+ solana: SOLANA_CHAIN_LOGO,
6614
+ "solana mainnet": SOLANA_CHAIN_LOGO
5624
6615
  };
5625
6616
  function ScreenLayout({ children, footer }) {
5626
6617
  const { tokens, theme } = useBlinkConfig();
@@ -5868,6 +6859,8 @@ function PrimaryButton({
5868
6859
  children,
5869
6860
  onClick,
5870
6861
  href,
6862
+ target,
6863
+ rel,
5871
6864
  disabled,
5872
6865
  loading,
5873
6866
  loadingText = "Please wait...",
@@ -5920,6 +6913,8 @@ function PrimaryButton({
5920
6913
  "a",
5921
6914
  {
5922
6915
  href,
6916
+ target,
6917
+ rel,
5923
6918
  onClick,
5924
6919
  style: {
5925
6920
  ...buttonStyle2(tokens, { disabled: false, loading: false }),
@@ -6676,6 +7671,8 @@ function SourceRow(props) {
6676
7671
  "a",
6677
7672
  {
6678
7673
  href: props.href,
7674
+ target: props.target,
7675
+ rel: props.rel,
6679
7676
  onClick: props.onClick,
6680
7677
  onMouseEnter: () => setHovered(true),
6681
7678
  onMouseLeave: () => setHovered(false),
@@ -7199,6 +8196,22 @@ var closeButtonStyle2 = (tokens) => ({
7199
8196
  cursor: "pointer",
7200
8197
  padding: 0
7201
8198
  });
8199
+ function AmountTooLowScreen({
8200
+ minAmountUsd,
8201
+ onRetry,
8202
+ onClose
8203
+ }) {
8204
+ return /* @__PURE__ */ jsx(
8205
+ BlinkErrorScreen,
8206
+ {
8207
+ title: "Amount too low",
8208
+ message: `The minimum payment amount is $${minAmountUsd.toFixed(2)}.`,
8209
+ retryLabel: onRetry ? "Try Again" : void 0,
8210
+ onRetry,
8211
+ onClose
8212
+ }
8213
+ );
8214
+ }
7202
8215
  var RESEND_COOLDOWN_SECONDS = 30;
7203
8216
  function OtpVerifyScreen({
7204
8217
  maskedIdentifier,
@@ -7474,19 +8487,15 @@ var contentStyle5 = {
7474
8487
  gap: 16
7475
8488
  };
7476
8489
  var illustrationFrameStyle = {
7477
- flex: 1,
7478
- minHeight: 0,
8490
+ flex: "0 0 auto",
7479
8491
  width: "100%",
7480
- maxWidth: 329,
7481
8492
  display: "flex",
7482
8493
  alignItems: "center",
7483
8494
  justifyContent: "center"
7484
8495
  };
7485
8496
  var illustrationImgStyle = {
7486
- maxWidth: "100%",
7487
- maxHeight: "100%",
7488
- width: "auto",
7489
- height: "auto",
8497
+ width: 200,
8498
+ height: 200,
7490
8499
  objectFit: "contain",
7491
8500
  display: "block",
7492
8501
  pointerEvents: "none",
@@ -7494,7 +8503,7 @@ var illustrationImgStyle = {
7494
8503
  };
7495
8504
  var footerButtonStyle = {
7496
8505
  width: "100%",
7497
- paddingTop: 20,
8506
+ paddingTop: 48,
7498
8507
  paddingBottom: 36
7499
8508
  };
7500
8509
  var errorBannerStyle3 = (tokens) => ({
@@ -7568,19 +8577,17 @@ var contentStyle6 = {
7568
8577
  gap: 16
7569
8578
  };
7570
8579
  var illustrationFrameStyle2 = {
7571
- flex: 1,
7572
- minHeight: 0,
8580
+ flex: "0 1 auto",
7573
8581
  width: "100%",
7574
- maxWidth: 329,
8582
+ maxWidth: 280,
8583
+ height: "clamp(140px, 34vh, 248px)",
7575
8584
  display: "flex",
7576
8585
  alignItems: "center",
7577
8586
  justifyContent: "center"
7578
8587
  };
7579
8588
  var illustrationImgStyle2 = {
7580
- maxWidth: "100%",
7581
- maxHeight: "100%",
7582
- width: "auto",
7583
- height: "auto",
8589
+ width: "100%",
8590
+ height: "100%",
7584
8591
  objectFit: "contain",
7585
8592
  display: "block",
7586
8593
  pointerEvents: "none",
@@ -7724,6 +8731,36 @@ function triggerDeeplink(uri) {
7724
8731
  }
7725
8732
  }
7726
8733
 
8734
+ // src/walletDeeplinks.ts
8735
+ function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
8736
+ const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
8737
+ return matchedUri ?? fallbackUri;
8738
+ }
8739
+ function classifyWalletDeeplinkNavigation(uri) {
8740
+ if (isCustomSchemeUri(uri)) {
8741
+ return { navigationClass: "javascript" };
8742
+ }
8743
+ try {
8744
+ const parsed = new URL(uri);
8745
+ if (isBrowseUniversalLink(parsed)) {
8746
+ return {
8747
+ navigationClass: "iframe-escaping-native-anchor",
8748
+ anchorTarget: "_blank",
8749
+ anchorRel: "noopener noreferrer"
8750
+ };
8751
+ }
8752
+ } catch {
8753
+ return { navigationClass: "javascript" };
8754
+ }
8755
+ return { navigationClass: "javascript" };
8756
+ }
8757
+ function shouldOpenWithJavaScript(navigation) {
8758
+ return navigation.navigationClass === "javascript";
8759
+ }
8760
+ function isBrowseUniversalLink(parsed) {
8761
+ return parsed.protocol === "https:" && parsed.pathname.startsWith("/ul/browse/") && parsed.searchParams.has("ref");
8762
+ }
8763
+
7727
8764
  // src/sentry.ts
7728
8765
  var _mod;
7729
8766
  function captureException(error) {
@@ -7798,14 +8835,20 @@ function WalletPickerScreen({
7798
8835
  const rowLoader = isRowPreparing ? /* @__PURE__ */ jsx(Spinner, { size: 20 }) : void 0;
7799
8836
  if (usesDirectLinkCards) {
7800
8837
  if (directPreparedSession?.uri) {
8838
+ const navigation = classifyWalletDeeplinkNavigation(directPreparedSession.uri);
8839
+ const openWithJavaScript = shouldOpenWithJavaScript(navigation);
7801
8840
  return /* @__PURE__ */ jsx(
7802
8841
  SourceRow,
7803
8842
  {
7804
8843
  logo: logoSrc,
7805
8844
  name: provider.name,
7806
8845
  href: directPreparedSession.uri,
8846
+ target: navigation.anchorTarget,
8847
+ rel: navigation.anchorRel,
7807
8848
  onClick: (e) => {
7808
- e.preventDefault();
8849
+ if (openWithJavaScript) {
8850
+ e.preventDefault();
8851
+ }
7809
8852
  setSelectedProviderId(provider.id);
7810
8853
  if (directPreparedSession.sessionId) {
7811
8854
  void setAuthorizationSessionProvider(
@@ -7816,7 +8859,9 @@ function WalletPickerScreen({
7816
8859
  captureException(err);
7817
8860
  });
7818
8861
  }
7819
- openDeeplink(directPreparedSession.uri);
8862
+ if (openWithJavaScript) {
8863
+ openDeeplink(directPreparedSession.uri);
8864
+ }
7820
8865
  void onSelectProvider(provider.id, directPreparedSession);
7821
8866
  }
7822
8867
  },
@@ -8674,6 +9719,7 @@ function SelectDepositSourceScreen({
8674
9719
  const rowAccountId = (opt) => opt.accountId ?? fallbackAccountId;
8675
9720
  const isSelected = (opt) => !!selectedTokenSymbol && !!selectedChainName && !!selectedWalletId && opt.symbol === selectedTokenSymbol && opt.chainName === selectedChainName && opt.walletId === selectedWalletId;
8676
9721
  const tokenOptionKey = (opt) => `${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`;
9722
+ const hasPendingMobileSelection = pendingMobileSelection != null;
8677
9723
  const handleAuthorizedPick = (opt) => {
8678
9724
  onPickToken(opt.symbol, opt.chainName, opt.walletId);
8679
9725
  onDone();
@@ -8703,14 +9749,20 @@ function SelectDepositSourceScreen({
8703
9749
  handleAuthorizedPick(opt);
8704
9750
  };
8705
9751
  const footerProviderName = preparedAuthorization?.providerName ?? pendingMobileSelection?.providerName ?? "your wallet";
9752
+ const preparedAuthorizationNavigation = preparedAuthorization ? classifyWalletDeeplinkNavigation(preparedAuthorization.deeplinkUri) : null;
9753
+ const preparedAuthorizationOpensWithJavaScript = preparedAuthorizationNavigation ? shouldOpenWithJavaScript(preparedAuthorizationNavigation) : false;
8706
9754
  const footer = pendingMobileSelection ? preparedAuthorization ? /* @__PURE__ */ jsx(
8707
9755
  PrimaryButton,
8708
9756
  {
8709
9757
  href: preparedAuthorization.deeplinkUri,
9758
+ target: preparedAuthorizationNavigation?.anchorTarget,
9759
+ rel: preparedAuthorizationNavigation?.anchorRel,
8710
9760
  onClick: (event) => {
8711
- event.preventDefault();
8712
9761
  onCommitTokenAuthorization?.(preparedAuthorization);
8713
- openDeeplink(preparedAuthorization.deeplinkUri);
9762
+ if (preparedAuthorizationOpensWithJavaScript) {
9763
+ event.preventDefault();
9764
+ openDeeplink(preparedAuthorization.deeplinkUri);
9765
+ }
8714
9766
  },
8715
9767
  children: `Continue in ${footerProviderName}`
8716
9768
  }
@@ -8760,7 +9812,7 @@ function SelectDepositSourceScreen({
8760
9812
  symbol: opt.symbol,
8761
9813
  chainName: opt.chainName,
8762
9814
  balance: opt.balance,
8763
- selected: isSelected(opt),
9815
+ selected: !hasPendingMobileSelection && isSelected(opt),
8764
9816
  onClick: () => handleAuthorizedPick(opt)
8765
9817
  },
8766
9818
  `auth-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
@@ -8774,7 +9826,7 @@ function SelectDepositSourceScreen({
8774
9826
  chainName: opt.chainName,
8775
9827
  balance: opt.balance,
8776
9828
  requiresAuth: true,
8777
- selected: isSelected(opt) || pendingMobileSelection?.key === tokenOptionKey(opt),
9829
+ selected: hasPendingMobileSelection ? pendingMobileSelection?.key === tokenOptionKey(opt) : isSelected(opt),
8778
9830
  onClick: () => handleRequiresAuthPick(account, opt)
8779
9831
  },
8780
9832
  `req-${opt.accountId ?? ""}-${opt.chainName}-${opt.symbol}-${opt.walletId ?? ""}`
@@ -9959,11 +11011,13 @@ function OpenWalletScreen({
9959
11011
  !loading && onRetryStatus != null && error == null,
9960
11012
  deeplinkUri
9961
11013
  );
11014
+ const deeplinkNavigation = classifyWalletDeeplinkNavigation(deeplinkUri);
11015
+ const openWithJavaScript = shouldOpenWithJavaScript(deeplinkNavigation);
9962
11016
  useEffect(() => {
9963
- if (!useDeeplink || loading || !deeplinkUri || autoOpenedRef.current === deeplinkUri) return;
11017
+ if (!useDeeplink || loading || !deeplinkUri || !openWithJavaScript || autoOpenedRef.current === deeplinkUri) return;
9964
11018
  autoOpenedRef.current = deeplinkUri;
9965
11019
  triggerDeeplink(deeplinkUri);
9966
- }, [useDeeplink, loading, deeplinkUri]);
11020
+ }, [useDeeplink, loading, deeplinkUri, openWithJavaScript]);
9967
11021
  const handleOpen = useCallback(() => {
9968
11022
  openDeeplink(deeplinkUri);
9969
11023
  }, [deeplinkUri]);
@@ -10003,7 +11057,10 @@ function OpenWalletScreen({
10003
11057
  /* @__PURE__ */ jsxs(
10004
11058
  PrimaryButton,
10005
11059
  {
10006
- onClick: handleOpen,
11060
+ href: !openWithJavaScript ? deeplinkUri : void 0,
11061
+ target: deeplinkNavigation.anchorTarget,
11062
+ rel: deeplinkNavigation.anchorRel,
11063
+ onClick: openWithJavaScript ? handleOpen : void 0,
10007
11064
  loading,
10008
11065
  loadingText: "Preparing authorization\u2026",
10009
11066
  children: [
@@ -10095,6 +11152,7 @@ var inlineWaitStyle = (color) => ({
10095
11152
  });
10096
11153
  function ConfirmSignScreen({
10097
11154
  walletName,
11155
+ chainFamily,
10098
11156
  signing,
10099
11157
  error,
10100
11158
  onSign,
@@ -10103,26 +11161,28 @@ function ConfirmSignScreen({
10103
11161
  const { tokens } = useBlinkConfig();
10104
11162
  const displayName = walletName ?? "your wallet";
10105
11163
  const logoSrc = walletName ? KNOWN_LOGOS[walletName.toLowerCase()] : void 0;
11164
+ const isSvmTransfer = chainFamily === "svm";
11165
+ const heading = isSvmTransfer ? "Ready for passkey approval" : "Wallet authorized";
11166
+ const subtitle = isSvmTransfer ? `${displayName} setup is complete. Your Solana payment signs with your passkey only.` : `${displayName} approved the connection. Tap below to confirm your payment.`;
11167
+ const badge = isSvmTransfer ? "Wallet setup complete" : "Authorization complete";
11168
+ const hint = isSvmTransfer ? "No wallet pop-up is needed for this transfer" : "You may be prompted for biometric verification";
10106
11169
  return /* @__PURE__ */ jsxs(
10107
11170
  ScreenLayout,
10108
11171
  {
10109
11172
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
10110
11173
  /* @__PURE__ */ jsx(PrimaryButton, { onClick: onSign, disabled: signing, children: "Confirm payment" }),
10111
11174
  error && /* @__PURE__ */ jsx("p", { style: errorStyle2(tokens.textMuted), children: error }),
10112
- !error && /* @__PURE__ */ jsx("p", { style: hintStyle(tokens.textMuted), children: "You may be prompted for biometric verification" })
11175
+ !error && /* @__PURE__ */ jsx("p", { style: hintStyle(tokens.textMuted), children: hint })
10113
11176
  ] }),
10114
11177
  children: [
10115
11178
  /* @__PURE__ */ jsx(ScreenHeader, { onLogout }),
10116
11179
  /* @__PURE__ */ jsxs("div", { style: contentStyle15, children: [
10117
11180
  logoSrc ? /* @__PURE__ */ jsx("img", { src: logoSrc, alt: displayName, style: logoStyle3 }) : /* @__PURE__ */ jsx(Spinner, { size: 48 }),
10118
- /* @__PURE__ */ jsx("h2", { style: headingStyle14(tokens.text), children: "Wallet authorized" }),
10119
- /* @__PURE__ */ jsxs("p", { style: subtitleStyle11(tokens.textSecondary), children: [
10120
- displayName,
10121
- " approved the connection. Tap below to confirm your payment."
10122
- ] }),
11181
+ /* @__PURE__ */ jsx("h2", { style: headingStyle14(tokens.text), children: heading }),
11182
+ /* @__PURE__ */ jsx("p", { style: subtitleStyle11(tokens.textSecondary), children: subtitle }),
10123
11183
  /* @__PURE__ */ jsxs("div", { style: successBadgeStyle(tokens), children: [
10124
11184
  /* @__PURE__ */ jsx("span", { style: checkmarkStyle, children: "\u2713" }),
10125
- /* @__PURE__ */ jsx("span", { children: "Authorization complete" })
11185
+ /* @__PURE__ */ jsx("span", { children: badge })
10126
11186
  ] })
10127
11187
  ] })
10128
11188
  ]
@@ -10865,6 +11925,7 @@ function buildConfirmSignScreenProps({
10865
11925
  }) {
10866
11926
  return {
10867
11927
  walletName: flow.state.providers.find((p) => p.id === flow.state.selectedProviderId)?.name ?? null,
11928
+ chainFamily: remote.pollingTransfer?.signPayload?.chainFamily ?? null,
10868
11929
  signing: remote.transferSigningSigning,
10869
11930
  error: flow.state.error || remote.transferSigningError,
10870
11931
  onSign: handlers.onConfirmSign,
@@ -11085,6 +12146,19 @@ function buildSuccessScreenProps({
11085
12146
  onLogout: authenticated ? handlers.onLogout : void 0
11086
12147
  };
11087
12148
  }
12149
+ function buildAmountTooLowScreenProps({
12150
+ flow,
12151
+ handlers
12152
+ }) {
12153
+ const { state, onDismiss } = flow;
12154
+ const minAmountUsd = state.phase.step === "amount-too-low" ? state.phase.minAmountUsd : state.amountTooLow?.minAmountUsd ?? flow.minTransferAmountUsd;
12155
+ const canRetry = flow.depositAmount == null;
12156
+ return {
12157
+ minAmountUsd,
12158
+ onRetry: canRetry ? handlers.onNewPayment : void 0,
12159
+ onClose: onDismiss
12160
+ };
12161
+ }
11088
12162
  function StepRenderer(props) {
11089
12163
  const screen = screenForPhase(props.flow.state.phase);
11090
12164
  return /* @__PURE__ */ jsx(StepRendererContent, { ...props, screen });
@@ -11152,6 +12226,8 @@ function StepRendererContent({
11152
12226
  return /* @__PURE__ */ jsx(SelectSourceScreen, { ...buildSelectSourceScreenProps(input) });
11153
12227
  case "success":
11154
12228
  return /* @__PURE__ */ jsx(SuccessScreen, { ...buildSuccessScreenProps(input) });
12229
+ case "amount-too-low":
12230
+ return /* @__PURE__ */ jsx(AmountTooLowScreen, { ...buildAmountTooLowScreenProps(input) });
11155
12231
  default: {
11156
12232
  const _exhaustive = screen;
11157
12233
  throw new Error(`Unhandled screen: ${_exhaustive}`);
@@ -11581,9 +12657,8 @@ function useTransferHandlers(deps) {
11581
12657
  destination
11582
12658
  ]);
11583
12659
  const handlePay = useCallback(async (payAmount, sourceOverrides) => {
11584
- const minUsd = effectiveMinTransferAmountUsd(depositAmount, minTransferAmountUsd);
11585
- if (isNaN(payAmount) || payAmount < minUsd) {
11586
- dispatch({ type: "SET_ERROR", error: `Minimum amount is $${minUsd.toFixed(2)}.` });
12660
+ if (isNaN(payAmount) || payAmount < minTransferAmountUsd) {
12661
+ dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
11587
12662
  return;
11588
12663
  }
11589
12664
  if (!sourceOverrides?.sourceId && !sourceId) {
@@ -11634,7 +12709,10 @@ function useTransferHandlers(deps) {
11634
12709
  );
11635
12710
  clearPendingTransferState();
11636
12711
  dispatch({ type: "TRANSFER_CREATED", transfer: stagedTransfer });
11637
- const signedTransfer2 = await transferSigning.signTransfer(stagedTransfer.id);
12712
+ const signedTransfer2 = await transferSigning.signTransfer(
12713
+ stagedTransfer.id,
12714
+ { knownSignPayload: stagedTransfer.signPayload }
12715
+ );
11638
12716
  dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer2 });
11639
12717
  polling.startPolling(stagedTransfer.id);
11640
12718
  return;
@@ -11665,7 +12743,10 @@ function useTransferHandlers(deps) {
11665
12743
  polling.startPolling(t.id);
11666
12744
  return;
11667
12745
  }
11668
- const signedTransfer = await transferSigning.signTransfer(t.id);
12746
+ const signedTransfer = await transferSigning.signTransfer(
12747
+ t.id,
12748
+ { knownSignPayload: t.signPayload }
12749
+ );
11669
12750
  dispatch({ type: "TRANSFER_SIGNED", transfer: signedTransfer });
11670
12751
  polling.startPolling(t.id);
11671
12752
  } catch (err) {
@@ -11872,12 +12953,6 @@ function useMobileFlowHandlers(dispatch, polling, reloadAccounts, pollingTransfe
11872
12953
  };
11873
12954
  }
11874
12955
 
11875
- // src/walletDeeplinks.ts
11876
- function resolveWalletDeeplink(providerId, walletDeeplinks, fallbackUri) {
11877
- const matchedUri = walletDeeplinks?.find((item) => item.providerId === providerId)?.uri;
11878
- return matchedUri ?? fallbackUri;
11879
- }
11880
-
11881
12956
  // src/hooks/providerSelectionGuards.ts
11882
12957
  function resolveSetupFlowDepositAmount({
11883
12958
  hasActiveWallet: hasActiveWallet2,
@@ -11944,9 +13019,10 @@ function buildDesktopDirectDuringSetupRunOptions() {
11944
13019
  alwaysPauseForOneTap: true
11945
13020
  };
11946
13021
  }
11947
- function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol) {
13022
+ function buildDesktopTokenAuthorizationRunOptions(chainName, tokenSymbol, _chainFamily = "evm") {
13023
+ const options = buildDesktopDirectDuringSetupRunOptions();
11948
13024
  return {
11949
- ...buildDesktopDirectDuringSetupRunOptions(),
13025
+ ...options,
11950
13026
  // Token authorization should batch like the first-time desktop setup flow:
11951
13027
  // auto-resolve SELECT_SOURCE now, then inject the transfer session during one-tap setup.
11952
13028
  autoResolveSource: { chainName, tokenSymbol }
@@ -12615,10 +13691,10 @@ function useProviderHandlers(deps) {
12615
13691
  dispatch({ type: "SET_ERROR", error: null });
12616
13692
  dispatch({ type: "SET_INCREASING_LIMIT", value: true });
12617
13693
  dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: depositAmount ?? 5 });
12618
- let desktopChainName = null;
13694
+ let desktopChain = null;
12619
13695
  if (!isMobile) {
12620
- desktopChainName = chains.find((chain) => chain.commonId === chainId)?.name ?? null;
12621
- if (!desktopChainName) {
13696
+ desktopChain = chains.find((chain) => chain.commonId === chainId) ?? null;
13697
+ if (!desktopChain) {
12622
13698
  dispatch({ type: "SET_INCREASING_LIMIT", value: false });
12623
13699
  dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
12624
13700
  dispatch({ type: "SET_ERROR", error: `No chain found for chainId ${chainId}` });
@@ -12627,7 +13703,7 @@ function useProviderHandlers(deps) {
12627
13703
  dispatch({
12628
13704
  type: "SET_SETUP_DEPOSIT_TOKEN",
12629
13705
  symbol: tokenSymbol,
12630
- chainName: desktopChainName,
13706
+ chainName: desktopChain.name,
12631
13707
  walletId: _walletId,
12632
13708
  tokenAddress,
12633
13709
  chainId
@@ -12661,7 +13737,11 @@ function useProviderHandlers(deps) {
12661
13737
  dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
12662
13738
  const result = await orchestrator.run(
12663
13739
  session.id,
12664
- buildDesktopTokenAuthorizationRunOptions(desktopChainName, tokenSymbol)
13740
+ buildDesktopTokenAuthorizationRunOptions(
13741
+ desktopChain.name,
13742
+ tokenSymbol,
13743
+ desktopChain.chainFamily
13744
+ )
12665
13745
  );
12666
13746
  if (result.status === "cancelled") {
12667
13747
  dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount: null });
@@ -14067,6 +15147,11 @@ function BlinkPaymentInner({
14067
15147
  dispatch({ type: "SYNC_AMOUNT", amount: depositAmount.toString() });
14068
15148
  }
14069
15149
  }, [depositAmount, dispatch]);
15150
+ useEffect(() => {
15151
+ if (depositAmount != null && depositAmount < minTransferAmountUsd) {
15152
+ dispatch({ type: "AMOUNT_TOO_LOW", minAmountUsd: minTransferAmountUsd });
15153
+ }
15154
+ }, [depositAmount, minTransferAmountUsd, dispatch]);
14070
15155
  useEffect(() => {
14071
15156
  if (!ready || effectiveAuthenticated) return;
14072
15157
  clearLocalSessionArtifacts();