@swype-org/react-sdk 0.2.242 → 0.2.263

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
@@ -6,7 +6,7 @@ import { mainnet, arbitrum, base, polygon, bsc, megaeth, monad, hyperliquid, bas
6
6
  import { injected, coinbaseWallet } from 'wagmi/connectors';
7
7
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
- import { getAccount, disconnect, reconnect, getWalletClient } from '@wagmi/core';
9
+ import { getAccount, disconnect, reconnect, getWalletClient, getConnectors } from '@wagmi/core';
10
10
  import { encodeFunctionData, recoverTypedDataAddress, decodeAbiParameters } from 'viem';
11
11
  import { parseErc6492Signature } from 'viem/utils';
12
12
  import * as QRCode from 'qrcode';
@@ -1130,12 +1130,225 @@ function useBlinkDepositAmount() {
1130
1130
  };
1131
1131
  }
1132
1132
 
1133
+ // src/walletConnectorResolver.ts
1134
+ function normalize(value) {
1135
+ return (value ?? "").trim().toLowerCase();
1136
+ }
1137
+ function buildTargetMatchers(target) {
1138
+ const values = [target.providerName, target.wagmiConnectorId].map(normalize).filter(Boolean);
1139
+ const aliases = new Set(values);
1140
+ for (const value of values) {
1141
+ if (value.includes("metamask")) {
1142
+ aliases.add("metamask");
1143
+ aliases.add("io.metamask");
1144
+ }
1145
+ if (value === "base" || value === "base account" || value === "base app" || value.includes("coinbase")) {
1146
+ aliases.add("base");
1147
+ aliases.add("coinbase");
1148
+ aliases.add("coinbasewalletsdk");
1149
+ }
1150
+ if (value.includes("trust")) {
1151
+ aliases.add("trust");
1152
+ aliases.add("trustwallet");
1153
+ }
1154
+ if (value.includes("okx")) {
1155
+ aliases.add("okx");
1156
+ aliases.add("okxwallet");
1157
+ aliases.add("com.okx.wallet");
1158
+ }
1159
+ if (value.includes("rabby")) {
1160
+ aliases.add("rabby");
1161
+ aliases.add("io.rabby");
1162
+ }
1163
+ if (value.includes("phantom")) {
1164
+ aliases.add("phantom");
1165
+ aliases.add("app.phantom");
1166
+ }
1167
+ if (value.includes("injected")) {
1168
+ aliases.add("injected");
1169
+ }
1170
+ }
1171
+ return [...aliases];
1172
+ }
1173
+ function connectorMatchesWallet(connector, target) {
1174
+ if (!connector) {
1175
+ return false;
1176
+ }
1177
+ const connectorId = normalize(connector.id);
1178
+ const connectorName = normalize(connector.name);
1179
+ if (target.wagmiConnectorId) {
1180
+ const targetConnectorId = normalize(target.wagmiConnectorId);
1181
+ if (connectorId === targetConnectorId) {
1182
+ return true;
1183
+ }
1184
+ }
1185
+ const matchers = buildTargetMatchers(target);
1186
+ if (matchers.length === 0) {
1187
+ return false;
1188
+ }
1189
+ return matchers.some(
1190
+ (matcher) => connectorId === matcher || connectorName === matcher || connectorId.includes(matcher) || connectorName.includes(matcher)
1191
+ );
1192
+ }
1193
+ function resolveWalletConnector(connectors, target) {
1194
+ if (target.wagmiConnectorId || target.providerName) {
1195
+ return connectors.find((connector) => connectorMatchesWallet(connector, target));
1196
+ }
1197
+ const metaMaskConnector = connectors.find((connector) => connectorMatchesWallet(
1198
+ connector,
1199
+ { wagmiConnectorId: "metamask" }
1200
+ ));
1201
+ return metaMaskConnector ?? connectors[0];
1202
+ }
1203
+
1204
+ // src/wagmiRevokeAndDisconnect.ts
1205
+ var REVOKE_METHOD = "wallet_revokePermissions";
1206
+ var REVOKE_PARAMS = [{ eth_accounts: {} }];
1207
+ var EIP6963_DISCOVERY_MS = 200;
1208
+ async function tryRevoke(provider, context) {
1209
+ if (!provider?.request) return false;
1210
+ try {
1211
+ await provider.request({ method: REVOKE_METHOD, params: REVOKE_PARAMS });
1212
+ console.info("[blink-sdk][revoke] ok", context);
1213
+ return true;
1214
+ } catch (err) {
1215
+ const code = err?.code;
1216
+ const message = err instanceof Error ? err.message : String(err);
1217
+ console.info("[blink-sdk][revoke] failed", { ...context, code, message });
1218
+ return false;
1219
+ }
1220
+ }
1221
+ async function getProviderForConnector(connector) {
1222
+ try {
1223
+ const provider = await connector.getProvider();
1224
+ return provider ?? null;
1225
+ } catch (err) {
1226
+ const message = err instanceof Error ? err.message : String(err);
1227
+ console.info("[blink-sdk][revoke] connector.getProvider() threw", {
1228
+ connectorId: connector.id,
1229
+ message
1230
+ });
1231
+ return null;
1232
+ }
1233
+ }
1234
+ async function discoverEip6963Providers(windowImpl, timeoutMs) {
1235
+ return new Promise((resolve) => {
1236
+ const seen = /* @__PURE__ */ new Map();
1237
+ const onAnnounce = (event) => {
1238
+ const detail = event.detail;
1239
+ if (!detail || !detail.info?.uuid) return;
1240
+ seen.set(detail.info.uuid, detail);
1241
+ };
1242
+ windowImpl.addEventListener("eip6963:announceProvider", onAnnounce);
1243
+ try {
1244
+ windowImpl.dispatchEvent(new Event("eip6963:requestProvider"));
1245
+ } catch {
1246
+ }
1247
+ const timer = setTimeout(() => {
1248
+ windowImpl.removeEventListener(
1249
+ "eip6963:announceProvider",
1250
+ onAnnounce
1251
+ );
1252
+ resolve([...seen.values()]);
1253
+ }, timeoutMs);
1254
+ if (typeof timer.unref === "function") {
1255
+ timer.unref();
1256
+ }
1257
+ });
1258
+ }
1259
+ async function probeAccounts(provider) {
1260
+ if (!provider.request) return [];
1261
+ try {
1262
+ const accounts = await provider.request({ method: "eth_accounts" });
1263
+ return Array.isArray(accounts) ? accounts : [];
1264
+ } catch {
1265
+ return [];
1266
+ }
1267
+ }
1268
+ async function revokeAndDisconnectConnector(deps) {
1269
+ const activeConnector = deps.getActiveConnector();
1270
+ if (activeConnector) {
1271
+ const provider = await getProviderForConnector(activeConnector);
1272
+ await tryRevoke(provider, {
1273
+ layer: 1,
1274
+ source: "wagmi-connector",
1275
+ rdns: activeConnector.id
1276
+ });
1277
+ await deps.disconnectFn(activeConnector).catch(() => {
1278
+ });
1279
+ return;
1280
+ }
1281
+ let recentRdns = null;
1282
+ try {
1283
+ recentRdns = await deps.getRecentConnectorId() ?? null;
1284
+ } catch {
1285
+ }
1286
+ if (recentRdns) {
1287
+ const matchedConnector = resolveWalletConnector(deps.listConnectors(), {
1288
+ wagmiConnectorId: recentRdns
1289
+ });
1290
+ if (matchedConnector) {
1291
+ const provider = await getProviderForConnector(matchedConnector);
1292
+ const revoked = await tryRevoke(provider, {
1293
+ layer: 2,
1294
+ source: "recent-connector",
1295
+ rdns: matchedConnector.id
1296
+ });
1297
+ await deps.disconnectFn(matchedConnector).catch(() => {
1298
+ });
1299
+ if (revoked) return;
1300
+ } else {
1301
+ console.info("[blink-sdk][revoke] recentConnectorId has no matching connector", {
1302
+ recentRdns
1303
+ });
1304
+ }
1305
+ }
1306
+ const win = deps.windowImpl ?? (typeof window === "undefined" ? null : window);
1307
+ if (!win) {
1308
+ console.warn("[blink-sdk][revoke] no candidate found and no window for EIP-6963 fallback");
1309
+ return;
1310
+ }
1311
+ const announces = await discoverEip6963Providers(
1312
+ win,
1313
+ deps.eip6963TimeoutMs ?? EIP6963_DISCOVERY_MS
1314
+ );
1315
+ if (announces.length === 0) {
1316
+ console.warn("[blink-sdk][revoke] EIP-6963 enumeration found no providers");
1317
+ return;
1318
+ }
1319
+ let anyRevoked = false;
1320
+ for (const detail of announces) {
1321
+ const accounts = await probeAccounts(detail.provider);
1322
+ if (accounts.length === 0) continue;
1323
+ const ok = await tryRevoke(detail.provider, {
1324
+ layer: 3,
1325
+ source: "eip6963-broadcast",
1326
+ rdns: detail.info.rdns
1327
+ });
1328
+ if (ok) anyRevoked = true;
1329
+ }
1330
+ if (!anyRevoked) {
1331
+ console.warn("[blink-sdk][revoke] EIP-6963 enumeration found no providers with a grant");
1332
+ }
1333
+ }
1334
+ async function revokeAndDisconnectActiveWagmiConnector(wagmiConfig) {
1335
+ await revokeAndDisconnectConnector({
1336
+ getActiveConnector: () => getAccount(wagmiConfig).connector,
1337
+ getRecentConnectorId: async () => {
1338
+ const value = await wagmiConfig.storage?.getItem("recentConnectorId");
1339
+ return typeof value === "string" ? value : null;
1340
+ },
1341
+ listConnectors: () => getConnectors(wagmiConfig),
1342
+ disconnectFn: (connector) => disconnect(wagmiConfig, { connector })
1343
+ });
1344
+ }
1345
+
1133
1346
  // src/otherWalletConnect.ts
1134
1347
  function findWalletConnectConnector(connectors) {
1135
1348
  return connectors.find((connector) => {
1136
- const id = normalize(connector.id);
1137
- const name = normalize(connector.name);
1138
- const type = normalize(connector.type);
1349
+ const id = normalize2(connector.id);
1350
+ const name = normalize2(connector.name);
1351
+ const type = normalize2(connector.type);
1139
1352
  return id === "walletconnect" || id === "wallet-connect" || name === "walletconnect" || name === "wallet connect" || type === "walletconnect";
1140
1353
  });
1141
1354
  }
@@ -1152,9 +1365,9 @@ function findReownExtensionConnector(connectors, wallet) {
1152
1365
  const eligible = connectors.filter((connector) => !findWalletConnectConnector([connector]));
1153
1366
  const match = eligible.find((connector) => {
1154
1367
  const connectorValues = [
1155
- normalize(connector.id),
1156
- normalize(connector.name),
1157
- normalize(connector.type)
1368
+ normalize2(connector.id),
1369
+ normalize2(connector.name),
1370
+ normalize2(connector.type)
1158
1371
  ].filter(Boolean);
1159
1372
  return targets.some(
1160
1373
  (target) => connectorValues.some(
@@ -1183,8 +1396,8 @@ async function findReownExtensionConnectorAsync(connectors, wallet, bridgeClient
1183
1396
  });
1184
1397
  return void 0;
1185
1398
  }
1186
- const targetRdns = normalize(resolved.rdns);
1187
- const match = connectors.find((connector) => normalize(connector.id) === targetRdns);
1399
+ const targetRdns = normalize2(resolved.rdns);
1400
+ const match = connectors.find((connector) => normalize2(connector.id) === targetRdns);
1188
1401
  console.info("[blink-bridge] findReownExtensionConnectorAsync \u2192 match", {
1189
1402
  walletName: wallet.name,
1190
1403
  flag: resolved.flag,
@@ -1202,84 +1415,59 @@ function getReownExtensionTargets(wallet) {
1202
1415
  return [...targets];
1203
1416
  }
1204
1417
  function addTarget(targets, value) {
1205
- const normalized = normalize(value);
1418
+ const normalized = normalize2(value);
1206
1419
  if (normalized) {
1207
1420
  targets.add(normalized);
1208
1421
  }
1209
1422
  }
1210
- function normalize(value) {
1423
+ function normalize2(value) {
1211
1424
  return (value ?? "").trim().toLowerCase().replaceAll(/[^a-z0-9.]/g, "");
1212
1425
  }
1213
1426
 
1214
- // src/walletConnectorResolver.ts
1215
- function normalize2(value) {
1216
- return (value ?? "").trim().toLowerCase();
1217
- }
1218
- function buildTargetMatchers(target) {
1219
- const values = [target.providerName, target.wagmiConnectorId].map(normalize2).filter(Boolean);
1220
- const aliases = new Set(values);
1221
- for (const value of values) {
1222
- if (value.includes("metamask")) {
1223
- aliases.add("metamask");
1224
- aliases.add("io.metamask");
1225
- }
1226
- if (value === "base" || value === "base account" || value === "base app" || value.includes("coinbase")) {
1227
- aliases.add("base");
1228
- aliases.add("coinbase");
1229
- aliases.add("coinbasewalletsdk");
1230
- }
1231
- if (value.includes("trust")) {
1232
- aliases.add("trust");
1233
- aliases.add("trustwallet");
1234
- }
1235
- if (value.includes("okx")) {
1236
- aliases.add("okx");
1237
- aliases.add("okxwallet");
1238
- aliases.add("com.okx.wallet");
1239
- }
1240
- if (value.includes("rabby")) {
1241
- aliases.add("rabby");
1242
- aliases.add("io.rabby");
1243
- }
1244
- if (value.includes("phantom")) {
1245
- aliases.add("phantom");
1246
- aliases.add("app.phantom");
1247
- }
1248
- if (value.includes("injected")) {
1249
- aliases.add("injected");
1250
- }
1251
- }
1252
- return [...aliases];
1253
- }
1254
- function connectorMatchesWallet(connector, target) {
1255
- if (!connector) {
1256
- return false;
1257
- }
1258
- const connectorId = normalize2(connector.id);
1259
- const connectorName = normalize2(connector.name);
1260
- if (target.wagmiConnectorId) {
1261
- const targetConnectorId = normalize2(target.wagmiConnectorId);
1262
- if (connectorId === targetConnectorId) {
1263
- return true;
1264
- }
1265
- }
1266
- const matchers = buildTargetMatchers(target);
1267
- if (matchers.length === 0) {
1268
- return false;
1269
- }
1270
- return matchers.some(
1271
- (matcher) => connectorId === matcher || connectorName === matcher || connectorId.includes(matcher) || connectorName.includes(matcher)
1272
- );
1273
- }
1274
- function resolveWalletConnector(connectors, target) {
1275
- if (target.wagmiConnectorId || target.providerName) {
1276
- return connectors.find((connector) => connectorMatchesWallet(connector, target));
1427
+ // src/resolveScreen.ts
1428
+ function screenForPhase(phase) {
1429
+ switch (phase.step) {
1430
+ case "initializing":
1431
+ case "data-loading":
1432
+ return "loading";
1433
+ case "manual-transfer":
1434
+ return "manual-transfer";
1435
+ case "login":
1436
+ return "login";
1437
+ case "deposit-options":
1438
+ return "deposit-options";
1439
+ case "wallet-picker":
1440
+ return "wallet-picker";
1441
+ case "wallet-setup":
1442
+ if (phase.mobile) {
1443
+ return "open-wallet";
1444
+ }
1445
+ if (phase.desktopWait) {
1446
+ return "open-wallet";
1447
+ }
1448
+ return "setup-deposit";
1449
+ case "select-source":
1450
+ return phase.isDesktop ? "setup" : "select-source";
1451
+ case "one-tap-setup":
1452
+ return "setup";
1453
+ case "token-picker":
1454
+ return "token-picker";
1455
+ case "guest-source-picker":
1456
+ return "guest-source-picker";
1457
+ case "deposit":
1458
+ return "deposit";
1459
+ case "processing":
1460
+ return "processing";
1461
+ case "confirm-sign":
1462
+ return "confirm-sign";
1463
+ case "completed":
1464
+ case "failed":
1465
+ return "success";
1466
+ case "amount-too-low":
1467
+ return "amount-too-low";
1468
+ case "enter-amount":
1469
+ return "enter-amount";
1277
1470
  }
1278
- const metaMaskConnector = connectors.find((connector) => connectorMatchesWallet(
1279
- connector,
1280
- { wagmiConnectorId: "metamask" }
1281
- ));
1282
- return metaMaskConnector ?? connectors[0];
1283
1471
  }
1284
1472
 
1285
1473
  // src/passkey-delegation.ts
@@ -1723,8 +1911,9 @@ async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
1723
1911
  return await res.json();
1724
1912
  }
1725
1913
  async function createAccount(apiBaseUrl, token, params) {
1914
+ const provisionalId = params.id ?? crypto.randomUUID();
1726
1915
  const body = {
1727
- id: params.id ?? crypto.randomUUID(),
1916
+ id: provisionalId,
1728
1917
  name: params.name,
1729
1918
  credentialId: params.credentialId
1730
1919
  };
@@ -1752,7 +1941,8 @@ async function createAccount(apiBaseUrl, token, params) {
1752
1941
  body: JSON.stringify(body)
1753
1942
  });
1754
1943
  if (!res.ok) await throwApiError(res);
1755
- return await res.json();
1944
+ const account = await res.json();
1945
+ return { ...account, id: account.id ?? provisionalId };
1756
1946
  }
1757
1947
  async function createAccountAuthorizationSession(apiBaseUrl, token, accountId, credentialId, options) {
1758
1948
  const body = { credentialId };
@@ -7345,289 +7535,706 @@ function EffectiveDepositAmountProvider({
7345
7535
  return /* @__PURE__ */ jsx(EffectiveDepositAmountContext.Provider, { value: memoValue, children });
7346
7536
  }
7347
7537
 
7348
- // src/resolveScreen.ts
7349
- function screenForPhase(phase) {
7350
- switch (phase.step) {
7351
- case "initializing":
7352
- case "data-loading":
7353
- return "loading";
7354
- case "manual-transfer":
7355
- return "manual-transfer";
7356
- case "login":
7357
- return "login";
7358
- case "deposit-options":
7359
- return "deposit-options";
7360
- case "wallet-picker":
7361
- return "wallet-picker";
7362
- case "wallet-setup":
7363
- if (phase.mobile) {
7364
- return "open-wallet";
7365
- }
7366
- if (phase.desktopWait) {
7367
- return "open-wallet";
7368
- }
7369
- return "setup-deposit";
7370
- case "select-source":
7371
- return phase.isDesktop ? "setup" : "select-source";
7372
- case "one-tap-setup":
7373
- return "setup";
7374
- case "token-picker":
7375
- return "token-picker";
7376
- case "guest-source-picker":
7377
- return "guest-source-picker";
7378
- case "deposit":
7379
- return "deposit";
7380
- case "processing":
7381
- return "processing";
7382
- case "confirm-sign":
7383
- return "confirm-sign";
7538
+ // src/manualTransferUtils.ts
7539
+ function screenForSession(session) {
7540
+ if (!session) return "source-selector";
7541
+ switch (session.status) {
7542
+ case "awaiting_deposit":
7543
+ return "awaiting-deposit";
7544
+ case "deposit_received":
7545
+ return "deposit-received";
7546
+ case "routing":
7547
+ return "deposit-routing";
7384
7548
  case "completed":
7549
+ return "deposit-complete";
7550
+ case "refunded":
7385
7551
  case "failed":
7386
- return "success";
7387
- case "amount-too-low":
7388
- return "amount-too-low";
7389
- case "enter-amount":
7390
- return "enter-amount";
7552
+ case "wrong_token":
7553
+ return "deposit-failed";
7391
7554
  }
7392
7555
  }
7393
-
7394
- // src/processingStatus.ts
7395
- var PROCESSING_TIMEOUT_MS = 18e4;
7396
- var TERMINAL_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["COMPLETED", "FAILED", "EXPIRED"]);
7397
- var SIGNABLE_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["CREATED", "AUTHORIZED"]);
7398
- function isTerminalTransferStatus(status) {
7399
- return TERMINAL_TRANSFER_STATUSES.has(status);
7400
- }
7401
- function isTransferSignable(transfer) {
7402
- return transfer != null && SIGNABLE_TRANSFER_STATUSES.has(transfer.status);
7403
- }
7404
- function isTransferAwaitingCompletion(transfer) {
7405
- if (!transfer) return false;
7406
- return !isTerminalTransferStatus(transfer.status);
7407
- }
7408
- function resolvePreferredTransfer(polledTransfer, localTransfer) {
7409
- return polledTransfer ?? localTransfer;
7556
+ function isSameChainSameTokenSelection(option, destination) {
7557
+ return option.chainId === destination.chainId && option.tokenAddress.toLowerCase() === destination.token.address.toLowerCase();
7410
7558
  }
7411
- function getTransferStatus(polledTransfer, localTransfer) {
7412
- const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
7413
- return transfer?.status ?? "UNKNOWN";
7559
+ function feeCopy(_session) {
7560
+ return "No fees";
7414
7561
  }
7415
- function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
7416
- if (!processingStartedAtMs) return false;
7417
- return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
7562
+ function toTransfer(session) {
7563
+ return {
7564
+ id: session.sessionId,
7565
+ status: session.status,
7566
+ amount: {
7567
+ amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd),
7568
+ currency: "USD"
7569
+ },
7570
+ sources: [],
7571
+ destinations: [{
7572
+ id: session.sessionId,
7573
+ chainId: session.destination.chainId,
7574
+ address: session.destination.address,
7575
+ token: { address: session.destination.token.address, symbol: "" },
7576
+ amount: { amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd), currency: "USD" }
7577
+ }],
7578
+ createDate: session.createDate,
7579
+ updateDate: session.updateDate
7580
+ };
7418
7581
  }
7419
- var STATUS_DISPLAY_LABELS = {
7420
- CREATED: "created",
7421
- AUTHORIZED: "authorized",
7422
- SENDING: "sending",
7423
- SENT: "confirming delivery",
7424
- COMPLETED: "completed",
7425
- FAILED: "failed"
7426
- };
7427
- function getStatusDisplayLabel(status) {
7428
- return STATUS_DISPLAY_LABELS[status] ?? status;
7582
+ var SOLANA_CHAIN_ID = 792703809;
7583
+ var SOLANA_NATIVE_SOL_ADDRESS = "11111111111111111111111111111111";
7584
+ function formatDepositUri(address, chainId, depToken) {
7585
+ if (chainId === SOLANA_CHAIN_ID) {
7586
+ if (!depToken || depToken === SOLANA_NATIVE_SOL_ADDRESS) {
7587
+ return `solana:${address}`;
7588
+ }
7589
+ return `solana:${address}?spl-token=${depToken}`;
7590
+ }
7591
+ return address;
7429
7592
  }
7430
- function buildProcessingTimeoutMessage(status) {
7431
- const label = getStatusDisplayLabel(status);
7432
- return `Payment is taking longer than expected (status: ${label}). Please try again.`;
7593
+ var dataUrlCache = /* @__PURE__ */ new Map();
7594
+ var inFlight = /* @__PURE__ */ new Map();
7595
+ function keyFor(uri, colors) {
7596
+ return `${uri}|${colors.dark}|${colors.light}`;
7597
+ }
7598
+ function getCachedQrDataUrl(uri, colors) {
7599
+ return dataUrlCache.get(keyFor(uri, colors)) ?? null;
7600
+ }
7601
+ function getOrRenderQrDataUrl(uri, colors) {
7602
+ const key = keyFor(uri, colors);
7603
+ const cached = dataUrlCache.get(key);
7604
+ if (cached) return Promise.resolve(cached);
7605
+ const pending = inFlight.get(key);
7606
+ if (pending) return pending;
7607
+ const promise = QRCode.toDataURL(uri, {
7608
+ errorCorrectionLevel: "H",
7609
+ margin: 1,
7610
+ width: 203,
7611
+ color: { dark: colors.dark, light: colors.light }
7612
+ }).then((url) => {
7613
+ dataUrlCache.set(key, url);
7614
+ inFlight.delete(key);
7615
+ return url;
7616
+ }).catch((err) => {
7617
+ inFlight.delete(key);
7618
+ throw err;
7619
+ });
7620
+ inFlight.set(key, promise);
7621
+ return promise;
7433
7622
  }
7434
7623
 
7435
- // src/paymentResolvePhase.ts
7436
- function hasActiveWallet(accounts) {
7437
- return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
7438
- }
7439
- function resolveTerminalPhase(state) {
7440
- if (state.amountTooLow != null) {
7441
- return {
7442
- step: "amount-too-low",
7443
- minAmountUsd: state.amountTooLow.minAmountUsd
7444
- };
7445
- }
7446
- const transferCompleted = state.transfer?.status === "COMPLETED";
7447
- const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
7448
- if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
7449
- return { step: "completed", transfer: state.transfer };
7450
- }
7451
- if (state.transfer?.status === "FAILED") {
7452
- return {
7453
- step: "failed",
7454
- transfer: state.transfer,
7455
- error: state.error ?? "Transfer failed."
7456
- };
7457
- }
7458
- if (state.creatingTransfer || isTransferAwaitingCompletion(state.transfer)) {
7459
- return { step: "processing", transfer: state.transfer };
7460
- }
7461
- return null;
7624
+ // src/hooks/useManualTransferSession.ts
7625
+ function perTokenKey(chainId, tokenAddress) {
7626
+ return `${chainId}:${tokenAddress.toLowerCase()}`;
7462
7627
  }
7463
- function resolveStickyPhase(state) {
7464
- const currentPhase = state.phase;
7465
- if (currentPhase.step === "manual-transfer" && !state.loginRequested) {
7466
- return currentPhase;
7467
- }
7468
- if (currentPhase.step === "deposit-options" && !state.loginRequested && !state.privyAuthenticated) {
7469
- return currentPhase;
7470
- }
7471
- if (!state.loginRequested && state.setupFlowScreen === "one-tap-setup") {
7472
- return { step: "one-tap-setup", action: null };
7473
- }
7474
- if (!state.loginRequested && state.setupFlowScreen === "deposit-confirm" && state.selectedAccountId != null) {
7475
- return {
7476
- step: "wallet-setup",
7477
- mobile: null,
7478
- accountId: state.selectedAccountId
7479
- };
7480
- }
7481
- if (!state.loginRequested && state.mobileTokenAuthorizationPending) {
7482
- return {
7483
- step: "wallet-setup",
7484
- mobile: { deeplinkUri: "", providerId: state.selectedProviderId },
7485
- accountId: null
7486
- };
7487
- }
7488
- const isFundingSourceSubflow = !state.loginRequested && (currentPhase.step === "token-picker" || currentPhase.step === "guest-source-picker" || currentPhase.step === "select-source" || currentPhase.step === "confirm-sign");
7489
- if (isFundingSourceSubflow) {
7490
- return currentPhase;
7491
- }
7492
- if ((state.standardDesktopInlineOpenWallet || state.desktopWait != null) && state.privyAuthenticated && state.activeCredentialId != null && state.selectedAccountId != null && !state.loginRequested) {
7493
- return {
7494
- step: "wallet-setup",
7495
- mobile: null,
7496
- desktopWait: state.desktopWait,
7497
- accountId: state.selectedAccountId
7628
+ function useManualTransferSession({
7629
+ destination,
7630
+ merchantAuthorization,
7631
+ idempotencyKey,
7632
+ onComplete,
7633
+ onError,
7634
+ pollEnabled = true
7635
+ }) {
7636
+ const { apiBaseUrl, tokens } = useBlinkConfig();
7637
+ const [sourceOptions, setSourceOptions] = useState(null);
7638
+ const [loadingSources, setLoadingSources] = useState(true);
7639
+ const [selectedToken, setSelectedToken] = useState("");
7640
+ const [selectedChainId, setSelectedChainId] = useState("");
7641
+ const [sessionsByFamily, setSessionsByFamily] = useState({});
7642
+ const [perTokenSessions, setPerTokenSessions] = useState({});
7643
+ const [loading, setLoading] = useState(false);
7644
+ const [error, setError] = useState(null);
7645
+ const [copiedAddress, setCopiedAddress] = useState(null);
7646
+ const completedRef = useRef(/* @__PURE__ */ new Set());
7647
+ const premintedFamiliesRef = useRef(/* @__PURE__ */ new Set());
7648
+ const inFlightPerTokenRef = useRef(/* @__PURE__ */ new Set());
7649
+ useEffect(() => {
7650
+ if (!merchantAuthorization) return;
7651
+ let cancelled = false;
7652
+ setLoadingSources(true);
7653
+ fetchManualTransferSources(apiBaseUrl, { merchantAuthorization, destination }).then((sources) => {
7654
+ if (cancelled) return;
7655
+ setSourceOptions(sources);
7656
+ setLoadingSources(false);
7657
+ const destTokenAddrLower = destination.token.address.toLowerCase();
7658
+ const destMatch = sources.find(
7659
+ (s) => s.chainId === destination.chainId && s.tokenAddress.toLowerCase() === destTokenAddrLower
7660
+ );
7661
+ const svmCanonical = sources.find((s) => s.canonical && s.chainFamily === "svm");
7662
+ const evmCanonical = sources.find((s) => s.canonical && s.chainFamily === "evm");
7663
+ const defaultOption = destMatch ?? svmCanonical ?? evmCanonical ?? sources[0];
7664
+ if (defaultOption) {
7665
+ setSelectedToken(defaultOption.tokenSymbol);
7666
+ setSelectedChainId(String(defaultOption.chainId));
7667
+ }
7668
+ }).catch((err) => {
7669
+ if (!cancelled) {
7670
+ setError(err instanceof Error ? err.message : String(err));
7671
+ setLoadingSources(false);
7672
+ }
7673
+ });
7674
+ return () => {
7675
+ cancelled = true;
7498
7676
  };
7499
- }
7500
- if (state.mobileFlow && state.deeplinkUri != null) {
7501
- return {
7502
- step: "wallet-setup",
7503
- mobile: { deeplinkUri: state.deeplinkUri, providerId: state.selectedProviderId },
7504
- accountId: null
7677
+ }, [apiBaseUrl, destination, merchantAuthorization]);
7678
+ useEffect(() => {
7679
+ if (!sourceOptions || !merchantAuthorization) return;
7680
+ const canonicals = sourceOptions.filter((s) => s.canonical);
7681
+ if (canonicals.length === 0) return;
7682
+ let cancelled = false;
7683
+ for (const opt of canonicals) {
7684
+ if (premintedFamiliesRef.current.has(opt.chainFamily)) continue;
7685
+ premintedFamiliesRef.current.add(opt.chainFamily);
7686
+ const family = opt.chainFamily;
7687
+ const run = async () => {
7688
+ try {
7689
+ const created = await createManualTransfer(apiBaseUrl, {
7690
+ merchantAuthorization,
7691
+ destination,
7692
+ idempotencyKey,
7693
+ source: { chainId: opt.chainId, tokenAddress: opt.tokenAddress }
7694
+ });
7695
+ if (cancelled) return;
7696
+ setSessionsByFamily((prev) => ({ ...prev, [family]: created }));
7697
+ const uri = formatDepositUri(created.depositAddress, opt.chainId, opt.tokenAddress);
7698
+ void getOrRenderQrDataUrl(uri, { dark: tokens.text, light: tokens.bg });
7699
+ } catch (err) {
7700
+ if (cancelled) return;
7701
+ premintedFamiliesRef.current.delete(family);
7702
+ setError(err instanceof Error ? err.message : String(err));
7703
+ }
7704
+ };
7705
+ void run();
7706
+ }
7707
+ return () => {
7708
+ cancelled = true;
7505
7709
  };
7506
- }
7507
- if (currentPhase.step === "wallet-picker" && currentPhase.reason === "switch" && !state.creatingTransfer && !(state.mobileFlow && state.deeplinkUri)) {
7508
- return currentPhase;
7509
- }
7510
- return null;
7511
- }
7512
- function deriveFreshPhase(state) {
7513
- if (!state.privyReady) {
7514
- return { step: "initializing" };
7515
- }
7516
- if (state.privyAuthenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
7517
- return { step: "initializing" };
7518
- }
7519
- if (state.loginRequested) {
7520
- return { step: "login" };
7521
- }
7522
- if (state.enableFullWidget && !state.privyAuthenticated) {
7523
- return { step: "deposit-options" };
7524
- }
7525
- if (!state.privyAuthenticated) {
7526
- return { step: "login" };
7527
- }
7528
- if (state.loadingData && state.activeCredentialId != null) {
7529
- return { step: "data-loading" };
7530
- }
7531
- if (state.requireAmountEntry) {
7532
- return { step: "enter-amount" };
7533
- }
7534
- if (state.activeCredentialId != null && !hasActiveWallet(state.accounts) && !state.mobileFlow) {
7535
- return { step: "wallet-picker", reason: "link" };
7536
- }
7537
- if (state.activeCredentialId != null && hasActiveWallet(state.accounts) && !state.loadingData) {
7538
- return { step: "deposit" };
7539
- }
7540
- return { step: "wallet-picker", reason: "entry" };
7541
- }
7542
- function resolvePhase(state) {
7543
- return resolveTerminalPhase(state) ?? resolveStickyPhase(state) ?? deriveFreshPhase(state);
7544
- }
7545
-
7546
- // src/paymentReducer.ts
7547
- var DEFAULT_ONE_TAP_LIMIT = 1e4;
7548
- function deriveSourceTypeAndId(state) {
7549
- if (state.selectedWalletId) {
7550
- return { sourceType: "walletId", sourceId: state.selectedWalletId };
7551
- }
7552
- if (state.selectedAccountId) {
7553
- return { sourceType: "accountId", sourceId: state.selectedAccountId };
7554
- }
7555
- return { sourceType: "accountId", sourceId: "" };
7556
- }
7557
- function clearStaleSelection(state) {
7558
- if (state.selectedAccountId == null) return state;
7559
- const stillExists = state.accounts.some((a) => a.id === state.selectedAccountId);
7560
- if (stillExists) return state;
7710
+ }, [
7711
+ apiBaseUrl,
7712
+ destination,
7713
+ idempotencyKey,
7714
+ merchantAuthorization,
7715
+ sourceOptions,
7716
+ tokens.text,
7717
+ tokens.bg
7718
+ ]);
7719
+ const tokenChoices = useMemo(
7720
+ () => Array.from(new Set((sourceOptions ?? []).map((opt) => opt.tokenSymbol))),
7721
+ [sourceOptions]
7722
+ );
7723
+ const chainChoices = useMemo(() => {
7724
+ const seen = /* @__PURE__ */ new Set();
7725
+ return (sourceOptions ?? []).filter((opt) => {
7726
+ if (seen.has(opt.chainId)) return false;
7727
+ seen.add(opt.chainId);
7728
+ return true;
7729
+ });
7730
+ }, [sourceOptions]);
7731
+ const tokenLogoUriBySymbol = useMemo(() => {
7732
+ const out = {};
7733
+ for (const opt of sourceOptions ?? []) {
7734
+ if (out[opt.tokenSymbol] == null) {
7735
+ out[opt.tokenSymbol] = opt.tokenLogoUri;
7736
+ }
7737
+ }
7738
+ return out;
7739
+ }, [sourceOptions]);
7740
+ const selectedOption = useMemo(
7741
+ () => (sourceOptions ?? []).find(
7742
+ (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === selectedChainId
7743
+ ) ?? null,
7744
+ [sourceOptions, selectedToken, selectedChainId]
7745
+ );
7746
+ const tokensForSelectedChain = useMemo(() => {
7747
+ if (!selectedChainId) return null;
7748
+ return new Set(
7749
+ (sourceOptions ?? []).filter((opt) => String(opt.chainId) === selectedChainId).map((opt) => opt.tokenSymbol)
7750
+ );
7751
+ }, [sourceOptions, selectedChainId]);
7752
+ const chainsForSelectedToken = useMemo(() => {
7753
+ if (!selectedToken) return null;
7754
+ return new Set(
7755
+ (sourceOptions ?? []).filter((opt) => opt.tokenSymbol === selectedToken).map((opt) => opt.chainId)
7756
+ );
7757
+ }, [sourceOptions, selectedToken]);
7758
+ const session = useMemo(() => {
7759
+ if (!selectedOption) return null;
7760
+ const perToken = perTokenSessions[perTokenKey(selectedOption.chainId, selectedOption.tokenAddress)] ?? null;
7761
+ if (isSameChainSameTokenSelection(selectedOption, destination)) {
7762
+ return perToken ?? sessionsByFamily[selectedOption.chainFamily] ?? null;
7763
+ }
7764
+ const familySession = sessionsByFamily[selectedOption.chainFamily];
7765
+ if (familySession) return familySession;
7766
+ return perToken;
7767
+ }, [selectedOption, destination, sessionsByFamily, perTokenSessions]);
7768
+ const depositAddress = session?.depositAddress;
7769
+ const qrReady = !!depositAddress;
7770
+ const lastFeeCopyRef = useRef(null);
7771
+ const nextFeeCopy = session ? feeCopy() : null;
7772
+ const sessionFeeCopy = nextFeeCopy === lastFeeCopyRef.current ? lastFeeCopyRef.current : lastFeeCopyRef.current = nextFeeCopy;
7773
+ const activeSessionId = session?.sessionId;
7774
+ const activeSessionStatus = session?.status;
7775
+ useEffect(() => {
7776
+ if (!pollEnabled) return;
7777
+ if (!activeSessionId) return;
7778
+ if (activeSessionStatus && ["completed", "failed", "refunded", "wrong_token"].includes(activeSessionStatus)) return;
7779
+ const timer = window.setInterval(() => {
7780
+ fetchManualTransferSession(apiBaseUrl, activeSessionId).then((updated) => {
7781
+ setSessionsByFamily((prev) => {
7782
+ for (const family of Object.keys(prev)) {
7783
+ if (prev[family]?.sessionId === updated.sessionId) {
7784
+ return { ...prev, [family]: updated };
7785
+ }
7786
+ }
7787
+ return prev;
7788
+ });
7789
+ setPerTokenSessions((prev) => {
7790
+ for (const key of Object.keys(prev)) {
7791
+ if (prev[key]?.sessionId === updated.sessionId) {
7792
+ return { ...prev, [key]: updated };
7793
+ }
7794
+ }
7795
+ return prev;
7796
+ });
7797
+ }).catch((err) => setError(err instanceof Error ? err.message : String(err)));
7798
+ }, 2e3);
7799
+ return () => window.clearInterval(timer);
7800
+ }, [apiBaseUrl, activeSessionId, activeSessionStatus, pollEnabled]);
7801
+ const completionSignature = useMemo(() => {
7802
+ const entries2 = [];
7803
+ for (const family of Object.keys(sessionsByFamily)) {
7804
+ const s = sessionsByFamily[family];
7805
+ if (s) entries2.push([s.sessionId, s.status]);
7806
+ }
7807
+ for (const key of Object.keys(perTokenSessions)) {
7808
+ const s = perTokenSessions[key];
7809
+ entries2.push([s.sessionId, s.status]);
7810
+ }
7811
+ entries2.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
7812
+ return JSON.stringify(entries2);
7813
+ }, [sessionsByFamily, perTokenSessions]);
7814
+ useEffect(() => {
7815
+ const all = [];
7816
+ for (const family of Object.keys(sessionsByFamily)) {
7817
+ const s = sessionsByFamily[family];
7818
+ if (s) all.push(s);
7819
+ }
7820
+ for (const key of Object.keys(perTokenSessions)) {
7821
+ all.push(perTokenSessions[key]);
7822
+ }
7823
+ for (const s of all) {
7824
+ if (s.status !== "completed") continue;
7825
+ if (completedRef.current.has(s.sessionId)) continue;
7826
+ completedRef.current.add(s.sessionId);
7827
+ onComplete?.(toTransfer(s));
7828
+ break;
7829
+ }
7830
+ }, [completionSignature, onComplete]);
7831
+ useEffect(() => {
7832
+ if (!error) return;
7833
+ onError?.(error);
7834
+ }, [error, onError]);
7835
+ const createSession = useCallback(async (option) => {
7836
+ if (!merchantAuthorization) return;
7837
+ setLoading(true);
7838
+ setError(null);
7839
+ try {
7840
+ const created = await createManualTransfer(apiBaseUrl, {
7841
+ merchantAuthorization,
7842
+ destination,
7843
+ idempotencyKey,
7844
+ source: { chainId: option.chainId, tokenAddress: option.tokenAddress }
7845
+ });
7846
+ const key = perTokenKey(option.chainId, option.tokenAddress);
7847
+ setPerTokenSessions((prev) => ({ ...prev, [key]: created }));
7848
+ const uri = formatDepositUri(created.depositAddress, option.chainId, option.tokenAddress);
7849
+ void getOrRenderQrDataUrl(uri, { dark: tokens.text, light: tokens.bg });
7850
+ } catch (err) {
7851
+ setError(err instanceof Error ? err.message : String(err));
7852
+ } finally {
7853
+ setLoading(false);
7854
+ }
7855
+ }, [apiBaseUrl, destination, idempotencyKey, merchantAuthorization, tokens.text, tokens.bg]);
7856
+ useEffect(() => {
7857
+ if (!selectedOption) return;
7858
+ const sameTokenSelection = isSameChainSameTokenSelection(selectedOption, destination);
7859
+ if (!sameTokenSelection) {
7860
+ if (sessionsByFamily[selectedOption.chainFamily]) return;
7861
+ if (premintedFamiliesRef.current.has(selectedOption.chainFamily)) return;
7862
+ }
7863
+ const key = perTokenKey(selectedOption.chainId, selectedOption.tokenAddress);
7864
+ if (perTokenSessions[key]) return;
7865
+ if (inFlightPerTokenRef.current.has(key)) return;
7866
+ inFlightPerTokenRef.current.add(key);
7867
+ void createSession(selectedOption).finally(() => {
7868
+ inFlightPerTokenRef.current.delete(key);
7869
+ });
7870
+ }, [selectedOption, destination, sessionsByFamily, perTokenSessions, createSession]);
7871
+ const resetSessionForNewSelection = useCallback(() => {
7872
+ setError(null);
7873
+ }, []);
7874
+ const selectToken = useCallback((value) => {
7875
+ setSelectedToken(value);
7876
+ const pairValid = (sourceOptions ?? []).some(
7877
+ (opt) => opt.tokenSymbol === value && String(opt.chainId) === selectedChainId
7878
+ );
7879
+ if (!pairValid) {
7880
+ const firstChain = (sourceOptions ?? []).find(
7881
+ (opt) => opt.tokenSymbol === value
7882
+ );
7883
+ setSelectedChainId(
7884
+ firstChain ? String(firstChain.chainId) : ""
7885
+ );
7886
+ }
7887
+ }, [selectedChainId, sourceOptions]);
7888
+ const selectChainId = useCallback((value) => {
7889
+ setSelectedChainId(value);
7890
+ const pairValid = (sourceOptions ?? []).some(
7891
+ (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === value
7892
+ );
7893
+ if (!pairValid) {
7894
+ const firstToken = (sourceOptions ?? []).find(
7895
+ (opt) => String(opt.chainId) === value
7896
+ );
7897
+ setSelectedToken(
7898
+ firstToken ? firstToken.tokenSymbol : ""
7899
+ );
7900
+ }
7901
+ }, [selectedToken, sourceOptions]);
7902
+ const screen = screenForSession(session);
7903
+ const copyDepositAddress = useCallback(async (address) => {
7904
+ try {
7905
+ await navigator.clipboard.writeText(address);
7906
+ } catch {
7907
+ const textarea = document.createElement("textarea");
7908
+ textarea.value = address;
7909
+ textarea.setAttribute("readonly", "");
7910
+ textarea.style.position = "absolute";
7911
+ textarea.style.opacity = "0";
7912
+ document.body.appendChild(textarea);
7913
+ textarea.select();
7914
+ try {
7915
+ document.execCommand("copy");
7916
+ } finally {
7917
+ document.body.removeChild(textarea);
7918
+ }
7919
+ }
7920
+ setCopiedAddress(address);
7921
+ window.setTimeout(() => setCopiedAddress((cur) => cur === address ? null : cur), 1500);
7922
+ }, []);
7923
+ const backToSourceSelector = useCallback(() => {
7924
+ setCopiedAddress(null);
7925
+ setError(null);
7926
+ if (!selectedOption) return;
7927
+ const family = selectedOption.chainFamily;
7928
+ setSessionsByFamily((prev) => {
7929
+ if (!prev[family]) return prev;
7930
+ const next = { ...prev };
7931
+ delete next[family];
7932
+ return next;
7933
+ });
7934
+ premintedFamiliesRef.current.delete(family);
7935
+ const ptKey = perTokenKey(selectedOption.chainId, selectedOption.tokenAddress);
7936
+ setPerTokenSessions((prev) => {
7937
+ if (!prev[ptKey]) return prev;
7938
+ const next = { ...prev };
7939
+ delete next[ptKey];
7940
+ return next;
7941
+ });
7942
+ }, [selectedOption]);
7561
7943
  return {
7562
- ...state,
7563
- selectedAccountId: null,
7564
- selectedWalletId: null,
7565
- selectedTokenSymbol: null
7944
+ sourceOptions,
7945
+ loadingSources,
7946
+ selectedToken,
7947
+ selectedChainId,
7948
+ session,
7949
+ loading,
7950
+ error,
7951
+ qrReady,
7952
+ copiedAddress,
7953
+ tokenChoices,
7954
+ chainChoices,
7955
+ tokenLogoUriBySymbol,
7956
+ selectedOption,
7957
+ tokensForSelectedChain,
7958
+ chainsForSelectedToken,
7959
+ screen,
7960
+ sessionFeeCopy,
7961
+ depositAddress,
7962
+ createSession,
7963
+ copyDepositAddress,
7964
+ backToSourceSelector,
7965
+ resetSessionForNewSelection,
7966
+ selectToken,
7967
+ selectChainId
7566
7968
  };
7567
7969
  }
7568
- function createInitialState(config) {
7569
- return {
7570
- phase: config.initialPhase ?? { step: "initializing" },
7571
- error: null,
7572
- setupFlowScreen: null,
7573
- providers: [],
7574
- accounts: [],
7575
- chains: [],
7576
- loadingData: false,
7577
- depositSelectionRefreshing: false,
7578
- selectedProviderId: null,
7579
- selectedAccountId: null,
7580
- selectedWalletId: null,
7581
- selectedTokenSymbol: null,
7582
- savedSelection: null,
7583
- amount: config.depositAmount != null ? config.depositAmount.toString() : "",
7584
- transfer: null,
7585
- pendingTransferId: null,
7586
- creatingTransfer: false,
7587
- passkeyConfigLoaded: false,
7588
- activeCredentialId: config.activeCredentialId,
7589
- knownCredentialIds: [],
7590
- oneTapLimit: DEFAULT_ONE_TAP_LIMIT,
7591
- oneTapLimitSavedDuringSetup: false,
7592
- mobileFlow: false,
7593
- deeplinkUri: null,
7594
- increasingLimit: false,
7595
- activePublicKey: null,
7596
- loginRequested: false,
7597
- standardDesktopInlineOpenWallet: false,
7598
- desktopWait: null,
7599
- setupAuthorizationSessionId: null,
7600
- mobileTokenAuthorizationPending: false,
7601
- privyReady: false,
7602
- privyAuthenticated: false,
7603
- lastResumedAt: 0,
7604
- setupDepositAmount: null,
7605
- setupDepositToken: null,
7606
- setupDepositConfirmed: false,
7607
- guestWalletPrepared: null,
7608
- guestWalletDeeplinksPreparing: false,
7609
- amountTooLow: null,
7610
- enableFullWidget: config.enableFullWidget ?? false,
7611
- requireAmountEntry: config.depositAmount == null
7612
- };
7970
+ var ManualTransferSessionContext = createContext(null);
7971
+ function ManualTransferSessionProvider({
7972
+ destination,
7973
+ merchantAuthorization,
7974
+ idempotencyKey,
7975
+ onComplete,
7976
+ onError,
7977
+ pollEnabled,
7978
+ children
7979
+ }) {
7980
+ const session = useManualTransferSession({
7981
+ destination,
7982
+ merchantAuthorization,
7983
+ idempotencyKey,
7984
+ onComplete,
7985
+ onError,
7986
+ pollEnabled
7987
+ });
7988
+ return /* @__PURE__ */ jsx(ManualTransferSessionContext.Provider, { value: session, children });
7613
7989
  }
7614
- function clearAuthenticatedSessionState(state) {
7615
- return {
7616
- ...state,
7617
- error: null,
7618
- setupFlowScreen: null,
7619
- providers: [],
7620
- accounts: [],
7621
- chains: [],
7622
- loadingData: false,
7623
- depositSelectionRefreshing: false,
7624
- selectedProviderId: null,
7625
- selectedAccountId: null,
7626
- selectedWalletId: null,
7627
- selectedTokenSymbol: null,
7628
- savedSelection: null,
7629
- transfer: null,
7630
- pendingTransferId: null,
7990
+ function useManualTransferSessionContext() {
7991
+ const ctx = useContext(ManualTransferSessionContext);
7992
+ if (!ctx) {
7993
+ throw new Error(
7994
+ "useManualTransferSessionContext must be used within a <ManualTransferSessionProvider>"
7995
+ );
7996
+ }
7997
+ return ctx;
7998
+ }
7999
+
8000
+ // src/processingStatus.ts
8001
+ var PROCESSING_TIMEOUT_MS = 18e4;
8002
+ var TERMINAL_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["COMPLETED", "FAILED", "EXPIRED"]);
8003
+ var SIGNABLE_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["CREATED", "AUTHORIZED"]);
8004
+ function isTerminalTransferStatus(status) {
8005
+ return TERMINAL_TRANSFER_STATUSES.has(status);
8006
+ }
8007
+ function isTransferSignable(transfer) {
8008
+ return transfer != null && SIGNABLE_TRANSFER_STATUSES.has(transfer.status);
8009
+ }
8010
+ function isTransferAwaitingCompletion(transfer) {
8011
+ if (!transfer) return false;
8012
+ return !isTerminalTransferStatus(transfer.status);
8013
+ }
8014
+ function resolvePreferredTransfer(polledTransfer, localTransfer) {
8015
+ return polledTransfer ?? localTransfer;
8016
+ }
8017
+ function getTransferStatus(polledTransfer, localTransfer) {
8018
+ const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
8019
+ return transfer?.status ?? "UNKNOWN";
8020
+ }
8021
+ function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
8022
+ if (!processingStartedAtMs) return false;
8023
+ return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
8024
+ }
8025
+ var STATUS_DISPLAY_LABELS = {
8026
+ CREATED: "created",
8027
+ AUTHORIZED: "authorized",
8028
+ SENDING: "sending",
8029
+ SENT: "confirming delivery",
8030
+ COMPLETED: "completed",
8031
+ FAILED: "failed"
8032
+ };
8033
+ function getStatusDisplayLabel(status) {
8034
+ return STATUS_DISPLAY_LABELS[status] ?? status;
8035
+ }
8036
+ function buildProcessingTimeoutMessage(status) {
8037
+ const label = getStatusDisplayLabel(status);
8038
+ return `Payment is taking longer than expected (status: ${label}). Please try again.`;
8039
+ }
8040
+
8041
+ // src/paymentResolvePhase.ts
8042
+ function hasActiveWallet(accounts) {
8043
+ return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
8044
+ }
8045
+ function resolveTerminalPhase(state) {
8046
+ if (state.amountTooLow != null) {
8047
+ return {
8048
+ step: "amount-too-low",
8049
+ minAmountUsd: state.amountTooLow.minAmountUsd
8050
+ };
8051
+ }
8052
+ const transferCompleted = state.transfer?.status === "COMPLETED";
8053
+ const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
8054
+ if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
8055
+ return { step: "completed", transfer: state.transfer };
8056
+ }
8057
+ if (state.transfer?.status === "FAILED") {
8058
+ return {
8059
+ step: "failed",
8060
+ transfer: state.transfer,
8061
+ error: state.error ?? "Transfer failed."
8062
+ };
8063
+ }
8064
+ if (state.creatingTransfer || isTransferAwaitingCompletion(state.transfer)) {
8065
+ return { step: "processing", transfer: state.transfer };
8066
+ }
8067
+ return null;
8068
+ }
8069
+ function resolveStickyPhase(state) {
8070
+ const currentPhase = state.phase;
8071
+ if (currentPhase.step === "manual-transfer" && !state.loginRequested) {
8072
+ return currentPhase;
8073
+ }
8074
+ if (currentPhase.step === "deposit-options" && !state.loginRequested && !state.privyAuthenticated) {
8075
+ return currentPhase;
8076
+ }
8077
+ if (!state.loginRequested && state.setupFlowScreen === "one-tap-setup") {
8078
+ return { step: "one-tap-setup", action: null };
8079
+ }
8080
+ if (!state.loginRequested && state.setupFlowScreen === "deposit-confirm" && state.selectedAccountId != null) {
8081
+ return {
8082
+ step: "wallet-setup",
8083
+ mobile: null,
8084
+ accountId: state.selectedAccountId
8085
+ };
8086
+ }
8087
+ if (!state.loginRequested && state.mobileTokenAuthorizationPending) {
8088
+ return {
8089
+ step: "wallet-setup",
8090
+ mobile: { deeplinkUri: "", providerId: state.selectedProviderId },
8091
+ accountId: null
8092
+ };
8093
+ }
8094
+ const isFundingSourceSubflow = !state.loginRequested && (currentPhase.step === "token-picker" || currentPhase.step === "guest-source-picker" || currentPhase.step === "select-source" || currentPhase.step === "confirm-sign");
8095
+ if (isFundingSourceSubflow) {
8096
+ return currentPhase;
8097
+ }
8098
+ if ((state.standardDesktopInlineOpenWallet || state.desktopWait != null) && state.privyAuthenticated && state.activeCredentialId != null && state.selectedAccountId != null && !state.loginRequested) {
8099
+ return {
8100
+ step: "wallet-setup",
8101
+ mobile: null,
8102
+ desktopWait: state.desktopWait,
8103
+ accountId: state.selectedAccountId
8104
+ };
8105
+ }
8106
+ if (state.mobileFlow && state.deeplinkUri != null) {
8107
+ return {
8108
+ step: "wallet-setup",
8109
+ mobile: { deeplinkUri: state.deeplinkUri, providerId: state.selectedProviderId },
8110
+ accountId: null
8111
+ };
8112
+ }
8113
+ if (currentPhase.step === "wallet-picker" && currentPhase.reason === "switch" && !state.creatingTransfer && !(state.mobileFlow && state.deeplinkUri)) {
8114
+ return currentPhase;
8115
+ }
8116
+ return null;
8117
+ }
8118
+ function deriveFreshPhase(state) {
8119
+ if (!state.privyReady) {
8120
+ return { step: "initializing" };
8121
+ }
8122
+ if (state.privyAuthenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
8123
+ return { step: "initializing" };
8124
+ }
8125
+ if (state.loginRequested) {
8126
+ return { step: "login" };
8127
+ }
8128
+ if (state.enableFullWidget && !state.privyAuthenticated) {
8129
+ return { step: "deposit-options" };
8130
+ }
8131
+ if (!state.privyAuthenticated) {
8132
+ return { step: "login" };
8133
+ }
8134
+ if (state.loadingData && state.activeCredentialId != null) {
8135
+ return { step: "data-loading" };
8136
+ }
8137
+ if (state.requireAmountEntry) {
8138
+ return { step: "enter-amount" };
8139
+ }
8140
+ if (state.activeCredentialId != null && !hasActiveWallet(state.accounts) && !state.mobileFlow) {
8141
+ return { step: "wallet-picker", reason: "link" };
8142
+ }
8143
+ if (state.activeCredentialId != null && hasActiveWallet(state.accounts) && !state.loadingData) {
8144
+ return { step: "deposit" };
8145
+ }
8146
+ return { step: "wallet-picker", reason: "entry" };
8147
+ }
8148
+ function resolvePhase(state) {
8149
+ return resolveTerminalPhase(state) ?? resolveStickyPhase(state) ?? deriveFreshPhase(state);
8150
+ }
8151
+
8152
+ // src/paymentReducer.ts
8153
+ var DEFAULT_ONE_TAP_LIMIT = 1e4;
8154
+ function deriveSourceTypeAndId(state) {
8155
+ if (state.selectedWalletId) {
8156
+ return { sourceType: "walletId", sourceId: state.selectedWalletId };
8157
+ }
8158
+ if (state.selectedAccountId) {
8159
+ return { sourceType: "accountId", sourceId: state.selectedAccountId };
8160
+ }
8161
+ return { sourceType: "accountId", sourceId: "" };
8162
+ }
8163
+ function clearStaleSelection(state) {
8164
+ if (state.selectedAccountId == null) return state;
8165
+ if (state.desktopWait != null) return state;
8166
+ const stillExists = state.accounts.some((a) => a.id === state.selectedAccountId);
8167
+ if (stillExists) return state;
8168
+ return {
8169
+ ...state,
8170
+ selectedAccountId: null,
8171
+ selectedWalletId: null,
8172
+ selectedTokenSymbol: null
8173
+ };
8174
+ }
8175
+ function createInitialState(config) {
8176
+ return {
8177
+ phase: config.initialPhase ?? { step: "initializing" },
8178
+ error: null,
8179
+ setupFlowScreen: null,
8180
+ providers: [],
8181
+ accounts: [],
8182
+ chains: [],
8183
+ loadingData: false,
8184
+ depositSelectionRefreshing: false,
8185
+ selectedProviderId: null,
8186
+ selectedAccountId: null,
8187
+ selectedWalletId: null,
8188
+ selectedTokenSymbol: null,
8189
+ savedSelection: null,
8190
+ amount: config.depositAmount != null ? config.depositAmount.toString() : "",
8191
+ transfer: null,
8192
+ pendingTransferId: null,
8193
+ creatingTransfer: false,
8194
+ passkeyConfigLoaded: false,
8195
+ activeCredentialId: config.activeCredentialId,
8196
+ knownCredentialIds: [],
8197
+ oneTapLimit: DEFAULT_ONE_TAP_LIMIT,
8198
+ oneTapLimitSavedDuringSetup: false,
8199
+ mobileFlow: false,
8200
+ deeplinkUri: null,
8201
+ increasingLimit: false,
8202
+ activePublicKey: null,
8203
+ loginRequested: false,
8204
+ standardDesktopInlineOpenWallet: false,
8205
+ desktopWait: null,
8206
+ setupAuthorizationSessionId: null,
8207
+ mobileTokenAuthorizationPending: false,
8208
+ privyReady: false,
8209
+ privyAuthenticated: false,
8210
+ lastResumedAt: 0,
8211
+ setupDepositAmount: null,
8212
+ setupDepositToken: null,
8213
+ setupDepositConfirmed: false,
8214
+ guestWalletPrepared: null,
8215
+ guestWalletDeeplinksPreparing: false,
8216
+ amountTooLow: null,
8217
+ enableFullWidget: config.enableFullWidget ?? false,
8218
+ requireAmountEntry: config.depositAmount == null
8219
+ };
8220
+ }
8221
+ function clearAuthenticatedSessionState(state) {
8222
+ return {
8223
+ ...state,
8224
+ error: null,
8225
+ setupFlowScreen: null,
8226
+ providers: [],
8227
+ accounts: [],
8228
+ chains: [],
8229
+ loadingData: false,
8230
+ depositSelectionRefreshing: false,
8231
+ selectedProviderId: null,
8232
+ selectedAccountId: null,
8233
+ selectedWalletId: null,
8234
+ selectedTokenSymbol: null,
8235
+ savedSelection: null,
8236
+ transfer: null,
8237
+ pendingTransferId: null,
7631
8238
  creatingTransfer: false,
7632
8239
  passkeyConfigLoaded: false,
7633
8240
  activeCredentialId: null,
@@ -8071,552 +8678,118 @@ function applyAction(state, action) {
8071
8678
  // unauthenticated path) preserves the flag implicitly via the
8072
8679
  // surrounding `...state` spread, so no change is needed there.
8073
8680
  enableFullWidget: state.enableFullWidget
8074
- };
8075
- case "SYNC_PRIVY_SESSION":
8076
- if (action.ready && !action.authenticated) {
8077
- return {
8078
- ...clearAuthenticatedSessionState(state),
8079
- privyReady: true,
8080
- privyAuthenticated: false
8081
- };
8082
- }
8083
- return {
8084
- ...state,
8085
- privyReady: action.ready,
8086
- privyAuthenticated: action.authenticated,
8087
- ...action.authenticated ? { loginRequested: false } : {}
8088
- };
8089
- case "SYNC_AMOUNT":
8090
- return {
8091
- ...state,
8092
- amount: action.amount,
8093
- requireAmountEntry: action.amount === "" ? state.requireAmountEntry : false
8094
- };
8095
- case "SET_AMOUNT_INPUT":
8096
- return { ...state, amount: action.value };
8097
- case "FINALIZE_AMOUNT":
8098
- return { ...state, requireAmountEntry: false };
8099
- case "PAGE_RESUMED":
8100
- return { ...state, lastResumedAt: Date.now() };
8101
- // ── Setup deposit (combined first-time flow) ────────────────
8102
- case "SET_SETUP_DEPOSIT_AMOUNT":
8103
- return { ...state, setupDepositAmount: action.amount };
8104
- case "SET_SETUP_DEPOSIT_TOKEN":
8105
- return {
8106
- ...state,
8107
- setupDepositToken: {
8108
- symbol: action.symbol,
8109
- chainName: action.chainName,
8110
- ...action.walletId ? { walletId: action.walletId } : {},
8111
- ...action.tokenAddress ? { tokenAddress: action.tokenAddress } : {},
8112
- ...action.chainId != null ? { chainId: action.chainId } : {}
8113
- }
8114
- };
8115
- case "CLEAR_SETUP_DEPOSIT_TOKEN":
8116
- return { ...state, setupDepositToken: null };
8117
- case "CONFIRM_SETUP_DEPOSIT":
8118
- return { ...state, setupDepositConfirmed: true };
8119
- default:
8120
- return state;
8121
- }
8122
- }
8123
-
8124
- // src/setupDepositConfirmation.ts
8125
- function deriveEffectiveSetupSource(setupDepositToken, setupSelectedSourceOption) {
8126
- if (setupDepositToken) return setupDepositToken;
8127
- if (setupSelectedSourceOption) {
8128
- return {
8129
- symbol: setupSelectedSourceOption.tokenSymbol,
8130
- chainName: setupSelectedSourceOption.chainName,
8131
- walletId: setupSelectedSourceOption.walletId,
8132
- tokenAddress: setupSelectedSourceOption.tokenAddress,
8133
- chainId: setupSelectedSourceOption.chainId != null ? Number(setupSelectedSourceOption.chainId) : void 0
8134
- };
8135
- }
8136
- return null;
8137
- }
8138
- function planConfirmSetupDeposit(input) {
8139
- const effective = deriveEffectiveSetupSource(
8140
- input.setupDepositToken,
8141
- input.setupSelectedSourceOption
8142
- );
8143
- if (!effective) {
8144
- return { kind: "error", error: "Select a source token before continuing." };
8145
- }
8146
- if (!effective.walletId) {
8147
- return {
8148
- kind: "error",
8149
- error: "Selected source is not ready yet. Wait for wallet discovery to finish."
8150
- };
8151
- }
8152
- const actions = [
8153
- { type: "SET_ERROR", error: null },
8154
- {
8155
- type: "SET_SETUP_DEPOSIT_TOKEN",
8156
- symbol: effective.symbol,
8157
- chainName: effective.chainName,
8158
- walletId: effective.walletId,
8159
- tokenAddress: effective.tokenAddress,
8160
- chainId: effective.chainId
8161
- }
8162
- ];
8163
- if (input.selectedAccountId) {
8164
- actions.push({
8165
- type: "SELECT_ACCOUNT",
8166
- accountId: input.selectedAccountId,
8167
- walletId: effective.walletId
8168
- });
8169
- actions.push({
8170
- type: "SELECT_TOKEN",
8171
- walletId: effective.walletId,
8172
- tokenSymbol: effective.symbol
8173
- });
8174
- }
8175
- actions.push({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
8176
- actions.push({ type: "CONFIRM_SETUP_DEPOSIT" });
8177
- return {
8178
- kind: "proceed",
8179
- actions,
8180
- resolveSource: {
8181
- chainName: effective.chainName,
8182
- tokenSymbol: effective.symbol
8183
- }
8184
- };
8185
- }
8186
-
8187
- // src/manualTransferUtils.ts
8188
- function screenForSession(session) {
8189
- if (!session) return "source-selector";
8190
- switch (session.status) {
8191
- case "awaiting_deposit":
8192
- return "awaiting-deposit";
8193
- case "deposit_received":
8194
- return "deposit-received";
8195
- case "routing":
8196
- return "deposit-routing";
8197
- case "completed":
8198
- return "deposit-complete";
8199
- case "refunded":
8200
- case "failed":
8201
- case "wrong_token":
8202
- return "deposit-failed";
8203
- }
8204
- }
8205
- function feeCopy(_session) {
8206
- return "No fees";
8207
- }
8208
- function toTransfer(session) {
8209
- return {
8210
- id: session.sessionId,
8211
- status: session.status,
8212
- amount: {
8213
- amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd),
8214
- currency: "USD"
8215
- },
8216
- sources: [],
8217
- destinations: [{
8218
- id: session.sessionId,
8219
- chainId: session.destination.chainId,
8220
- address: session.destination.address,
8221
- token: { address: session.destination.token.address, symbol: "" },
8222
- amount: { amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd), currency: "USD" }
8223
- }],
8224
- createDate: session.createDate,
8225
- updateDate: session.updateDate
8226
- };
8227
- }
8228
- var SOLANA_CHAIN_ID = 792703809;
8229
- var SOLANA_NATIVE_SOL_ADDRESS = "11111111111111111111111111111111";
8230
- function formatDepositUri(address, chainId, depToken) {
8231
- if (chainId === SOLANA_CHAIN_ID) {
8232
- if (!depToken || depToken === SOLANA_NATIVE_SOL_ADDRESS) {
8233
- return `solana:${address}`;
8234
- }
8235
- return `solana:${address}?spl-token=${depToken}`;
8236
- }
8237
- return address;
8238
- }
8239
-
8240
- // src/hooks/useManualTransferSession.ts
8241
- function useManualTransferSession({
8242
- destination,
8243
- merchantAuthorization,
8244
- idempotencyKey,
8245
- mock = false,
8246
- onComplete,
8247
- onError
8248
- }) {
8249
- const { apiBaseUrl } = useBlinkConfig();
8250
- const [sourceOptions, setSourceOptions] = useState(null);
8251
- const [loadingSources, setLoadingSources] = useState(true);
8252
- const [selectedToken, setSelectedToken] = useState("");
8253
- const [selectedChainId, setSelectedChainId] = useState("");
8254
- const [session, setSession] = useState(null);
8255
- const [loading, setLoading] = useState(false);
8256
- const [error, setError] = useState(null);
8257
- const [qrReady, setQrReady] = useState(false);
8258
- const [copiedAddress, setCopiedAddress] = useState(null);
8259
- const completedRef = useRef(null);
8260
- const lastCreatedOptionRef = useRef(null);
8261
- useEffect(() => {
8262
- if (!merchantAuthorization) return;
8263
- let cancelled = false;
8264
- setLoadingSources(true);
8265
- fetchManualTransferSources(apiBaseUrl, { merchantAuthorization, destination }).then((sources) => {
8266
- if (!cancelled) {
8267
- setSourceOptions(sources);
8268
- setLoadingSources(false);
8269
- const defaultOption = sources.find(
8270
- (opt) => opt.tokenSymbol === "USDC" && opt.chainId === 792703809
8271
- );
8272
- if (defaultOption) {
8273
- setSelectedToken("USDC");
8274
- setSelectedChainId("792703809");
8275
- }
8276
- }
8277
- }).catch((err) => {
8278
- if (!cancelled) {
8279
- setError(err instanceof Error ? err.message : String(err));
8280
- setLoadingSources(false);
8281
- }
8282
- });
8283
- return () => {
8284
- cancelled = true;
8285
- };
8286
- }, [apiBaseUrl, destination, merchantAuthorization]);
8287
- const depositAddress = session?.depositAddress;
8288
- useEffect(() => {
8289
- if (!depositAddress) {
8290
- setQrReady(false);
8291
- return;
8292
- }
8293
- const timer = window.setTimeout(() => setQrReady(true), 1200);
8294
- return () => window.clearTimeout(timer);
8295
- }, [depositAddress]);
8296
- const lastFeeCopyRef = useRef(null);
8297
- const nextFeeCopy = session ? feeCopy() : null;
8298
- const sessionFeeCopy = nextFeeCopy === lastFeeCopyRef.current ? lastFeeCopyRef.current : lastFeeCopyRef.current = nextFeeCopy;
8299
- const advanceMockStatus = useCallback((status) => {
8300
- setSession((prev) => prev ? { ...prev, status } : prev);
8301
- }, []);
8302
- useEffect(() => {
8303
- if (mock) return;
8304
- if (!session?.sessionId || ["completed", "failed", "refunded", "wrong_token"].includes(session.status)) return;
8305
- const timer = window.setInterval(() => {
8306
- fetchManualTransferSession(apiBaseUrl, session.sessionId).then(setSession).catch((err) => setError(err instanceof Error ? err.message : String(err)));
8307
- }, 2e3);
8308
- return () => window.clearInterval(timer);
8309
- }, [apiBaseUrl, mock, session?.sessionId, session?.status]);
8310
- useEffect(() => {
8311
- if (mock) return;
8312
- if (!session?.sessionId || session.status !== "awaiting_deposit") return;
8313
- const sessionId = session.sessionId;
8314
- const timer = window.setInterval(() => {
8315
- refreshManualTransferQuote(apiBaseUrl, sessionId).then(({ quoteValidUntil, minAmountUsd, slippage }) => {
8316
- setSession(
8317
- (prev) => prev && prev.sessionId === sessionId ? { ...prev, quoteValidUntil, minAmountUsd, slippage } : prev
8318
- );
8319
- }).catch((err) => {
8320
- console.warn("[manual-transfer] refresh quote failed", err);
8321
- });
8322
- }, 1e4);
8323
- return () => window.clearInterval(timer);
8324
- }, [apiBaseUrl, mock, session?.sessionId, session?.status]);
8325
- useEffect(() => {
8326
- if (session?.status !== "completed") return;
8327
- if (completedRef.current === session.sessionId) return;
8328
- completedRef.current = session.sessionId;
8329
- onComplete?.(toTransfer(session));
8330
- }, [onComplete, session]);
8331
- useEffect(() => {
8332
- if (!error) return;
8333
- onError?.(error);
8334
- }, [error, onError]);
8335
- const tokenChoices = useMemo(
8336
- () => Array.from(new Set((sourceOptions ?? []).map((opt) => opt.tokenSymbol))),
8337
- [sourceOptions]
8338
- );
8339
- const chainChoices = useMemo(() => {
8340
- const seen = /* @__PURE__ */ new Set();
8341
- return (sourceOptions ?? []).filter((opt) => {
8342
- if (seen.has(opt.chainId)) return false;
8343
- seen.add(opt.chainId);
8344
- return true;
8345
- });
8346
- }, [sourceOptions]);
8347
- const tokenLogoUriBySymbol = useMemo(() => {
8348
- const out = {};
8349
- for (const opt of sourceOptions ?? []) {
8350
- if (out[opt.tokenSymbol] == null) {
8351
- out[opt.tokenSymbol] = opt.tokenLogoUri;
8352
- }
8353
- }
8354
- return out;
8355
- }, [sourceOptions]);
8356
- const selectedOption = useMemo(
8357
- () => (sourceOptions ?? []).find(
8358
- (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === selectedChainId
8359
- ) ?? null,
8360
- [sourceOptions, selectedToken, selectedChainId]
8361
- );
8362
- const tokensForSelectedChain = useMemo(() => {
8363
- if (!selectedChainId) return null;
8364
- return new Set(
8365
- (sourceOptions ?? []).filter((opt) => String(opt.chainId) === selectedChainId).map((opt) => opt.tokenSymbol)
8366
- );
8367
- }, [sourceOptions, selectedChainId]);
8368
- const chainsForSelectedToken = useMemo(() => {
8369
- if (!selectedToken) return null;
8370
- return new Set(
8371
- (sourceOptions ?? []).filter((opt) => opt.tokenSymbol === selectedToken).map((opt) => opt.chainId)
8372
- );
8373
- }, [sourceOptions, selectedToken]);
8374
- const createSession = useCallback(async (option) => {
8375
- if (!merchantAuthorization) return;
8376
- setLoading(true);
8377
- setError(null);
8378
- try {
8379
- if (mock) {
8380
- const mockSession = {
8381
- sessionId: `mock-${Date.now()}`,
8382
- idempotencyKey: idempotencyKey ?? `mock-idem-${Date.now()}`,
8383
- merchantId: "mock-merchant",
8384
- destination,
8385
- source: {
8386
- chainId: option.chainId,
8387
- tokenAddress: option.tokenAddress,
8388
- tokenSymbol: option.tokenSymbol
8389
- },
8390
- refundTo: null,
8391
- depositAddress: "0x" + "a1b2c3d4e5f6".repeat(3).slice(0, 40),
8392
- requestId: `mock-req-${Date.now()}`,
8393
- status: "awaiting_deposit",
8394
- minAmountUsd: option.minAmountUsd,
8395
- quoteValidUntil: new Date(Date.now() + 36e5).toISOString(),
8396
- depositTxHashes: [],
8397
- destinationTxHash: null,
8398
- refundTxHashes: [],
8399
- deliveredAmountUsd: null,
8400
- errorCode: null,
8401
- errorMessage: null,
8402
- slippage: null,
8403
- createDate: (/* @__PURE__ */ new Date()).toISOString(),
8404
- updateDate: (/* @__PURE__ */ new Date()).toISOString()
8405
- };
8406
- setSession(mockSession);
8407
- } else {
8408
- const created = await createManualTransfer(apiBaseUrl, {
8409
- merchantAuthorization,
8410
- destination,
8411
- idempotencyKey,
8412
- source: {
8413
- chainId: option.chainId,
8414
- tokenAddress: option.tokenAddress
8415
- }
8416
- });
8417
- setSession(created);
8418
- }
8419
- } catch (err) {
8420
- setError(err instanceof Error ? err.message : String(err));
8421
- } finally {
8422
- setLoading(false);
8423
- }
8424
- }, [apiBaseUrl, destination, idempotencyKey, merchantAuthorization, mock]);
8425
- const resetSessionForNewSelection = useCallback(() => {
8426
- setSession(null);
8427
- lastCreatedOptionRef.current = null;
8428
- }, []);
8429
- const selectToken = useCallback((value) => {
8430
- setSelectedToken(value);
8431
- resetSessionForNewSelection();
8432
- const pairValid = (sourceOptions ?? []).some(
8433
- (opt) => opt.tokenSymbol === value && String(opt.chainId) === selectedChainId
8434
- );
8435
- if (!pairValid) {
8436
- const firstChain = (sourceOptions ?? []).find(
8437
- (opt) => opt.tokenSymbol === value
8438
- );
8439
- setSelectedChainId(
8440
- firstChain ? String(firstChain.chainId) : ""
8441
- );
8442
- }
8443
- }, [resetSessionForNewSelection, selectedChainId, sourceOptions]);
8444
- const selectChainId = useCallback((value) => {
8445
- setSelectedChainId(value);
8446
- resetSessionForNewSelection();
8447
- const pairValid = (sourceOptions ?? []).some(
8448
- (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === value
8449
- );
8450
- if (!pairValid) {
8451
- const firstToken = (sourceOptions ?? []).find(
8452
- (opt) => String(opt.chainId) === value
8453
- );
8454
- setSelectedToken(
8455
- firstToken ? firstToken.tokenSymbol : ""
8456
- );
8457
- }
8458
- }, [resetSessionForNewSelection, selectedToken, sourceOptions]);
8459
- useEffect(() => {
8460
- if (!selectedOption) return;
8461
- const optionKey = `${selectedOption.chainId}:${selectedOption.tokenAddress}`;
8462
- if (lastCreatedOptionRef.current === optionKey) return;
8463
- lastCreatedOptionRef.current = optionKey;
8464
- void createSession(selectedOption);
8465
- }, [selectedOption, createSession]);
8466
- const screen = screenForSession(session);
8467
- const copyDepositAddress = useCallback(async (address) => {
8468
- try {
8469
- await navigator.clipboard.writeText(address);
8470
- } catch {
8471
- const textarea = document.createElement("textarea");
8472
- textarea.value = address;
8473
- textarea.setAttribute("readonly", "");
8474
- textarea.style.position = "absolute";
8475
- textarea.style.opacity = "0";
8476
- document.body.appendChild(textarea);
8477
- textarea.select();
8478
- try {
8479
- document.execCommand("copy");
8480
- } finally {
8481
- document.body.removeChild(textarea);
8681
+ };
8682
+ case "SYNC_PRIVY_SESSION":
8683
+ if (action.ready && !action.authenticated) {
8684
+ return {
8685
+ ...clearAuthenticatedSessionState(state),
8686
+ privyReady: true,
8687
+ privyAuthenticated: false
8688
+ };
8482
8689
  }
8690
+ return {
8691
+ ...state,
8692
+ privyReady: action.ready,
8693
+ privyAuthenticated: action.authenticated,
8694
+ ...action.authenticated ? { loginRequested: false } : {}
8695
+ };
8696
+ case "SYNC_AMOUNT":
8697
+ return {
8698
+ ...state,
8699
+ amount: action.amount,
8700
+ requireAmountEntry: action.amount === "" ? state.requireAmountEntry : false
8701
+ };
8702
+ case "SET_AMOUNT_INPUT":
8703
+ return { ...state, amount: action.value };
8704
+ case "FINALIZE_AMOUNT":
8705
+ return { ...state, requireAmountEntry: false };
8706
+ case "PAGE_RESUMED":
8707
+ return { ...state, lastResumedAt: Date.now() };
8708
+ // ── Setup deposit (combined first-time flow) ────────────────
8709
+ case "SET_SETUP_DEPOSIT_AMOUNT":
8710
+ return { ...state, setupDepositAmount: action.amount };
8711
+ case "SET_SETUP_DEPOSIT_TOKEN":
8712
+ return {
8713
+ ...state,
8714
+ setupDepositToken: {
8715
+ symbol: action.symbol,
8716
+ chainName: action.chainName,
8717
+ ...action.walletId ? { walletId: action.walletId } : {},
8718
+ ...action.tokenAddress ? { tokenAddress: action.tokenAddress } : {},
8719
+ ...action.chainId != null ? { chainId: action.chainId } : {}
8720
+ }
8721
+ };
8722
+ case "CLEAR_SETUP_DEPOSIT_TOKEN":
8723
+ return { ...state, setupDepositToken: null };
8724
+ case "CONFIRM_SETUP_DEPOSIT":
8725
+ return { ...state, setupDepositConfirmed: true };
8726
+ default:
8727
+ return state;
8728
+ }
8729
+ }
8730
+
8731
+ // src/setupDepositConfirmation.ts
8732
+ function deriveEffectiveSetupSource(setupDepositToken, setupSelectedSourceOption) {
8733
+ if (setupDepositToken) return setupDepositToken;
8734
+ if (setupSelectedSourceOption) {
8735
+ return {
8736
+ symbol: setupSelectedSourceOption.tokenSymbol,
8737
+ chainName: setupSelectedSourceOption.chainName,
8738
+ walletId: setupSelectedSourceOption.walletId,
8739
+ tokenAddress: setupSelectedSourceOption.tokenAddress,
8740
+ chainId: setupSelectedSourceOption.chainId != null ? Number(setupSelectedSourceOption.chainId) : void 0
8741
+ };
8742
+ }
8743
+ return null;
8744
+ }
8745
+ function planConfirmSetupDeposit(input) {
8746
+ const effective = deriveEffectiveSetupSource(
8747
+ input.setupDepositToken,
8748
+ input.setupSelectedSourceOption
8749
+ );
8750
+ if (!effective) {
8751
+ return { kind: "error", error: "Select a source token before continuing." };
8752
+ }
8753
+ if (!effective.walletId) {
8754
+ return {
8755
+ kind: "error",
8756
+ error: "Selected source is not ready yet. Wait for wallet discovery to finish."
8757
+ };
8758
+ }
8759
+ const actions = [
8760
+ { type: "SET_ERROR", error: null },
8761
+ {
8762
+ type: "SET_SETUP_DEPOSIT_TOKEN",
8763
+ symbol: effective.symbol,
8764
+ chainName: effective.chainName,
8765
+ walletId: effective.walletId,
8766
+ tokenAddress: effective.tokenAddress,
8767
+ chainId: effective.chainId
8483
8768
  }
8484
- setCopiedAddress(address);
8485
- window.setTimeout(() => setCopiedAddress((cur) => cur === address ? null : cur), 1500);
8486
- }, []);
8487
- const backToSourceSelector = useCallback(() => {
8488
- setSession(null);
8489
- setCopiedAddress(null);
8490
- setError(null);
8491
- lastCreatedOptionRef.current = null;
8492
- if (selectedOption) {
8493
- void createSession(selectedOption);
8494
- }
8495
- }, [selectedOption, createSession]);
8769
+ ];
8770
+ if (input.selectedAccountId) {
8771
+ actions.push({
8772
+ type: "SELECT_ACCOUNT",
8773
+ accountId: input.selectedAccountId,
8774
+ walletId: effective.walletId
8775
+ });
8776
+ actions.push({
8777
+ type: "SELECT_TOKEN",
8778
+ walletId: effective.walletId,
8779
+ tokenSymbol: effective.symbol
8780
+ });
8781
+ }
8782
+ actions.push({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
8783
+ actions.push({ type: "CONFIRM_SETUP_DEPOSIT" });
8496
8784
  return {
8497
- sourceOptions,
8498
- loadingSources,
8499
- selectedToken,
8500
- selectedChainId,
8501
- session,
8502
- loading,
8503
- error,
8504
- qrReady,
8505
- copiedAddress,
8506
- tokenChoices,
8507
- chainChoices,
8508
- tokenLogoUriBySymbol,
8509
- selectedOption,
8510
- tokensForSelectedChain,
8511
- chainsForSelectedToken,
8512
- screen,
8513
- sessionFeeCopy,
8514
- depositAddress,
8515
- createSession,
8516
- copyDepositAddress,
8517
- backToSourceSelector,
8518
- resetSessionForNewSelection,
8519
- advanceMockStatus,
8520
- selectToken,
8521
- selectChainId
8785
+ kind: "proceed",
8786
+ actions,
8787
+ resolveSource: {
8788
+ chainName: effective.chainName,
8789
+ tokenSymbol: effective.symbol
8790
+ }
8522
8791
  };
8523
8792
  }
8524
- var MOCK_STATUSES = [
8525
- "awaiting_deposit",
8526
- "deposit_received",
8527
- "routing",
8528
- "completed",
8529
- "failed"
8530
- ];
8531
- function DevMockPanel({
8532
- status,
8533
- onSetStatus,
8534
- hasSession
8535
- }) {
8536
- const [collapsed, setCollapsed] = useState(true);
8537
- return /* @__PURE__ */ jsxs("div", { style: devPanelContainerStyle, children: [
8538
- /* @__PURE__ */ jsx(
8539
- "button",
8540
- {
8541
- type: "button",
8542
- onClick: () => setCollapsed((c) => !c),
8543
- style: devPanelToggleStyle,
8544
- children: collapsed ? "DEV" : "\u2715"
8545
- }
8546
- ),
8547
- !collapsed && /* @__PURE__ */ jsxs("div", { style: devPanelBodyStyle, children: [
8548
- /* @__PURE__ */ jsx("span", { style: devPanelLabelStyle, children: hasSession ? `Status: ${status}` : "No session" }),
8549
- MOCK_STATUSES.map((s) => /* @__PURE__ */ jsx(
8550
- "button",
8551
- {
8552
- type: "button",
8553
- disabled: !hasSession || status === s,
8554
- onClick: () => onSetStatus(s),
8555
- style: devPanelButtonStyle(status === s),
8556
- children: s.replace(/_/g, " ")
8557
- },
8558
- s
8559
- ))
8560
- ] })
8561
- ] });
8562
- }
8563
- var devPanelContainerStyle = {
8564
- position: "fixed",
8565
- bottom: 16,
8566
- right: 16,
8567
- zIndex: 99999,
8568
- display: "flex",
8569
- flexDirection: "column",
8570
- alignItems: "flex-end",
8571
- gap: 8,
8572
- fontFamily: "monospace",
8573
- fontSize: 12
8574
- };
8575
- var devPanelToggleStyle = {
8576
- background: "#1a1a1a",
8577
- border: "1px solid #333",
8578
- borderRadius: 8,
8579
- color: "#0f0",
8580
- cursor: "pointer",
8581
- fontSize: 11,
8582
- fontFamily: "monospace",
8583
- fontWeight: 700,
8584
- height: 36,
8585
- width: 36,
8586
- display: "flex",
8587
- alignItems: "center",
8588
- justifyContent: "center"
8589
- };
8590
- var devPanelBodyStyle = {
8591
- background: "#1a1a1a",
8592
- border: "1px solid #333",
8593
- borderRadius: 10,
8594
- display: "flex",
8595
- flexDirection: "column",
8596
- gap: 4,
8597
- padding: 10,
8598
- minWidth: 160
8599
- };
8600
- var devPanelLabelStyle = {
8601
- color: "#888",
8602
- fontSize: 10,
8603
- marginBottom: 4,
8604
- textTransform: "uppercase",
8605
- letterSpacing: "0.05em"
8606
- };
8607
- var devPanelButtonStyle = (active) => ({
8608
- background: active ? "#0f0" : "#2a2a2a",
8609
- border: "1px solid #444",
8610
- borderRadius: 6,
8611
- color: active ? "#000" : "#ccc",
8612
- cursor: active ? "default" : "pointer",
8613
- fontFamily: "monospace",
8614
- fontSize: 11,
8615
- fontWeight: active ? 700 : 400,
8616
- padding: "6px 10px",
8617
- textAlign: "left",
8618
- textTransform: "capitalize"
8619
- });
8620
8793
  function ScreenLayout({ children, footer }) {
8621
8794
  const { tokens, theme, isMobileApp } = useBlinkConfig();
8622
8795
  const isRedesign = theme.endsWith("New");
@@ -10331,16 +10504,21 @@ function DepositQrCodeImpl({
10331
10504
  depToken
10332
10505
  }) {
10333
10506
  const { tokens } = useBlinkConfig();
10334
- const [dataUrl, setDataUrl] = useState(null);
10335
10507
  const uri = formatDepositUri(address, chainId, depToken);
10508
+ const dark = tokens.text;
10509
+ const light = tokens.bg;
10510
+ const [dataUrl, setDataUrl] = useState(
10511
+ () => getCachedQrDataUrl(uri, { dark, light })
10512
+ );
10336
10513
  useEffect(() => {
10514
+ const cached = getCachedQrDataUrl(uri, { dark, light });
10515
+ if (cached) {
10516
+ setDataUrl(cached);
10517
+ return;
10518
+ }
10337
10519
  let cancelled = false;
10338
- QRCode.toDataURL(uri, {
10339
- errorCorrectionLevel: "H",
10340
- margin: 1,
10341
- width: 203,
10342
- color: { dark: tokens.text, light: tokens.bg }
10343
- }).then((url) => {
10520
+ setDataUrl(null);
10521
+ getOrRenderQrDataUrl(uri, { dark, light }).then((url) => {
10344
10522
  if (!cancelled) setDataUrl(url);
10345
10523
  }).catch(() => {
10346
10524
  if (!cancelled) setDataUrl(null);
@@ -10348,7 +10526,7 @@ function DepositQrCodeImpl({
10348
10526
  return () => {
10349
10527
  cancelled = true;
10350
10528
  };
10351
- }, [uri]);
10529
+ }, [uri, dark, light]);
10352
10530
  return /* @__PURE__ */ jsx("div", { style: qrFrameStyle(tokens.bgCardTranslucent), children: dataUrl ? /* @__PURE__ */ jsxs("div", { style: qrContainerStyle, children: [
10353
10531
  /* @__PURE__ */ jsx("img", { src: dataUrl, alt: "Deposit QR code", style: qrImageStyle }),
10354
10532
  /* @__PURE__ */ jsx("img", { src: BLINK_QR_LOGO, alt: "", style: qrLogoStyle })
@@ -11436,7 +11614,7 @@ function EnterAmountScreen({
11436
11614
  )
11437
11615
  ] }),
11438
11616
  children: [
11439
- /* @__PURE__ */ jsx(ScreenHeader, { right: headerRight }),
11617
+ /* @__PURE__ */ jsx(ScreenHeader, { title: "Deposit", right: headerRight }),
11440
11618
  /* @__PURE__ */ jsxs("div", { style: contentStyle4(isDesktop), children: [
11441
11619
  isDesktop ? /* @__PURE__ */ jsxs("div", { style: desktopHeroRowStyle(heroColor, getDesktopHeroFontSize(value)), children: [
11442
11620
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: dollarStyle2(isZero), children: "$" }),
@@ -12777,7 +12955,8 @@ function SetupScreen({
12777
12955
  onLogout,
12778
12956
  loading,
12779
12957
  loadingShimmersEnabled = false,
12780
- error
12958
+ error,
12959
+ selectedTokenSymbol
12781
12960
  }) {
12782
12961
  const { tokens } = useBlinkConfig();
12783
12962
  const [selectedPreset, setSelectedPreset] = useState(DEFAULT_MAX);
@@ -12886,7 +13065,8 @@ function SetupScreen({
12886
13065
  style: illustrationStyle3
12887
13066
  }
12888
13067
  ),
12889
- /* @__PURE__ */ jsx("h2", { style: headingStyle7(tokens.text), children: "Next time deposit\nUSDC in one tap" }),
13068
+ /* @__PURE__ */ jsx("h2", { style: headingStyle7(tokens.text), children: `Next time deposit
13069
+ ${selectedTokenSymbol ?? "USDC"} in one tap` }),
12890
13070
  /* @__PURE__ */ jsx("p", { style: subtitleStyle5(tokens.textSecondary), children: "Set a cap for passkey deposits.\nYou always stay in control." }),
12891
13071
  error && /* @__PURE__ */ jsx("div", { style: errorBannerStyle5(tokens), children: error }),
12892
13072
  /* @__PURE__ */ jsx("div", { style: chipsRowStyle, children: PRESETS.map(({ label, value }) => {
@@ -12914,8 +13094,8 @@ var contentStyle9 = {
12914
13094
  alignItems: "center",
12915
13095
  justifyContent: "center",
12916
13096
  textAlign: "center",
12917
- gap: 16,
12918
- padding: "48px 8px 8px"
13097
+ gap: 12,
13098
+ padding: "8px 0"
12919
13099
  };
12920
13100
  var shimmerContentStyle = {
12921
13101
  display: "flex",
@@ -13286,7 +13466,7 @@ function SetupDepositScreen({
13286
13466
  NotificationBanner,
13287
13467
  {
13288
13468
  variant: "negative",
13289
- title: "Deposit amount exceeds balance",
13469
+ title: "Deposit amount exceeds stablecoin balance",
13290
13470
  description: "Pick another asset or add more funds to your wallet"
13291
13471
  }
13292
13472
  ) : /* @__PURE__ */ jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
@@ -13904,7 +14084,7 @@ function DepositScreen({
13904
14084
  NotificationBanner,
13905
14085
  {
13906
14086
  variant: "negative",
13907
- title: "Deposit amount exceeds balance",
14087
+ title: "Deposit amount exceeds stablecoin balance",
13908
14088
  description: "Pick another asset or add more funds to your wallet"
13909
14089
  }
13910
14090
  ) : needsAuthorization ? /* @__PURE__ */ jsx(
@@ -14826,12 +15006,12 @@ function DepositAddressScreen({
14826
15006
  ),
14827
15007
  /* @__PURE__ */ jsxs("div", { style: contentStyle16, children: [
14828
15008
  /* @__PURE__ */ jsx("h2", { style: depositTitleStyle(tokens.text), children: "Deposit Address" }),
14829
- qrReady && session ? /* @__PURE__ */ jsx(
15009
+ qrReady && session && selectedOption ? /* @__PURE__ */ jsx(
14830
15010
  DepositQrCode,
14831
15011
  {
14832
15012
  address: session.depositAddress,
14833
- chainId: session.source.chainId,
14834
- depToken: session.source.tokenAddress
15013
+ chainId: selectedOption.chainId,
15014
+ depToken: selectedOption.tokenAddress
14835
15015
  }
14836
15016
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
14837
15017
  /* @__PURE__ */ jsx("style", { children: `
@@ -15850,12 +16030,7 @@ var errorBannerStyle8 = (themeTokens) => ({
15850
16030
  textAlign: "left"
15851
16031
  });
15852
16032
  function ManualTransferFlow({
15853
- destination,
15854
- merchantAuthorization,
15855
- idempotencyKey,
15856
- mock = false,
15857
- onComplete,
15858
- onError,
16033
+ hasMerchantAuthorization,
15859
16034
  onCreatePasskey,
15860
16035
  createPasskeyLoading = false,
15861
16036
  createPasskeyError = null,
@@ -15884,17 +16059,9 @@ function ManualTransferFlow({
15884
16059
  depositAddress,
15885
16060
  copyDepositAddress,
15886
16061
  backToSourceSelector,
15887
- advanceMockStatus,
15888
16062
  selectToken,
15889
16063
  selectChainId
15890
- } = useManualTransferSession({
15891
- destination,
15892
- merchantAuthorization,
15893
- idempotencyKey,
15894
- mock,
15895
- onComplete,
15896
- onError
15897
- });
16064
+ } = useManualTransferSessionContext();
15898
16065
  const [passkeyCreated, setPasskeyCreated] = useState(false);
15899
16066
  const prevPasskeyLoadingRef = useRef(false);
15900
16067
  useEffect(() => {
@@ -15903,9 +16070,8 @@ function ManualTransferFlow({
15903
16070
  }
15904
16071
  prevPasskeyLoadingRef.current = createPasskeyLoading;
15905
16072
  }, [createPasskeyLoading, createPasskeyError]);
15906
- const DEV_MOCK_STATUS = mock;
15907
16073
  let screenContent = null;
15908
- if (!merchantAuthorization) {
16074
+ if (!hasMerchantAuthorization) {
15909
16075
  screenContent = /* @__PURE__ */ jsxs(ScreenLayout, { footer: /* @__PURE__ */ jsx(PrimaryButton, { onClick: onBack, children: "Back" }), children: [
15910
16076
  /* @__PURE__ */ jsx(ScreenHeader, { onBack }),
15911
16077
  /* @__PURE__ */ jsxs("div", { style: contentStyle20, children: [
@@ -15975,17 +16141,7 @@ function ManualTransferFlow({
15975
16141
  if (!screenContent) {
15976
16142
  return null;
15977
16143
  }
15978
- return /* @__PURE__ */ jsxs(Fragment, { children: [
15979
- screenContent,
15980
- DEV_MOCK_STATUS && merchantAuthorization && /* @__PURE__ */ jsx(
15981
- DevMockPanel,
15982
- {
15983
- status: session?.status ?? null,
15984
- onSetStatus: advanceMockStatus,
15985
- hasSession: !!session
15986
- }
15987
- )
15988
- ] });
16144
+ return screenContent;
15989
16145
  }
15990
16146
  var contentStyle20 = {
15991
16147
  alignItems: "center",
@@ -16285,6 +16441,7 @@ function buildSetupDepositScreenProps({
16285
16441
  function buildSetupScreenProps({
16286
16442
  flow,
16287
16443
  remote,
16444
+ derived,
16288
16445
  forms,
16289
16446
  handlers
16290
16447
  }) {
@@ -16293,11 +16450,12 @@ function buildSetupScreenProps({
16293
16450
  const waitingForDesktopWalletConnection = flow.isDesktop && remote.authExecutorExecuting && remote.pendingOneTapSetup == null && (remote.authExecutorCurrentAction == null || remote.authExecutorCurrentAction.type === "OPEN_PROVIDER");
16294
16451
  return {
16295
16452
  onSetupOneTap: handlers.onSetupOneTap,
16296
- onBack: handlers.onBackFromSubflow,
16297
- onLogout: handlers.onLogout,
16453
+ onBack: flow.isDesktop ? handlers.onBackFromSubflow : void 0,
16454
+ onLogout: flow.isDesktop ? handlers.onLogout : void 0,
16298
16455
  loading: savingOneTapLimit,
16299
16456
  loadingShimmersEnabled: waitingForDesktopWalletConnection,
16300
- error: state.error
16457
+ error: state.error,
16458
+ selectedTokenSymbol: derived.selectedSource?.token.symbol
16301
16459
  };
16302
16460
  }
16303
16461
  function buildConfirmSignScreenProps({
@@ -16574,12 +16732,7 @@ function StepRendererContent({
16574
16732
  return /* @__PURE__ */ jsx(
16575
16733
  ManualTransferFlow,
16576
16734
  {
16577
- destination: flow.destination,
16578
- merchantAuthorization: flow.merchantAuthorization,
16579
- idempotencyKey: flow.idempotencyKey,
16580
- mock: flow.mock,
16581
- onComplete: flow.onComplete,
16582
- onError: flow.onError,
16735
+ hasMerchantAuthorization: flow.merchantAuthorization != null,
16583
16736
  onCreatePasskey: handlers.onSignupWithPasskey,
16584
16737
  createPasskeyLoading: flow.passkeyLoading,
16585
16738
  createPasskeyError: flow.state.error,
@@ -18797,8 +18950,11 @@ function usePasskeyCheckEffect(deps) {
18797
18950
  setupAccountIdRef,
18798
18951
  reauthSessionIdRef,
18799
18952
  reauthTokenRef,
18800
- pollingTransferIdRef
18953
+ pollingTransferIdRef,
18954
+ reloadAccounts
18801
18955
  } = deps;
18956
+ const reloadAccountsRef = useRef(reloadAccounts);
18957
+ reloadAccountsRef.current = reloadAccounts;
18802
18958
  const { getAccessToken: privyGetAccessToken, user } = usePrivy();
18803
18959
  const effectiveGetAccessToken = deps.getAccessToken ?? privyGetAccessToken;
18804
18960
  const onCompleteRef = useRef(deps.onComplete);
@@ -18928,6 +19084,11 @@ function usePasskeyCheckEffect(deps) {
18928
19084
  mobileSetupFlowRef.current = false;
18929
19085
  setupAccountIdRef.current = null;
18930
19086
  clearMobileFlowState();
19087
+ try {
19088
+ await reloadAccountsRef.current();
19089
+ } catch {
19090
+ }
19091
+ dispatch({ type: "MOBILE_SETUP_COMPLETE" });
18931
19092
  return;
18932
19093
  }
18933
19094
  if (persisted.accountId && !persisted.transferId) {
@@ -18936,7 +19097,14 @@ function usePasskeyCheckEffect(deps) {
18936
19097
  const session = await fetchAuthorizationSession(apiBaseUrl, persisted.sessionId);
18937
19098
  if (cancelled) return;
18938
19099
  if (session.status === "AUTHORIZED") {
19100
+ mobileSetupFlowRef.current = false;
19101
+ setupAccountIdRef.current = null;
18939
19102
  clearMobileFlowState();
19103
+ try {
19104
+ await reloadAccountsRef.current();
19105
+ } catch {
19106
+ }
19107
+ dispatch({ type: "MOBILE_SETUP_COMPLETE" });
18940
19108
  return;
18941
19109
  }
18942
19110
  } catch {
@@ -19401,25 +19569,24 @@ function useMobilePollingEffect(deps) {
19401
19569
  dispatch({ type: "MOBILE_SETUP_COMPLETE" });
19402
19570
  };
19403
19571
  const pollWalletActive = async () => {
19572
+ if (setupSessionId) {
19573
+ try {
19574
+ const session = await fetchAuthorizationSession(apiBaseUrl, setupSessionId);
19575
+ if (cancelled) return;
19576
+ if (session.status === "AUTHORIZED") {
19577
+ await completeSetup();
19578
+ return;
19579
+ }
19580
+ } catch {
19581
+ }
19582
+ }
19404
19583
  try {
19405
19584
  const token = await getAccessTokenRef.current();
19406
19585
  if (!token || cancelled) return;
19407
19586
  const acct = await fetchAccount(apiBaseUrl, token, accountId, credentialId);
19408
19587
  if (cancelled) return;
19409
- const hasActive = acct.wallets.some((w) => w.status === "ACTIVE");
19410
- if (hasActive) {
19588
+ if (acct.wallets.some((w) => w.status === "ACTIVE")) {
19411
19589
  await completeSetup();
19412
- return;
19413
- }
19414
- if (setupSessionId) {
19415
- try {
19416
- const session = await fetchAuthorizationSession(apiBaseUrl, setupSessionId);
19417
- if (cancelled) return;
19418
- if (session.status === "AUTHORIZED") {
19419
- await completeSetup();
19420
- }
19421
- } catch {
19422
- }
19423
19590
  }
19424
19591
  } catch {
19425
19592
  }
@@ -19854,7 +20021,6 @@ function BlinkPayment(props) {
19854
20021
  function BlinkPaymentInner({
19855
20022
  destination,
19856
20023
  initialScreen,
19857
- mock,
19858
20024
  onComplete,
19859
20025
  onError,
19860
20026
  useWalletConnector: useWalletConnectorProp,
@@ -19943,6 +20109,7 @@ function BlinkPaymentInner({
19943
20109
  minTransferAmountUsd: effectiveMinDepositAmountUsd
19944
20110
  });
19945
20111
  const feeEstimateEnabled = effectiveAuthenticated && (state.phase.step === "deposit" || state.phase.step === "wallet-setup" || state.phase.step === "one-tap-setup");
20112
+ const manualTransferActive = screenForPhase(state.phase) === "manual-transfer";
19946
20113
  const setupSelectedSourceOption = useMemo(() => {
19947
20114
  const options = sourceSelection.pendingSelectSourceAction?.metadata?.options ?? [];
19948
20115
  return resolveSelectSourceOption(
@@ -20089,13 +20256,14 @@ function BlinkPaymentInner({
20089
20256
  idempotencyKey
20090
20257
  });
20091
20258
  const accountSwitchSessionId = state.setupAuthorizationSessionId;
20259
+ const accountSwitchListenerArmed = accountSwitchSessionId != null && state.desktopWait == null;
20092
20260
  const handleProviderWalletAccountSwitch = provider.handleWalletAccountSwitch;
20093
20261
  const onWalletAccountChanged = useCallback(async (change) => {
20094
20262
  if (!accountSwitchSessionId) return;
20095
20263
  await handleProviderWalletAccountSwitch(change, accountSwitchSessionId);
20096
20264
  }, [handleProviderWalletAccountSwitch, accountSwitchSessionId]);
20097
20265
  useWalletAccountSwitchEffect({
20098
- enabled: accountSwitchSessionId != null,
20266
+ enabled: accountSwitchListenerArmed,
20099
20267
  isDesktop,
20100
20268
  onAccountChanged: onWalletAccountChanged
20101
20269
  });
@@ -20191,6 +20359,7 @@ function BlinkPaymentInner({
20191
20359
  reauthTokenRef: mobileFlowRefs.reauthTokenRef,
20192
20360
  pollingTransferIdRef: transfer.pollingTransferIdRef,
20193
20361
  handleAuthorizedMobileReturn: mobileFlow.handleAuthorizedMobileReturn,
20362
+ reloadAccounts: transfer.reloadAccounts,
20194
20363
  onComplete,
20195
20364
  getAccessToken: effectiveGetAccessToken,
20196
20365
  pendingTransferFlowKey
@@ -20357,15 +20526,18 @@ function BlinkPaymentInner({
20357
20526
  dispatch,
20358
20527
  orchestrator
20359
20528
  ]);
20360
- const handleBackFromSetupDeposit = useCallback(() => {
20529
+ const handleBackFromSetupDeposit = useCallback(async () => {
20361
20530
  orchestrator.cancelPendingFlow();
20362
20531
  authExecutor.cancelPendingExecution();
20363
20532
  clearScreenErrors();
20533
+ if (isDesktop) {
20534
+ await revokeAndDisconnectActiveWagmiConnector(wagmiConfig);
20535
+ }
20364
20536
  dispatch({ type: "RESTORE_SELECTION" });
20365
20537
  dispatch({ type: "CLEAR_SETUP_DEPOSIT_TOKEN" });
20366
20538
  dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: null });
20367
20539
  dispatch({ type: "SET_USER_INTENT", intent: { step: "wallet-picker", reason: "switch" } });
20368
- }, [authExecutor, clearScreenErrors, orchestrator, dispatch]);
20540
+ }, [authExecutor, clearScreenErrors, orchestrator, dispatch, isDesktop, wagmiConfig]);
20369
20541
  const handleAuthorizationRetry = useCallback(() => {
20370
20542
  void (async () => {
20371
20543
  try {
@@ -20405,6 +20577,11 @@ function BlinkPaymentInner({
20405
20577
  clearScreenErrors();
20406
20578
  dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount });
20407
20579
  dispatch({ type: "SET_USER_INTENT", intent: { step: "wallet-picker", reason: "switch" } });
20580
+ void (async () => {
20581
+ await revokeAndDisconnectActiveWagmiConnector(wagmiConfig);
20582
+ await authExecutor.resetWalletConnect().catch(() => {
20583
+ });
20584
+ })();
20408
20585
  },
20409
20586
  onBackFromSetupDeposit: handleBackFromSetupDeposit,
20410
20587
  onBackFromSubflow: () => {
@@ -20453,65 +20630,76 @@ function BlinkPaymentInner({
20453
20630
  handleSetDepositToken,
20454
20631
  handleConfirmSetupDeposit,
20455
20632
  handleBackFromSetupDeposit,
20456
- handleAuthorizationRetry
20633
+ handleAuthorizationRetry,
20634
+ wagmiConfig
20457
20635
  ]);
20458
20636
  return /* @__PURE__ */ jsx(EffectiveDepositAmountProvider, { value: effectiveDepositAmount, children: /* @__PURE__ */ jsx(
20459
- StepRenderer,
20637
+ ManualTransferSessionProvider,
20460
20638
  {
20461
- flow: {
20462
- state,
20463
- authenticated: effectiveAuthenticated,
20464
- passkeyLoading: auth.passkeyLoginStatus !== "initial" && auth.passkeyLoginStatus !== "done" && auth.passkeyLoginStatus !== "error" || auth.passkeySignupStatus !== "initial" && auth.passkeySignupStatus !== "done" && auth.passkeySignupStatus !== "error" || auth.passkeySignupPopupActive,
20465
- isDesktop,
20466
- isMobileApp: isMobileApp ?? false,
20467
- merchantName,
20468
- onBack,
20469
- onDismiss,
20470
- depositAmount,
20471
- effectiveDepositAmount,
20472
- minTransferAmountUsd,
20473
- destination,
20474
- merchantAuthorization,
20475
- idempotencyKey,
20476
- mock,
20477
- onComplete,
20478
- onError
20479
- },
20480
- remote: {
20481
- pollingTransfer: polling.transfer,
20482
- pollingError: polling.error,
20483
- authExecutorError: authExecutor.error,
20484
- authExecutorExecuting: authExecutor.executing,
20485
- authExecutorCurrentAction: authExecutor.currentAction,
20486
- pendingOneTapSetup: orchestrator.pendingOneTapAction,
20487
- setupAuthorizationComplete,
20488
- transferSigningSigning: transferSigning.signing,
20489
- transferSigningError: transferSigning.error,
20490
- transferSigningPasskeyDismissed: transferSigning.passkeyDismissed,
20491
- pendingSelectSource: orchestrator.pendingSelectSourceAction
20492
- },
20493
- derived: {
20494
- pendingConnections: derived.pendingConnections,
20495
- depositEligibleAccounts: derived.depositEligibleAccounts,
20496
- sourceName: derived.sourceName,
20497
- maxSourceBalance: derived.maxSourceBalance,
20498
- tokenCount: derived.tokenCount,
20499
- selectedAccount: derived.selectedAccount,
20500
- selectedSource: derived.selectedSource,
20501
- selectSourceChoices: sourceSelection.selectSourceChoices,
20502
- selectSourceRecommended: sourceSelection.selectSourceRecommended,
20503
- selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance
20504
- },
20505
- forms: {
20506
- selectSourceChainName: sourceSelection.selectSourceChainName,
20507
- selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
20508
- savingOneTapLimit: oneTapSetup.savingOneTapLimit,
20509
- depositQuoteId: depositFee.quoteId,
20510
- depositQuoteFee: depositFee.quoteFee,
20511
- depositQuoteLoading: depositFee.quoteLoading,
20512
- depositQuoteError: depositFee.quoteError
20513
- },
20514
- handlers
20639
+ destination,
20640
+ merchantAuthorization,
20641
+ idempotencyKey,
20642
+ onComplete,
20643
+ onError,
20644
+ pollEnabled: manualTransferActive,
20645
+ children: /* @__PURE__ */ jsx(
20646
+ StepRenderer,
20647
+ {
20648
+ flow: {
20649
+ state,
20650
+ authenticated: effectiveAuthenticated,
20651
+ passkeyLoading: auth.passkeyLoginStatus !== "initial" && auth.passkeyLoginStatus !== "done" && auth.passkeyLoginStatus !== "error" || auth.passkeySignupStatus !== "initial" && auth.passkeySignupStatus !== "done" && auth.passkeySignupStatus !== "error" || auth.passkeySignupPopupActive,
20652
+ isDesktop,
20653
+ isMobileApp: isMobileApp ?? false,
20654
+ merchantName,
20655
+ onBack,
20656
+ onDismiss,
20657
+ depositAmount,
20658
+ effectiveDepositAmount,
20659
+ minTransferAmountUsd,
20660
+ destination,
20661
+ merchantAuthorization,
20662
+ idempotencyKey,
20663
+ onComplete,
20664
+ onError
20665
+ },
20666
+ remote: {
20667
+ pollingTransfer: polling.transfer,
20668
+ pollingError: polling.error,
20669
+ authExecutorError: authExecutor.error,
20670
+ authExecutorExecuting: authExecutor.executing,
20671
+ authExecutorCurrentAction: authExecutor.currentAction,
20672
+ pendingOneTapSetup: orchestrator.pendingOneTapAction,
20673
+ setupAuthorizationComplete,
20674
+ transferSigningSigning: transferSigning.signing,
20675
+ transferSigningError: transferSigning.error,
20676
+ transferSigningPasskeyDismissed: transferSigning.passkeyDismissed,
20677
+ pendingSelectSource: orchestrator.pendingSelectSourceAction
20678
+ },
20679
+ derived: {
20680
+ pendingConnections: derived.pendingConnections,
20681
+ depositEligibleAccounts: derived.depositEligibleAccounts,
20682
+ sourceName: derived.sourceName,
20683
+ maxSourceBalance: derived.maxSourceBalance,
20684
+ tokenCount: derived.tokenCount,
20685
+ selectedAccount: derived.selectedAccount,
20686
+ selectedSource: derived.selectedSource,
20687
+ selectSourceChoices: sourceSelection.selectSourceChoices,
20688
+ selectSourceRecommended: sourceSelection.selectSourceRecommended,
20689
+ selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance
20690
+ },
20691
+ forms: {
20692
+ selectSourceChainName: sourceSelection.selectSourceChainName,
20693
+ selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
20694
+ savingOneTapLimit: oneTapSetup.savingOneTapLimit,
20695
+ depositQuoteId: depositFee.quoteId,
20696
+ depositQuoteFee: depositFee.quoteFee,
20697
+ depositQuoteLoading: depositFee.quoteLoading,
20698
+ depositQuoteError: depositFee.quoteError
20699
+ },
20700
+ handlers
20701
+ }
20702
+ )
20515
20703
  }
20516
20704
  ) });
20517
20705
  }