@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.cjs CHANGED
@@ -1154,12 +1154,225 @@ function useBlinkDepositAmount() {
1154
1154
  };
1155
1155
  }
1156
1156
 
1157
+ // src/walletConnectorResolver.ts
1158
+ function normalize(value) {
1159
+ return (value ?? "").trim().toLowerCase();
1160
+ }
1161
+ function buildTargetMatchers(target) {
1162
+ const values = [target.providerName, target.wagmiConnectorId].map(normalize).filter(Boolean);
1163
+ const aliases = new Set(values);
1164
+ for (const value of values) {
1165
+ if (value.includes("metamask")) {
1166
+ aliases.add("metamask");
1167
+ aliases.add("io.metamask");
1168
+ }
1169
+ if (value === "base" || value === "base account" || value === "base app" || value.includes("coinbase")) {
1170
+ aliases.add("base");
1171
+ aliases.add("coinbase");
1172
+ aliases.add("coinbasewalletsdk");
1173
+ }
1174
+ if (value.includes("trust")) {
1175
+ aliases.add("trust");
1176
+ aliases.add("trustwallet");
1177
+ }
1178
+ if (value.includes("okx")) {
1179
+ aliases.add("okx");
1180
+ aliases.add("okxwallet");
1181
+ aliases.add("com.okx.wallet");
1182
+ }
1183
+ if (value.includes("rabby")) {
1184
+ aliases.add("rabby");
1185
+ aliases.add("io.rabby");
1186
+ }
1187
+ if (value.includes("phantom")) {
1188
+ aliases.add("phantom");
1189
+ aliases.add("app.phantom");
1190
+ }
1191
+ if (value.includes("injected")) {
1192
+ aliases.add("injected");
1193
+ }
1194
+ }
1195
+ return [...aliases];
1196
+ }
1197
+ function connectorMatchesWallet(connector, target) {
1198
+ if (!connector) {
1199
+ return false;
1200
+ }
1201
+ const connectorId = normalize(connector.id);
1202
+ const connectorName = normalize(connector.name);
1203
+ if (target.wagmiConnectorId) {
1204
+ const targetConnectorId = normalize(target.wagmiConnectorId);
1205
+ if (connectorId === targetConnectorId) {
1206
+ return true;
1207
+ }
1208
+ }
1209
+ const matchers = buildTargetMatchers(target);
1210
+ if (matchers.length === 0) {
1211
+ return false;
1212
+ }
1213
+ return matchers.some(
1214
+ (matcher) => connectorId === matcher || connectorName === matcher || connectorId.includes(matcher) || connectorName.includes(matcher)
1215
+ );
1216
+ }
1217
+ function resolveWalletConnector(connectors, target) {
1218
+ if (target.wagmiConnectorId || target.providerName) {
1219
+ return connectors.find((connector) => connectorMatchesWallet(connector, target));
1220
+ }
1221
+ const metaMaskConnector = connectors.find((connector) => connectorMatchesWallet(
1222
+ connector,
1223
+ { wagmiConnectorId: "metamask" }
1224
+ ));
1225
+ return metaMaskConnector ?? connectors[0];
1226
+ }
1227
+
1228
+ // src/wagmiRevokeAndDisconnect.ts
1229
+ var REVOKE_METHOD = "wallet_revokePermissions";
1230
+ var REVOKE_PARAMS = [{ eth_accounts: {} }];
1231
+ var EIP6963_DISCOVERY_MS = 200;
1232
+ async function tryRevoke(provider, context) {
1233
+ if (!provider?.request) return false;
1234
+ try {
1235
+ await provider.request({ method: REVOKE_METHOD, params: REVOKE_PARAMS });
1236
+ console.info("[blink-sdk][revoke] ok", context);
1237
+ return true;
1238
+ } catch (err) {
1239
+ const code = err?.code;
1240
+ const message = err instanceof Error ? err.message : String(err);
1241
+ console.info("[blink-sdk][revoke] failed", { ...context, code, message });
1242
+ return false;
1243
+ }
1244
+ }
1245
+ async function getProviderForConnector(connector) {
1246
+ try {
1247
+ const provider = await connector.getProvider();
1248
+ return provider ?? null;
1249
+ } catch (err) {
1250
+ const message = err instanceof Error ? err.message : String(err);
1251
+ console.info("[blink-sdk][revoke] connector.getProvider() threw", {
1252
+ connectorId: connector.id,
1253
+ message
1254
+ });
1255
+ return null;
1256
+ }
1257
+ }
1258
+ async function discoverEip6963Providers(windowImpl, timeoutMs) {
1259
+ return new Promise((resolve) => {
1260
+ const seen = /* @__PURE__ */ new Map();
1261
+ const onAnnounce = (event) => {
1262
+ const detail = event.detail;
1263
+ if (!detail || !detail.info?.uuid) return;
1264
+ seen.set(detail.info.uuid, detail);
1265
+ };
1266
+ windowImpl.addEventListener("eip6963:announceProvider", onAnnounce);
1267
+ try {
1268
+ windowImpl.dispatchEvent(new Event("eip6963:requestProvider"));
1269
+ } catch {
1270
+ }
1271
+ const timer = setTimeout(() => {
1272
+ windowImpl.removeEventListener(
1273
+ "eip6963:announceProvider",
1274
+ onAnnounce
1275
+ );
1276
+ resolve([...seen.values()]);
1277
+ }, timeoutMs);
1278
+ if (typeof timer.unref === "function") {
1279
+ timer.unref();
1280
+ }
1281
+ });
1282
+ }
1283
+ async function probeAccounts(provider) {
1284
+ if (!provider.request) return [];
1285
+ try {
1286
+ const accounts = await provider.request({ method: "eth_accounts" });
1287
+ return Array.isArray(accounts) ? accounts : [];
1288
+ } catch {
1289
+ return [];
1290
+ }
1291
+ }
1292
+ async function revokeAndDisconnectConnector(deps) {
1293
+ const activeConnector = deps.getActiveConnector();
1294
+ if (activeConnector) {
1295
+ const provider = await getProviderForConnector(activeConnector);
1296
+ await tryRevoke(provider, {
1297
+ layer: 1,
1298
+ source: "wagmi-connector",
1299
+ rdns: activeConnector.id
1300
+ });
1301
+ await deps.disconnectFn(activeConnector).catch(() => {
1302
+ });
1303
+ return;
1304
+ }
1305
+ let recentRdns = null;
1306
+ try {
1307
+ recentRdns = await deps.getRecentConnectorId() ?? null;
1308
+ } catch {
1309
+ }
1310
+ if (recentRdns) {
1311
+ const matchedConnector = resolveWalletConnector(deps.listConnectors(), {
1312
+ wagmiConnectorId: recentRdns
1313
+ });
1314
+ if (matchedConnector) {
1315
+ const provider = await getProviderForConnector(matchedConnector);
1316
+ const revoked = await tryRevoke(provider, {
1317
+ layer: 2,
1318
+ source: "recent-connector",
1319
+ rdns: matchedConnector.id
1320
+ });
1321
+ await deps.disconnectFn(matchedConnector).catch(() => {
1322
+ });
1323
+ if (revoked) return;
1324
+ } else {
1325
+ console.info("[blink-sdk][revoke] recentConnectorId has no matching connector", {
1326
+ recentRdns
1327
+ });
1328
+ }
1329
+ }
1330
+ const win = deps.windowImpl ?? (typeof window === "undefined" ? null : window);
1331
+ if (!win) {
1332
+ console.warn("[blink-sdk][revoke] no candidate found and no window for EIP-6963 fallback");
1333
+ return;
1334
+ }
1335
+ const announces = await discoverEip6963Providers(
1336
+ win,
1337
+ deps.eip6963TimeoutMs ?? EIP6963_DISCOVERY_MS
1338
+ );
1339
+ if (announces.length === 0) {
1340
+ console.warn("[blink-sdk][revoke] EIP-6963 enumeration found no providers");
1341
+ return;
1342
+ }
1343
+ let anyRevoked = false;
1344
+ for (const detail of announces) {
1345
+ const accounts = await probeAccounts(detail.provider);
1346
+ if (accounts.length === 0) continue;
1347
+ const ok = await tryRevoke(detail.provider, {
1348
+ layer: 3,
1349
+ source: "eip6963-broadcast",
1350
+ rdns: detail.info.rdns
1351
+ });
1352
+ if (ok) anyRevoked = true;
1353
+ }
1354
+ if (!anyRevoked) {
1355
+ console.warn("[blink-sdk][revoke] EIP-6963 enumeration found no providers with a grant");
1356
+ }
1357
+ }
1358
+ async function revokeAndDisconnectActiveWagmiConnector(wagmiConfig) {
1359
+ await revokeAndDisconnectConnector({
1360
+ getActiveConnector: () => core.getAccount(wagmiConfig).connector,
1361
+ getRecentConnectorId: async () => {
1362
+ const value = await wagmiConfig.storage?.getItem("recentConnectorId");
1363
+ return typeof value === "string" ? value : null;
1364
+ },
1365
+ listConnectors: () => core.getConnectors(wagmiConfig),
1366
+ disconnectFn: (connector) => core.disconnect(wagmiConfig, { connector })
1367
+ });
1368
+ }
1369
+
1157
1370
  // src/otherWalletConnect.ts
1158
1371
  function findWalletConnectConnector(connectors) {
1159
1372
  return connectors.find((connector) => {
1160
- const id = normalize(connector.id);
1161
- const name = normalize(connector.name);
1162
- const type = normalize(connector.type);
1373
+ const id = normalize2(connector.id);
1374
+ const name = normalize2(connector.name);
1375
+ const type = normalize2(connector.type);
1163
1376
  return id === "walletconnect" || id === "wallet-connect" || name === "walletconnect" || name === "wallet connect" || type === "walletconnect";
1164
1377
  });
1165
1378
  }
@@ -1176,9 +1389,9 @@ function findReownExtensionConnector(connectors, wallet) {
1176
1389
  const eligible = connectors.filter((connector) => !findWalletConnectConnector([connector]));
1177
1390
  const match = eligible.find((connector) => {
1178
1391
  const connectorValues = [
1179
- normalize(connector.id),
1180
- normalize(connector.name),
1181
- normalize(connector.type)
1392
+ normalize2(connector.id),
1393
+ normalize2(connector.name),
1394
+ normalize2(connector.type)
1182
1395
  ].filter(Boolean);
1183
1396
  return targets.some(
1184
1397
  (target) => connectorValues.some(
@@ -1207,8 +1420,8 @@ async function findReownExtensionConnectorAsync(connectors, wallet, bridgeClient
1207
1420
  });
1208
1421
  return void 0;
1209
1422
  }
1210
- const targetRdns = normalize(resolved.rdns);
1211
- const match = connectors.find((connector) => normalize(connector.id) === targetRdns);
1423
+ const targetRdns = normalize2(resolved.rdns);
1424
+ const match = connectors.find((connector) => normalize2(connector.id) === targetRdns);
1212
1425
  console.info("[blink-bridge] findReownExtensionConnectorAsync \u2192 match", {
1213
1426
  walletName: wallet.name,
1214
1427
  flag: resolved.flag,
@@ -1226,84 +1439,59 @@ function getReownExtensionTargets(wallet) {
1226
1439
  return [...targets];
1227
1440
  }
1228
1441
  function addTarget(targets, value) {
1229
- const normalized = normalize(value);
1442
+ const normalized = normalize2(value);
1230
1443
  if (normalized) {
1231
1444
  targets.add(normalized);
1232
1445
  }
1233
1446
  }
1234
- function normalize(value) {
1447
+ function normalize2(value) {
1235
1448
  return (value ?? "").trim().toLowerCase().replaceAll(/[^a-z0-9.]/g, "");
1236
1449
  }
1237
1450
 
1238
- // src/walletConnectorResolver.ts
1239
- function normalize2(value) {
1240
- return (value ?? "").trim().toLowerCase();
1241
- }
1242
- function buildTargetMatchers(target) {
1243
- const values = [target.providerName, target.wagmiConnectorId].map(normalize2).filter(Boolean);
1244
- const aliases = new Set(values);
1245
- for (const value of values) {
1246
- if (value.includes("metamask")) {
1247
- aliases.add("metamask");
1248
- aliases.add("io.metamask");
1249
- }
1250
- if (value === "base" || value === "base account" || value === "base app" || value.includes("coinbase")) {
1251
- aliases.add("base");
1252
- aliases.add("coinbase");
1253
- aliases.add("coinbasewalletsdk");
1254
- }
1255
- if (value.includes("trust")) {
1256
- aliases.add("trust");
1257
- aliases.add("trustwallet");
1258
- }
1259
- if (value.includes("okx")) {
1260
- aliases.add("okx");
1261
- aliases.add("okxwallet");
1262
- aliases.add("com.okx.wallet");
1263
- }
1264
- if (value.includes("rabby")) {
1265
- aliases.add("rabby");
1266
- aliases.add("io.rabby");
1267
- }
1268
- if (value.includes("phantom")) {
1269
- aliases.add("phantom");
1270
- aliases.add("app.phantom");
1271
- }
1272
- if (value.includes("injected")) {
1273
- aliases.add("injected");
1274
- }
1275
- }
1276
- return [...aliases];
1277
- }
1278
- function connectorMatchesWallet(connector, target) {
1279
- if (!connector) {
1280
- return false;
1281
- }
1282
- const connectorId = normalize2(connector.id);
1283
- const connectorName = normalize2(connector.name);
1284
- if (target.wagmiConnectorId) {
1285
- const targetConnectorId = normalize2(target.wagmiConnectorId);
1286
- if (connectorId === targetConnectorId) {
1287
- return true;
1288
- }
1289
- }
1290
- const matchers = buildTargetMatchers(target);
1291
- if (matchers.length === 0) {
1292
- return false;
1293
- }
1294
- return matchers.some(
1295
- (matcher) => connectorId === matcher || connectorName === matcher || connectorId.includes(matcher) || connectorName.includes(matcher)
1296
- );
1297
- }
1298
- function resolveWalletConnector(connectors, target) {
1299
- if (target.wagmiConnectorId || target.providerName) {
1300
- return connectors.find((connector) => connectorMatchesWallet(connector, target));
1451
+ // src/resolveScreen.ts
1452
+ function screenForPhase(phase) {
1453
+ switch (phase.step) {
1454
+ case "initializing":
1455
+ case "data-loading":
1456
+ return "loading";
1457
+ case "manual-transfer":
1458
+ return "manual-transfer";
1459
+ case "login":
1460
+ return "login";
1461
+ case "deposit-options":
1462
+ return "deposit-options";
1463
+ case "wallet-picker":
1464
+ return "wallet-picker";
1465
+ case "wallet-setup":
1466
+ if (phase.mobile) {
1467
+ return "open-wallet";
1468
+ }
1469
+ if (phase.desktopWait) {
1470
+ return "open-wallet";
1471
+ }
1472
+ return "setup-deposit";
1473
+ case "select-source":
1474
+ return phase.isDesktop ? "setup" : "select-source";
1475
+ case "one-tap-setup":
1476
+ return "setup";
1477
+ case "token-picker":
1478
+ return "token-picker";
1479
+ case "guest-source-picker":
1480
+ return "guest-source-picker";
1481
+ case "deposit":
1482
+ return "deposit";
1483
+ case "processing":
1484
+ return "processing";
1485
+ case "confirm-sign":
1486
+ return "confirm-sign";
1487
+ case "completed":
1488
+ case "failed":
1489
+ return "success";
1490
+ case "amount-too-low":
1491
+ return "amount-too-low";
1492
+ case "enter-amount":
1493
+ return "enter-amount";
1301
1494
  }
1302
- const metaMaskConnector = connectors.find((connector) => connectorMatchesWallet(
1303
- connector,
1304
- { wagmiConnectorId: "metamask" }
1305
- ));
1306
- return metaMaskConnector ?? connectors[0];
1307
1495
  }
1308
1496
 
1309
1497
  // src/passkey-delegation.ts
@@ -1747,8 +1935,9 @@ async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
1747
1935
  return await res.json();
1748
1936
  }
1749
1937
  async function createAccount(apiBaseUrl, token, params) {
1938
+ const provisionalId = params.id ?? crypto.randomUUID();
1750
1939
  const body = {
1751
- id: params.id ?? crypto.randomUUID(),
1940
+ id: provisionalId,
1752
1941
  name: params.name,
1753
1942
  credentialId: params.credentialId
1754
1943
  };
@@ -1776,7 +1965,8 @@ async function createAccount(apiBaseUrl, token, params) {
1776
1965
  body: JSON.stringify(body)
1777
1966
  });
1778
1967
  if (!res.ok) await throwApiError(res);
1779
- return await res.json();
1968
+ const account = await res.json();
1969
+ return { ...account, id: account.id ?? provisionalId };
1780
1970
  }
1781
1971
  async function createAccountAuthorizationSession(apiBaseUrl, token, accountId, credentialId, options) {
1782
1972
  const body = { credentialId };
@@ -7369,289 +7559,706 @@ function EffectiveDepositAmountProvider({
7369
7559
  return /* @__PURE__ */ jsxRuntime.jsx(EffectiveDepositAmountContext.Provider, { value: memoValue, children });
7370
7560
  }
7371
7561
 
7372
- // src/resolveScreen.ts
7373
- function screenForPhase(phase) {
7374
- switch (phase.step) {
7375
- case "initializing":
7376
- case "data-loading":
7377
- return "loading";
7378
- case "manual-transfer":
7379
- return "manual-transfer";
7380
- case "login":
7381
- return "login";
7382
- case "deposit-options":
7383
- return "deposit-options";
7384
- case "wallet-picker":
7385
- return "wallet-picker";
7386
- case "wallet-setup":
7387
- if (phase.mobile) {
7388
- return "open-wallet";
7389
- }
7390
- if (phase.desktopWait) {
7391
- return "open-wallet";
7392
- }
7393
- return "setup-deposit";
7394
- case "select-source":
7395
- return phase.isDesktop ? "setup" : "select-source";
7396
- case "one-tap-setup":
7397
- return "setup";
7398
- case "token-picker":
7399
- return "token-picker";
7400
- case "guest-source-picker":
7401
- return "guest-source-picker";
7402
- case "deposit":
7403
- return "deposit";
7404
- case "processing":
7405
- return "processing";
7406
- case "confirm-sign":
7407
- return "confirm-sign";
7562
+ // src/manualTransferUtils.ts
7563
+ function screenForSession(session) {
7564
+ if (!session) return "source-selector";
7565
+ switch (session.status) {
7566
+ case "awaiting_deposit":
7567
+ return "awaiting-deposit";
7568
+ case "deposit_received":
7569
+ return "deposit-received";
7570
+ case "routing":
7571
+ return "deposit-routing";
7408
7572
  case "completed":
7573
+ return "deposit-complete";
7574
+ case "refunded":
7409
7575
  case "failed":
7410
- return "success";
7411
- case "amount-too-low":
7412
- return "amount-too-low";
7413
- case "enter-amount":
7414
- return "enter-amount";
7576
+ case "wrong_token":
7577
+ return "deposit-failed";
7415
7578
  }
7416
7579
  }
7417
-
7418
- // src/processingStatus.ts
7419
- var PROCESSING_TIMEOUT_MS = 18e4;
7420
- var TERMINAL_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["COMPLETED", "FAILED", "EXPIRED"]);
7421
- var SIGNABLE_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["CREATED", "AUTHORIZED"]);
7422
- function isTerminalTransferStatus(status) {
7423
- return TERMINAL_TRANSFER_STATUSES.has(status);
7424
- }
7425
- function isTransferSignable(transfer) {
7426
- return transfer != null && SIGNABLE_TRANSFER_STATUSES.has(transfer.status);
7427
- }
7428
- function isTransferAwaitingCompletion(transfer) {
7429
- if (!transfer) return false;
7430
- return !isTerminalTransferStatus(transfer.status);
7431
- }
7432
- function resolvePreferredTransfer(polledTransfer, localTransfer) {
7433
- return polledTransfer ?? localTransfer;
7580
+ function isSameChainSameTokenSelection(option, destination) {
7581
+ return option.chainId === destination.chainId && option.tokenAddress.toLowerCase() === destination.token.address.toLowerCase();
7434
7582
  }
7435
- function getTransferStatus(polledTransfer, localTransfer) {
7436
- const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
7437
- return transfer?.status ?? "UNKNOWN";
7583
+ function feeCopy(_session) {
7584
+ return "No fees";
7438
7585
  }
7439
- function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
7440
- if (!processingStartedAtMs) return false;
7441
- return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
7586
+ function toTransfer(session) {
7587
+ return {
7588
+ id: session.sessionId,
7589
+ status: session.status,
7590
+ amount: {
7591
+ amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd),
7592
+ currency: "USD"
7593
+ },
7594
+ sources: [],
7595
+ destinations: [{
7596
+ id: session.sessionId,
7597
+ chainId: session.destination.chainId,
7598
+ address: session.destination.address,
7599
+ token: { address: session.destination.token.address, symbol: "" },
7600
+ amount: { amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd), currency: "USD" }
7601
+ }],
7602
+ createDate: session.createDate,
7603
+ updateDate: session.updateDate
7604
+ };
7442
7605
  }
7443
- var STATUS_DISPLAY_LABELS = {
7444
- CREATED: "created",
7445
- AUTHORIZED: "authorized",
7446
- SENDING: "sending",
7447
- SENT: "confirming delivery",
7448
- COMPLETED: "completed",
7449
- FAILED: "failed"
7450
- };
7451
- function getStatusDisplayLabel(status) {
7452
- return STATUS_DISPLAY_LABELS[status] ?? status;
7606
+ var SOLANA_CHAIN_ID = 792703809;
7607
+ var SOLANA_NATIVE_SOL_ADDRESS = "11111111111111111111111111111111";
7608
+ function formatDepositUri(address, chainId, depToken) {
7609
+ if (chainId === SOLANA_CHAIN_ID) {
7610
+ if (!depToken || depToken === SOLANA_NATIVE_SOL_ADDRESS) {
7611
+ return `solana:${address}`;
7612
+ }
7613
+ return `solana:${address}?spl-token=${depToken}`;
7614
+ }
7615
+ return address;
7453
7616
  }
7454
- function buildProcessingTimeoutMessage(status) {
7455
- const label = getStatusDisplayLabel(status);
7456
- return `Payment is taking longer than expected (status: ${label}). Please try again.`;
7617
+ var dataUrlCache = /* @__PURE__ */ new Map();
7618
+ var inFlight = /* @__PURE__ */ new Map();
7619
+ function keyFor(uri, colors) {
7620
+ return `${uri}|${colors.dark}|${colors.light}`;
7621
+ }
7622
+ function getCachedQrDataUrl(uri, colors) {
7623
+ return dataUrlCache.get(keyFor(uri, colors)) ?? null;
7624
+ }
7625
+ function getOrRenderQrDataUrl(uri, colors) {
7626
+ const key = keyFor(uri, colors);
7627
+ const cached = dataUrlCache.get(key);
7628
+ if (cached) return Promise.resolve(cached);
7629
+ const pending = inFlight.get(key);
7630
+ if (pending) return pending;
7631
+ const promise = QRCode__namespace.toDataURL(uri, {
7632
+ errorCorrectionLevel: "H",
7633
+ margin: 1,
7634
+ width: 203,
7635
+ color: { dark: colors.dark, light: colors.light }
7636
+ }).then((url) => {
7637
+ dataUrlCache.set(key, url);
7638
+ inFlight.delete(key);
7639
+ return url;
7640
+ }).catch((err) => {
7641
+ inFlight.delete(key);
7642
+ throw err;
7643
+ });
7644
+ inFlight.set(key, promise);
7645
+ return promise;
7457
7646
  }
7458
7647
 
7459
- // src/paymentResolvePhase.ts
7460
- function hasActiveWallet(accounts) {
7461
- return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
7462
- }
7463
- function resolveTerminalPhase(state) {
7464
- if (state.amountTooLow != null) {
7465
- return {
7466
- step: "amount-too-low",
7467
- minAmountUsd: state.amountTooLow.minAmountUsd
7468
- };
7469
- }
7470
- const transferCompleted = state.transfer?.status === "COMPLETED";
7471
- const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
7472
- if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
7473
- return { step: "completed", transfer: state.transfer };
7474
- }
7475
- if (state.transfer?.status === "FAILED") {
7476
- return {
7477
- step: "failed",
7478
- transfer: state.transfer,
7479
- error: state.error ?? "Transfer failed."
7480
- };
7481
- }
7482
- if (state.creatingTransfer || isTransferAwaitingCompletion(state.transfer)) {
7483
- return { step: "processing", transfer: state.transfer };
7484
- }
7485
- return null;
7648
+ // src/hooks/useManualTransferSession.ts
7649
+ function perTokenKey(chainId, tokenAddress) {
7650
+ return `${chainId}:${tokenAddress.toLowerCase()}`;
7486
7651
  }
7487
- function resolveStickyPhase(state) {
7488
- const currentPhase = state.phase;
7489
- if (currentPhase.step === "manual-transfer" && !state.loginRequested) {
7490
- return currentPhase;
7491
- }
7492
- if (currentPhase.step === "deposit-options" && !state.loginRequested && !state.privyAuthenticated) {
7493
- return currentPhase;
7494
- }
7495
- if (!state.loginRequested && state.setupFlowScreen === "one-tap-setup") {
7496
- return { step: "one-tap-setup", action: null };
7497
- }
7498
- if (!state.loginRequested && state.setupFlowScreen === "deposit-confirm" && state.selectedAccountId != null) {
7499
- return {
7500
- step: "wallet-setup",
7501
- mobile: null,
7502
- accountId: state.selectedAccountId
7503
- };
7504
- }
7505
- if (!state.loginRequested && state.mobileTokenAuthorizationPending) {
7506
- return {
7507
- step: "wallet-setup",
7508
- mobile: { deeplinkUri: "", providerId: state.selectedProviderId },
7509
- accountId: null
7510
- };
7511
- }
7512
- const isFundingSourceSubflow = !state.loginRequested && (currentPhase.step === "token-picker" || currentPhase.step === "guest-source-picker" || currentPhase.step === "select-source" || currentPhase.step === "confirm-sign");
7513
- if (isFundingSourceSubflow) {
7514
- return currentPhase;
7515
- }
7516
- if ((state.standardDesktopInlineOpenWallet || state.desktopWait != null) && state.privyAuthenticated && state.activeCredentialId != null && state.selectedAccountId != null && !state.loginRequested) {
7517
- return {
7518
- step: "wallet-setup",
7519
- mobile: null,
7520
- desktopWait: state.desktopWait,
7521
- accountId: state.selectedAccountId
7652
+ function useManualTransferSession({
7653
+ destination,
7654
+ merchantAuthorization,
7655
+ idempotencyKey,
7656
+ onComplete,
7657
+ onError,
7658
+ pollEnabled = true
7659
+ }) {
7660
+ const { apiBaseUrl, tokens } = useBlinkConfig();
7661
+ const [sourceOptions, setSourceOptions] = react.useState(null);
7662
+ const [loadingSources, setLoadingSources] = react.useState(true);
7663
+ const [selectedToken, setSelectedToken] = react.useState("");
7664
+ const [selectedChainId, setSelectedChainId] = react.useState("");
7665
+ const [sessionsByFamily, setSessionsByFamily] = react.useState({});
7666
+ const [perTokenSessions, setPerTokenSessions] = react.useState({});
7667
+ const [loading, setLoading] = react.useState(false);
7668
+ const [error, setError] = react.useState(null);
7669
+ const [copiedAddress, setCopiedAddress] = react.useState(null);
7670
+ const completedRef = react.useRef(/* @__PURE__ */ new Set());
7671
+ const premintedFamiliesRef = react.useRef(/* @__PURE__ */ new Set());
7672
+ const inFlightPerTokenRef = react.useRef(/* @__PURE__ */ new Set());
7673
+ react.useEffect(() => {
7674
+ if (!merchantAuthorization) return;
7675
+ let cancelled = false;
7676
+ setLoadingSources(true);
7677
+ fetchManualTransferSources(apiBaseUrl, { merchantAuthorization, destination }).then((sources) => {
7678
+ if (cancelled) return;
7679
+ setSourceOptions(sources);
7680
+ setLoadingSources(false);
7681
+ const destTokenAddrLower = destination.token.address.toLowerCase();
7682
+ const destMatch = sources.find(
7683
+ (s) => s.chainId === destination.chainId && s.tokenAddress.toLowerCase() === destTokenAddrLower
7684
+ );
7685
+ const svmCanonical = sources.find((s) => s.canonical && s.chainFamily === "svm");
7686
+ const evmCanonical = sources.find((s) => s.canonical && s.chainFamily === "evm");
7687
+ const defaultOption = destMatch ?? svmCanonical ?? evmCanonical ?? sources[0];
7688
+ if (defaultOption) {
7689
+ setSelectedToken(defaultOption.tokenSymbol);
7690
+ setSelectedChainId(String(defaultOption.chainId));
7691
+ }
7692
+ }).catch((err) => {
7693
+ if (!cancelled) {
7694
+ setError(err instanceof Error ? err.message : String(err));
7695
+ setLoadingSources(false);
7696
+ }
7697
+ });
7698
+ return () => {
7699
+ cancelled = true;
7522
7700
  };
7523
- }
7524
- if (state.mobileFlow && state.deeplinkUri != null) {
7525
- return {
7526
- step: "wallet-setup",
7527
- mobile: { deeplinkUri: state.deeplinkUri, providerId: state.selectedProviderId },
7528
- accountId: null
7701
+ }, [apiBaseUrl, destination, merchantAuthorization]);
7702
+ react.useEffect(() => {
7703
+ if (!sourceOptions || !merchantAuthorization) return;
7704
+ const canonicals = sourceOptions.filter((s) => s.canonical);
7705
+ if (canonicals.length === 0) return;
7706
+ let cancelled = false;
7707
+ for (const opt of canonicals) {
7708
+ if (premintedFamiliesRef.current.has(opt.chainFamily)) continue;
7709
+ premintedFamiliesRef.current.add(opt.chainFamily);
7710
+ const family = opt.chainFamily;
7711
+ const run = async () => {
7712
+ try {
7713
+ const created = await createManualTransfer(apiBaseUrl, {
7714
+ merchantAuthorization,
7715
+ destination,
7716
+ idempotencyKey,
7717
+ source: { chainId: opt.chainId, tokenAddress: opt.tokenAddress }
7718
+ });
7719
+ if (cancelled) return;
7720
+ setSessionsByFamily((prev) => ({ ...prev, [family]: created }));
7721
+ const uri = formatDepositUri(created.depositAddress, opt.chainId, opt.tokenAddress);
7722
+ void getOrRenderQrDataUrl(uri, { dark: tokens.text, light: tokens.bg });
7723
+ } catch (err) {
7724
+ if (cancelled) return;
7725
+ premintedFamiliesRef.current.delete(family);
7726
+ setError(err instanceof Error ? err.message : String(err));
7727
+ }
7728
+ };
7729
+ void run();
7730
+ }
7731
+ return () => {
7732
+ cancelled = true;
7529
7733
  };
7530
- }
7531
- if (currentPhase.step === "wallet-picker" && currentPhase.reason === "switch" && !state.creatingTransfer && !(state.mobileFlow && state.deeplinkUri)) {
7532
- return currentPhase;
7533
- }
7534
- return null;
7535
- }
7536
- function deriveFreshPhase(state) {
7537
- if (!state.privyReady) {
7538
- return { step: "initializing" };
7539
- }
7540
- if (state.privyAuthenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
7541
- return { step: "initializing" };
7542
- }
7543
- if (state.loginRequested) {
7544
- return { step: "login" };
7545
- }
7546
- if (state.enableFullWidget && !state.privyAuthenticated) {
7547
- return { step: "deposit-options" };
7548
- }
7549
- if (!state.privyAuthenticated) {
7550
- return { step: "login" };
7551
- }
7552
- if (state.loadingData && state.activeCredentialId != null) {
7553
- return { step: "data-loading" };
7554
- }
7555
- if (state.requireAmountEntry) {
7556
- return { step: "enter-amount" };
7557
- }
7558
- if (state.activeCredentialId != null && !hasActiveWallet(state.accounts) && !state.mobileFlow) {
7559
- return { step: "wallet-picker", reason: "link" };
7560
- }
7561
- if (state.activeCredentialId != null && hasActiveWallet(state.accounts) && !state.loadingData) {
7562
- return { step: "deposit" };
7563
- }
7564
- return { step: "wallet-picker", reason: "entry" };
7565
- }
7566
- function resolvePhase(state) {
7567
- return resolveTerminalPhase(state) ?? resolveStickyPhase(state) ?? deriveFreshPhase(state);
7568
- }
7569
-
7570
- // src/paymentReducer.ts
7571
- var DEFAULT_ONE_TAP_LIMIT = 1e4;
7572
- function deriveSourceTypeAndId(state) {
7573
- if (state.selectedWalletId) {
7574
- return { sourceType: "walletId", sourceId: state.selectedWalletId };
7575
- }
7576
- if (state.selectedAccountId) {
7577
- return { sourceType: "accountId", sourceId: state.selectedAccountId };
7578
- }
7579
- return { sourceType: "accountId", sourceId: "" };
7580
- }
7581
- function clearStaleSelection(state) {
7582
- if (state.selectedAccountId == null) return state;
7583
- const stillExists = state.accounts.some((a) => a.id === state.selectedAccountId);
7584
- if (stillExists) return state;
7734
+ }, [
7735
+ apiBaseUrl,
7736
+ destination,
7737
+ idempotencyKey,
7738
+ merchantAuthorization,
7739
+ sourceOptions,
7740
+ tokens.text,
7741
+ tokens.bg
7742
+ ]);
7743
+ const tokenChoices = react.useMemo(
7744
+ () => Array.from(new Set((sourceOptions ?? []).map((opt) => opt.tokenSymbol))),
7745
+ [sourceOptions]
7746
+ );
7747
+ const chainChoices = react.useMemo(() => {
7748
+ const seen = /* @__PURE__ */ new Set();
7749
+ return (sourceOptions ?? []).filter((opt) => {
7750
+ if (seen.has(opt.chainId)) return false;
7751
+ seen.add(opt.chainId);
7752
+ return true;
7753
+ });
7754
+ }, [sourceOptions]);
7755
+ const tokenLogoUriBySymbol = react.useMemo(() => {
7756
+ const out = {};
7757
+ for (const opt of sourceOptions ?? []) {
7758
+ if (out[opt.tokenSymbol] == null) {
7759
+ out[opt.tokenSymbol] = opt.tokenLogoUri;
7760
+ }
7761
+ }
7762
+ return out;
7763
+ }, [sourceOptions]);
7764
+ const selectedOption = react.useMemo(
7765
+ () => (sourceOptions ?? []).find(
7766
+ (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === selectedChainId
7767
+ ) ?? null,
7768
+ [sourceOptions, selectedToken, selectedChainId]
7769
+ );
7770
+ const tokensForSelectedChain = react.useMemo(() => {
7771
+ if (!selectedChainId) return null;
7772
+ return new Set(
7773
+ (sourceOptions ?? []).filter((opt) => String(opt.chainId) === selectedChainId).map((opt) => opt.tokenSymbol)
7774
+ );
7775
+ }, [sourceOptions, selectedChainId]);
7776
+ const chainsForSelectedToken = react.useMemo(() => {
7777
+ if (!selectedToken) return null;
7778
+ return new Set(
7779
+ (sourceOptions ?? []).filter((opt) => opt.tokenSymbol === selectedToken).map((opt) => opt.chainId)
7780
+ );
7781
+ }, [sourceOptions, selectedToken]);
7782
+ const session = react.useMemo(() => {
7783
+ if (!selectedOption) return null;
7784
+ const perToken = perTokenSessions[perTokenKey(selectedOption.chainId, selectedOption.tokenAddress)] ?? null;
7785
+ if (isSameChainSameTokenSelection(selectedOption, destination)) {
7786
+ return perToken ?? sessionsByFamily[selectedOption.chainFamily] ?? null;
7787
+ }
7788
+ const familySession = sessionsByFamily[selectedOption.chainFamily];
7789
+ if (familySession) return familySession;
7790
+ return perToken;
7791
+ }, [selectedOption, destination, sessionsByFamily, perTokenSessions]);
7792
+ const depositAddress = session?.depositAddress;
7793
+ const qrReady = !!depositAddress;
7794
+ const lastFeeCopyRef = react.useRef(null);
7795
+ const nextFeeCopy = session ? feeCopy() : null;
7796
+ const sessionFeeCopy = nextFeeCopy === lastFeeCopyRef.current ? lastFeeCopyRef.current : lastFeeCopyRef.current = nextFeeCopy;
7797
+ const activeSessionId = session?.sessionId;
7798
+ const activeSessionStatus = session?.status;
7799
+ react.useEffect(() => {
7800
+ if (!pollEnabled) return;
7801
+ if (!activeSessionId) return;
7802
+ if (activeSessionStatus && ["completed", "failed", "refunded", "wrong_token"].includes(activeSessionStatus)) return;
7803
+ const timer = window.setInterval(() => {
7804
+ fetchManualTransferSession(apiBaseUrl, activeSessionId).then((updated) => {
7805
+ setSessionsByFamily((prev) => {
7806
+ for (const family of Object.keys(prev)) {
7807
+ if (prev[family]?.sessionId === updated.sessionId) {
7808
+ return { ...prev, [family]: updated };
7809
+ }
7810
+ }
7811
+ return prev;
7812
+ });
7813
+ setPerTokenSessions((prev) => {
7814
+ for (const key of Object.keys(prev)) {
7815
+ if (prev[key]?.sessionId === updated.sessionId) {
7816
+ return { ...prev, [key]: updated };
7817
+ }
7818
+ }
7819
+ return prev;
7820
+ });
7821
+ }).catch((err) => setError(err instanceof Error ? err.message : String(err)));
7822
+ }, 2e3);
7823
+ return () => window.clearInterval(timer);
7824
+ }, [apiBaseUrl, activeSessionId, activeSessionStatus, pollEnabled]);
7825
+ const completionSignature = react.useMemo(() => {
7826
+ const entries2 = [];
7827
+ for (const family of Object.keys(sessionsByFamily)) {
7828
+ const s = sessionsByFamily[family];
7829
+ if (s) entries2.push([s.sessionId, s.status]);
7830
+ }
7831
+ for (const key of Object.keys(perTokenSessions)) {
7832
+ const s = perTokenSessions[key];
7833
+ entries2.push([s.sessionId, s.status]);
7834
+ }
7835
+ entries2.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
7836
+ return JSON.stringify(entries2);
7837
+ }, [sessionsByFamily, perTokenSessions]);
7838
+ react.useEffect(() => {
7839
+ const all = [];
7840
+ for (const family of Object.keys(sessionsByFamily)) {
7841
+ const s = sessionsByFamily[family];
7842
+ if (s) all.push(s);
7843
+ }
7844
+ for (const key of Object.keys(perTokenSessions)) {
7845
+ all.push(perTokenSessions[key]);
7846
+ }
7847
+ for (const s of all) {
7848
+ if (s.status !== "completed") continue;
7849
+ if (completedRef.current.has(s.sessionId)) continue;
7850
+ completedRef.current.add(s.sessionId);
7851
+ onComplete?.(toTransfer(s));
7852
+ break;
7853
+ }
7854
+ }, [completionSignature, onComplete]);
7855
+ react.useEffect(() => {
7856
+ if (!error) return;
7857
+ onError?.(error);
7858
+ }, [error, onError]);
7859
+ const createSession = react.useCallback(async (option) => {
7860
+ if (!merchantAuthorization) return;
7861
+ setLoading(true);
7862
+ setError(null);
7863
+ try {
7864
+ const created = await createManualTransfer(apiBaseUrl, {
7865
+ merchantAuthorization,
7866
+ destination,
7867
+ idempotencyKey,
7868
+ source: { chainId: option.chainId, tokenAddress: option.tokenAddress }
7869
+ });
7870
+ const key = perTokenKey(option.chainId, option.tokenAddress);
7871
+ setPerTokenSessions((prev) => ({ ...prev, [key]: created }));
7872
+ const uri = formatDepositUri(created.depositAddress, option.chainId, option.tokenAddress);
7873
+ void getOrRenderQrDataUrl(uri, { dark: tokens.text, light: tokens.bg });
7874
+ } catch (err) {
7875
+ setError(err instanceof Error ? err.message : String(err));
7876
+ } finally {
7877
+ setLoading(false);
7878
+ }
7879
+ }, [apiBaseUrl, destination, idempotencyKey, merchantAuthorization, tokens.text, tokens.bg]);
7880
+ react.useEffect(() => {
7881
+ if (!selectedOption) return;
7882
+ const sameTokenSelection = isSameChainSameTokenSelection(selectedOption, destination);
7883
+ if (!sameTokenSelection) {
7884
+ if (sessionsByFamily[selectedOption.chainFamily]) return;
7885
+ if (premintedFamiliesRef.current.has(selectedOption.chainFamily)) return;
7886
+ }
7887
+ const key = perTokenKey(selectedOption.chainId, selectedOption.tokenAddress);
7888
+ if (perTokenSessions[key]) return;
7889
+ if (inFlightPerTokenRef.current.has(key)) return;
7890
+ inFlightPerTokenRef.current.add(key);
7891
+ void createSession(selectedOption).finally(() => {
7892
+ inFlightPerTokenRef.current.delete(key);
7893
+ });
7894
+ }, [selectedOption, destination, sessionsByFamily, perTokenSessions, createSession]);
7895
+ const resetSessionForNewSelection = react.useCallback(() => {
7896
+ setError(null);
7897
+ }, []);
7898
+ const selectToken = react.useCallback((value) => {
7899
+ setSelectedToken(value);
7900
+ const pairValid = (sourceOptions ?? []).some(
7901
+ (opt) => opt.tokenSymbol === value && String(opt.chainId) === selectedChainId
7902
+ );
7903
+ if (!pairValid) {
7904
+ const firstChain = (sourceOptions ?? []).find(
7905
+ (opt) => opt.tokenSymbol === value
7906
+ );
7907
+ setSelectedChainId(
7908
+ firstChain ? String(firstChain.chainId) : ""
7909
+ );
7910
+ }
7911
+ }, [selectedChainId, sourceOptions]);
7912
+ const selectChainId = react.useCallback((value) => {
7913
+ setSelectedChainId(value);
7914
+ const pairValid = (sourceOptions ?? []).some(
7915
+ (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === value
7916
+ );
7917
+ if (!pairValid) {
7918
+ const firstToken = (sourceOptions ?? []).find(
7919
+ (opt) => String(opt.chainId) === value
7920
+ );
7921
+ setSelectedToken(
7922
+ firstToken ? firstToken.tokenSymbol : ""
7923
+ );
7924
+ }
7925
+ }, [selectedToken, sourceOptions]);
7926
+ const screen = screenForSession(session);
7927
+ const copyDepositAddress = react.useCallback(async (address) => {
7928
+ try {
7929
+ await navigator.clipboard.writeText(address);
7930
+ } catch {
7931
+ const textarea = document.createElement("textarea");
7932
+ textarea.value = address;
7933
+ textarea.setAttribute("readonly", "");
7934
+ textarea.style.position = "absolute";
7935
+ textarea.style.opacity = "0";
7936
+ document.body.appendChild(textarea);
7937
+ textarea.select();
7938
+ try {
7939
+ document.execCommand("copy");
7940
+ } finally {
7941
+ document.body.removeChild(textarea);
7942
+ }
7943
+ }
7944
+ setCopiedAddress(address);
7945
+ window.setTimeout(() => setCopiedAddress((cur) => cur === address ? null : cur), 1500);
7946
+ }, []);
7947
+ const backToSourceSelector = react.useCallback(() => {
7948
+ setCopiedAddress(null);
7949
+ setError(null);
7950
+ if (!selectedOption) return;
7951
+ const family = selectedOption.chainFamily;
7952
+ setSessionsByFamily((prev) => {
7953
+ if (!prev[family]) return prev;
7954
+ const next = { ...prev };
7955
+ delete next[family];
7956
+ return next;
7957
+ });
7958
+ premintedFamiliesRef.current.delete(family);
7959
+ const ptKey = perTokenKey(selectedOption.chainId, selectedOption.tokenAddress);
7960
+ setPerTokenSessions((prev) => {
7961
+ if (!prev[ptKey]) return prev;
7962
+ const next = { ...prev };
7963
+ delete next[ptKey];
7964
+ return next;
7965
+ });
7966
+ }, [selectedOption]);
7585
7967
  return {
7586
- ...state,
7587
- selectedAccountId: null,
7588
- selectedWalletId: null,
7589
- selectedTokenSymbol: null
7968
+ sourceOptions,
7969
+ loadingSources,
7970
+ selectedToken,
7971
+ selectedChainId,
7972
+ session,
7973
+ loading,
7974
+ error,
7975
+ qrReady,
7976
+ copiedAddress,
7977
+ tokenChoices,
7978
+ chainChoices,
7979
+ tokenLogoUriBySymbol,
7980
+ selectedOption,
7981
+ tokensForSelectedChain,
7982
+ chainsForSelectedToken,
7983
+ screen,
7984
+ sessionFeeCopy,
7985
+ depositAddress,
7986
+ createSession,
7987
+ copyDepositAddress,
7988
+ backToSourceSelector,
7989
+ resetSessionForNewSelection,
7990
+ selectToken,
7991
+ selectChainId
7590
7992
  };
7591
7993
  }
7592
- function createInitialState(config) {
7593
- return {
7594
- phase: config.initialPhase ?? { step: "initializing" },
7595
- error: null,
7596
- setupFlowScreen: null,
7597
- providers: [],
7598
- accounts: [],
7599
- chains: [],
7600
- loadingData: false,
7601
- depositSelectionRefreshing: false,
7602
- selectedProviderId: null,
7603
- selectedAccountId: null,
7604
- selectedWalletId: null,
7605
- selectedTokenSymbol: null,
7606
- savedSelection: null,
7607
- amount: config.depositAmount != null ? config.depositAmount.toString() : "",
7608
- transfer: null,
7609
- pendingTransferId: null,
7610
- creatingTransfer: false,
7611
- passkeyConfigLoaded: false,
7612
- activeCredentialId: config.activeCredentialId,
7613
- knownCredentialIds: [],
7614
- oneTapLimit: DEFAULT_ONE_TAP_LIMIT,
7615
- oneTapLimitSavedDuringSetup: false,
7616
- mobileFlow: false,
7617
- deeplinkUri: null,
7618
- increasingLimit: false,
7619
- activePublicKey: null,
7620
- loginRequested: false,
7621
- standardDesktopInlineOpenWallet: false,
7622
- desktopWait: null,
7623
- setupAuthorizationSessionId: null,
7624
- mobileTokenAuthorizationPending: false,
7625
- privyReady: false,
7626
- privyAuthenticated: false,
7627
- lastResumedAt: 0,
7628
- setupDepositAmount: null,
7629
- setupDepositToken: null,
7630
- setupDepositConfirmed: false,
7631
- guestWalletPrepared: null,
7632
- guestWalletDeeplinksPreparing: false,
7633
- amountTooLow: null,
7634
- enableFullWidget: config.enableFullWidget ?? false,
7635
- requireAmountEntry: config.depositAmount == null
7636
- };
7994
+ var ManualTransferSessionContext = react.createContext(null);
7995
+ function ManualTransferSessionProvider({
7996
+ destination,
7997
+ merchantAuthorization,
7998
+ idempotencyKey,
7999
+ onComplete,
8000
+ onError,
8001
+ pollEnabled,
8002
+ children
8003
+ }) {
8004
+ const session = useManualTransferSession({
8005
+ destination,
8006
+ merchantAuthorization,
8007
+ idempotencyKey,
8008
+ onComplete,
8009
+ onError,
8010
+ pollEnabled
8011
+ });
8012
+ return /* @__PURE__ */ jsxRuntime.jsx(ManualTransferSessionContext.Provider, { value: session, children });
7637
8013
  }
7638
- function clearAuthenticatedSessionState(state) {
7639
- return {
7640
- ...state,
7641
- error: null,
7642
- setupFlowScreen: null,
7643
- providers: [],
7644
- accounts: [],
7645
- chains: [],
7646
- loadingData: false,
7647
- depositSelectionRefreshing: false,
7648
- selectedProviderId: null,
7649
- selectedAccountId: null,
7650
- selectedWalletId: null,
7651
- selectedTokenSymbol: null,
7652
- savedSelection: null,
7653
- transfer: null,
7654
- pendingTransferId: null,
8014
+ function useManualTransferSessionContext() {
8015
+ const ctx = react.useContext(ManualTransferSessionContext);
8016
+ if (!ctx) {
8017
+ throw new Error(
8018
+ "useManualTransferSessionContext must be used within a <ManualTransferSessionProvider>"
8019
+ );
8020
+ }
8021
+ return ctx;
8022
+ }
8023
+
8024
+ // src/processingStatus.ts
8025
+ var PROCESSING_TIMEOUT_MS = 18e4;
8026
+ var TERMINAL_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["COMPLETED", "FAILED", "EXPIRED"]);
8027
+ var SIGNABLE_TRANSFER_STATUSES = /* @__PURE__ */ new Set(["CREATED", "AUTHORIZED"]);
8028
+ function isTerminalTransferStatus(status) {
8029
+ return TERMINAL_TRANSFER_STATUSES.has(status);
8030
+ }
8031
+ function isTransferSignable(transfer) {
8032
+ return transfer != null && SIGNABLE_TRANSFER_STATUSES.has(transfer.status);
8033
+ }
8034
+ function isTransferAwaitingCompletion(transfer) {
8035
+ if (!transfer) return false;
8036
+ return !isTerminalTransferStatus(transfer.status);
8037
+ }
8038
+ function resolvePreferredTransfer(polledTransfer, localTransfer) {
8039
+ return polledTransfer ?? localTransfer;
8040
+ }
8041
+ function getTransferStatus(polledTransfer, localTransfer) {
8042
+ const transfer = resolvePreferredTransfer(polledTransfer, localTransfer);
8043
+ return transfer?.status ?? "UNKNOWN";
8044
+ }
8045
+ function hasProcessingTimedOut(processingStartedAtMs, nowMs) {
8046
+ if (!processingStartedAtMs) return false;
8047
+ return nowMs - processingStartedAtMs >= PROCESSING_TIMEOUT_MS;
8048
+ }
8049
+ var STATUS_DISPLAY_LABELS = {
8050
+ CREATED: "created",
8051
+ AUTHORIZED: "authorized",
8052
+ SENDING: "sending",
8053
+ SENT: "confirming delivery",
8054
+ COMPLETED: "completed",
8055
+ FAILED: "failed"
8056
+ };
8057
+ function getStatusDisplayLabel(status) {
8058
+ return STATUS_DISPLAY_LABELS[status] ?? status;
8059
+ }
8060
+ function buildProcessingTimeoutMessage(status) {
8061
+ const label = getStatusDisplayLabel(status);
8062
+ return `Payment is taking longer than expected (status: ${label}). Please try again.`;
8063
+ }
8064
+
8065
+ // src/paymentResolvePhase.ts
8066
+ function hasActiveWallet(accounts) {
8067
+ return accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE"));
8068
+ }
8069
+ function resolveTerminalPhase(state) {
8070
+ if (state.amountTooLow != null) {
8071
+ return {
8072
+ step: "amount-too-low",
8073
+ minAmountUsd: state.amountTooLow.minAmountUsd
8074
+ };
8075
+ }
8076
+ const transferCompleted = state.transfer?.status === "COMPLETED";
8077
+ const needsPasskeyBootstrap = state.privyAuthenticated && !state.activeCredentialId;
8078
+ if (transferCompleted && !needsPasskeyBootstrap && !state.loginRequested) {
8079
+ return { step: "completed", transfer: state.transfer };
8080
+ }
8081
+ if (state.transfer?.status === "FAILED") {
8082
+ return {
8083
+ step: "failed",
8084
+ transfer: state.transfer,
8085
+ error: state.error ?? "Transfer failed."
8086
+ };
8087
+ }
8088
+ if (state.creatingTransfer || isTransferAwaitingCompletion(state.transfer)) {
8089
+ return { step: "processing", transfer: state.transfer };
8090
+ }
8091
+ return null;
8092
+ }
8093
+ function resolveStickyPhase(state) {
8094
+ const currentPhase = state.phase;
8095
+ if (currentPhase.step === "manual-transfer" && !state.loginRequested) {
8096
+ return currentPhase;
8097
+ }
8098
+ if (currentPhase.step === "deposit-options" && !state.loginRequested && !state.privyAuthenticated) {
8099
+ return currentPhase;
8100
+ }
8101
+ if (!state.loginRequested && state.setupFlowScreen === "one-tap-setup") {
8102
+ return { step: "one-tap-setup", action: null };
8103
+ }
8104
+ if (!state.loginRequested && state.setupFlowScreen === "deposit-confirm" && state.selectedAccountId != null) {
8105
+ return {
8106
+ step: "wallet-setup",
8107
+ mobile: null,
8108
+ accountId: state.selectedAccountId
8109
+ };
8110
+ }
8111
+ if (!state.loginRequested && state.mobileTokenAuthorizationPending) {
8112
+ return {
8113
+ step: "wallet-setup",
8114
+ mobile: { deeplinkUri: "", providerId: state.selectedProviderId },
8115
+ accountId: null
8116
+ };
8117
+ }
8118
+ const isFundingSourceSubflow = !state.loginRequested && (currentPhase.step === "token-picker" || currentPhase.step === "guest-source-picker" || currentPhase.step === "select-source" || currentPhase.step === "confirm-sign");
8119
+ if (isFundingSourceSubflow) {
8120
+ return currentPhase;
8121
+ }
8122
+ if ((state.standardDesktopInlineOpenWallet || state.desktopWait != null) && state.privyAuthenticated && state.activeCredentialId != null && state.selectedAccountId != null && !state.loginRequested) {
8123
+ return {
8124
+ step: "wallet-setup",
8125
+ mobile: null,
8126
+ desktopWait: state.desktopWait,
8127
+ accountId: state.selectedAccountId
8128
+ };
8129
+ }
8130
+ if (state.mobileFlow && state.deeplinkUri != null) {
8131
+ return {
8132
+ step: "wallet-setup",
8133
+ mobile: { deeplinkUri: state.deeplinkUri, providerId: state.selectedProviderId },
8134
+ accountId: null
8135
+ };
8136
+ }
8137
+ if (currentPhase.step === "wallet-picker" && currentPhase.reason === "switch" && !state.creatingTransfer && !(state.mobileFlow && state.deeplinkUri)) {
8138
+ return currentPhase;
8139
+ }
8140
+ return null;
8141
+ }
8142
+ function deriveFreshPhase(state) {
8143
+ if (!state.privyReady) {
8144
+ return { step: "initializing" };
8145
+ }
8146
+ if (state.privyAuthenticated && !state.activeCredentialId && !state.passkeyConfigLoaded) {
8147
+ return { step: "initializing" };
8148
+ }
8149
+ if (state.loginRequested) {
8150
+ return { step: "login" };
8151
+ }
8152
+ if (state.enableFullWidget && !state.privyAuthenticated) {
8153
+ return { step: "deposit-options" };
8154
+ }
8155
+ if (!state.privyAuthenticated) {
8156
+ return { step: "login" };
8157
+ }
8158
+ if (state.loadingData && state.activeCredentialId != null) {
8159
+ return { step: "data-loading" };
8160
+ }
8161
+ if (state.requireAmountEntry) {
8162
+ return { step: "enter-amount" };
8163
+ }
8164
+ if (state.activeCredentialId != null && !hasActiveWallet(state.accounts) && !state.mobileFlow) {
8165
+ return { step: "wallet-picker", reason: "link" };
8166
+ }
8167
+ if (state.activeCredentialId != null && hasActiveWallet(state.accounts) && !state.loadingData) {
8168
+ return { step: "deposit" };
8169
+ }
8170
+ return { step: "wallet-picker", reason: "entry" };
8171
+ }
8172
+ function resolvePhase(state) {
8173
+ return resolveTerminalPhase(state) ?? resolveStickyPhase(state) ?? deriveFreshPhase(state);
8174
+ }
8175
+
8176
+ // src/paymentReducer.ts
8177
+ var DEFAULT_ONE_TAP_LIMIT = 1e4;
8178
+ function deriveSourceTypeAndId(state) {
8179
+ if (state.selectedWalletId) {
8180
+ return { sourceType: "walletId", sourceId: state.selectedWalletId };
8181
+ }
8182
+ if (state.selectedAccountId) {
8183
+ return { sourceType: "accountId", sourceId: state.selectedAccountId };
8184
+ }
8185
+ return { sourceType: "accountId", sourceId: "" };
8186
+ }
8187
+ function clearStaleSelection(state) {
8188
+ if (state.selectedAccountId == null) return state;
8189
+ if (state.desktopWait != null) return state;
8190
+ const stillExists = state.accounts.some((a) => a.id === state.selectedAccountId);
8191
+ if (stillExists) return state;
8192
+ return {
8193
+ ...state,
8194
+ selectedAccountId: null,
8195
+ selectedWalletId: null,
8196
+ selectedTokenSymbol: null
8197
+ };
8198
+ }
8199
+ function createInitialState(config) {
8200
+ return {
8201
+ phase: config.initialPhase ?? { step: "initializing" },
8202
+ error: null,
8203
+ setupFlowScreen: null,
8204
+ providers: [],
8205
+ accounts: [],
8206
+ chains: [],
8207
+ loadingData: false,
8208
+ depositSelectionRefreshing: false,
8209
+ selectedProviderId: null,
8210
+ selectedAccountId: null,
8211
+ selectedWalletId: null,
8212
+ selectedTokenSymbol: null,
8213
+ savedSelection: null,
8214
+ amount: config.depositAmount != null ? config.depositAmount.toString() : "",
8215
+ transfer: null,
8216
+ pendingTransferId: null,
8217
+ creatingTransfer: false,
8218
+ passkeyConfigLoaded: false,
8219
+ activeCredentialId: config.activeCredentialId,
8220
+ knownCredentialIds: [],
8221
+ oneTapLimit: DEFAULT_ONE_TAP_LIMIT,
8222
+ oneTapLimitSavedDuringSetup: false,
8223
+ mobileFlow: false,
8224
+ deeplinkUri: null,
8225
+ increasingLimit: false,
8226
+ activePublicKey: null,
8227
+ loginRequested: false,
8228
+ standardDesktopInlineOpenWallet: false,
8229
+ desktopWait: null,
8230
+ setupAuthorizationSessionId: null,
8231
+ mobileTokenAuthorizationPending: false,
8232
+ privyReady: false,
8233
+ privyAuthenticated: false,
8234
+ lastResumedAt: 0,
8235
+ setupDepositAmount: null,
8236
+ setupDepositToken: null,
8237
+ setupDepositConfirmed: false,
8238
+ guestWalletPrepared: null,
8239
+ guestWalletDeeplinksPreparing: false,
8240
+ amountTooLow: null,
8241
+ enableFullWidget: config.enableFullWidget ?? false,
8242
+ requireAmountEntry: config.depositAmount == null
8243
+ };
8244
+ }
8245
+ function clearAuthenticatedSessionState(state) {
8246
+ return {
8247
+ ...state,
8248
+ error: null,
8249
+ setupFlowScreen: null,
8250
+ providers: [],
8251
+ accounts: [],
8252
+ chains: [],
8253
+ loadingData: false,
8254
+ depositSelectionRefreshing: false,
8255
+ selectedProviderId: null,
8256
+ selectedAccountId: null,
8257
+ selectedWalletId: null,
8258
+ selectedTokenSymbol: null,
8259
+ savedSelection: null,
8260
+ transfer: null,
8261
+ pendingTransferId: null,
7655
8262
  creatingTransfer: false,
7656
8263
  passkeyConfigLoaded: false,
7657
8264
  activeCredentialId: null,
@@ -8095,552 +8702,118 @@ function applyAction(state, action) {
8095
8702
  // unauthenticated path) preserves the flag implicitly via the
8096
8703
  // surrounding `...state` spread, so no change is needed there.
8097
8704
  enableFullWidget: state.enableFullWidget
8098
- };
8099
- case "SYNC_PRIVY_SESSION":
8100
- if (action.ready && !action.authenticated) {
8101
- return {
8102
- ...clearAuthenticatedSessionState(state),
8103
- privyReady: true,
8104
- privyAuthenticated: false
8105
- };
8106
- }
8107
- return {
8108
- ...state,
8109
- privyReady: action.ready,
8110
- privyAuthenticated: action.authenticated,
8111
- ...action.authenticated ? { loginRequested: false } : {}
8112
- };
8113
- case "SYNC_AMOUNT":
8114
- return {
8115
- ...state,
8116
- amount: action.amount,
8117
- requireAmountEntry: action.amount === "" ? state.requireAmountEntry : false
8118
- };
8119
- case "SET_AMOUNT_INPUT":
8120
- return { ...state, amount: action.value };
8121
- case "FINALIZE_AMOUNT":
8122
- return { ...state, requireAmountEntry: false };
8123
- case "PAGE_RESUMED":
8124
- return { ...state, lastResumedAt: Date.now() };
8125
- // ── Setup deposit (combined first-time flow) ────────────────
8126
- case "SET_SETUP_DEPOSIT_AMOUNT":
8127
- return { ...state, setupDepositAmount: action.amount };
8128
- case "SET_SETUP_DEPOSIT_TOKEN":
8129
- return {
8130
- ...state,
8131
- setupDepositToken: {
8132
- symbol: action.symbol,
8133
- chainName: action.chainName,
8134
- ...action.walletId ? { walletId: action.walletId } : {},
8135
- ...action.tokenAddress ? { tokenAddress: action.tokenAddress } : {},
8136
- ...action.chainId != null ? { chainId: action.chainId } : {}
8137
- }
8138
- };
8139
- case "CLEAR_SETUP_DEPOSIT_TOKEN":
8140
- return { ...state, setupDepositToken: null };
8141
- case "CONFIRM_SETUP_DEPOSIT":
8142
- return { ...state, setupDepositConfirmed: true };
8143
- default:
8144
- return state;
8145
- }
8146
- }
8147
-
8148
- // src/setupDepositConfirmation.ts
8149
- function deriveEffectiveSetupSource(setupDepositToken, setupSelectedSourceOption) {
8150
- if (setupDepositToken) return setupDepositToken;
8151
- if (setupSelectedSourceOption) {
8152
- return {
8153
- symbol: setupSelectedSourceOption.tokenSymbol,
8154
- chainName: setupSelectedSourceOption.chainName,
8155
- walletId: setupSelectedSourceOption.walletId,
8156
- tokenAddress: setupSelectedSourceOption.tokenAddress,
8157
- chainId: setupSelectedSourceOption.chainId != null ? Number(setupSelectedSourceOption.chainId) : void 0
8158
- };
8159
- }
8160
- return null;
8161
- }
8162
- function planConfirmSetupDeposit(input) {
8163
- const effective = deriveEffectiveSetupSource(
8164
- input.setupDepositToken,
8165
- input.setupSelectedSourceOption
8166
- );
8167
- if (!effective) {
8168
- return { kind: "error", error: "Select a source token before continuing." };
8169
- }
8170
- if (!effective.walletId) {
8171
- return {
8172
- kind: "error",
8173
- error: "Selected source is not ready yet. Wait for wallet discovery to finish."
8174
- };
8175
- }
8176
- const actions = [
8177
- { type: "SET_ERROR", error: null },
8178
- {
8179
- type: "SET_SETUP_DEPOSIT_TOKEN",
8180
- symbol: effective.symbol,
8181
- chainName: effective.chainName,
8182
- walletId: effective.walletId,
8183
- tokenAddress: effective.tokenAddress,
8184
- chainId: effective.chainId
8185
- }
8186
- ];
8187
- if (input.selectedAccountId) {
8188
- actions.push({
8189
- type: "SELECT_ACCOUNT",
8190
- accountId: input.selectedAccountId,
8191
- walletId: effective.walletId
8192
- });
8193
- actions.push({
8194
- type: "SELECT_TOKEN",
8195
- walletId: effective.walletId,
8196
- tokenSymbol: effective.symbol
8197
- });
8198
- }
8199
- actions.push({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
8200
- actions.push({ type: "CONFIRM_SETUP_DEPOSIT" });
8201
- return {
8202
- kind: "proceed",
8203
- actions,
8204
- resolveSource: {
8205
- chainName: effective.chainName,
8206
- tokenSymbol: effective.symbol
8207
- }
8208
- };
8209
- }
8210
-
8211
- // src/manualTransferUtils.ts
8212
- function screenForSession(session) {
8213
- if (!session) return "source-selector";
8214
- switch (session.status) {
8215
- case "awaiting_deposit":
8216
- return "awaiting-deposit";
8217
- case "deposit_received":
8218
- return "deposit-received";
8219
- case "routing":
8220
- return "deposit-routing";
8221
- case "completed":
8222
- return "deposit-complete";
8223
- case "refunded":
8224
- case "failed":
8225
- case "wrong_token":
8226
- return "deposit-failed";
8227
- }
8228
- }
8229
- function feeCopy(_session) {
8230
- return "No fees";
8231
- }
8232
- function toTransfer(session) {
8233
- return {
8234
- id: session.sessionId,
8235
- status: session.status,
8236
- amount: {
8237
- amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd),
8238
- currency: "USD"
8239
- },
8240
- sources: [],
8241
- destinations: [{
8242
- id: session.sessionId,
8243
- chainId: session.destination.chainId,
8244
- address: session.destination.address,
8245
- token: { address: session.destination.token.address, symbol: "" },
8246
- amount: { amount: Number(session.deliveredAmountUsd ?? session.minAmountUsd), currency: "USD" }
8247
- }],
8248
- createDate: session.createDate,
8249
- updateDate: session.updateDate
8250
- };
8251
- }
8252
- var SOLANA_CHAIN_ID = 792703809;
8253
- var SOLANA_NATIVE_SOL_ADDRESS = "11111111111111111111111111111111";
8254
- function formatDepositUri(address, chainId, depToken) {
8255
- if (chainId === SOLANA_CHAIN_ID) {
8256
- if (!depToken || depToken === SOLANA_NATIVE_SOL_ADDRESS) {
8257
- return `solana:${address}`;
8258
- }
8259
- return `solana:${address}?spl-token=${depToken}`;
8260
- }
8261
- return address;
8262
- }
8263
-
8264
- // src/hooks/useManualTransferSession.ts
8265
- function useManualTransferSession({
8266
- destination,
8267
- merchantAuthorization,
8268
- idempotencyKey,
8269
- mock = false,
8270
- onComplete,
8271
- onError
8272
- }) {
8273
- const { apiBaseUrl } = useBlinkConfig();
8274
- const [sourceOptions, setSourceOptions] = react.useState(null);
8275
- const [loadingSources, setLoadingSources] = react.useState(true);
8276
- const [selectedToken, setSelectedToken] = react.useState("");
8277
- const [selectedChainId, setSelectedChainId] = react.useState("");
8278
- const [session, setSession] = react.useState(null);
8279
- const [loading, setLoading] = react.useState(false);
8280
- const [error, setError] = react.useState(null);
8281
- const [qrReady, setQrReady] = react.useState(false);
8282
- const [copiedAddress, setCopiedAddress] = react.useState(null);
8283
- const completedRef = react.useRef(null);
8284
- const lastCreatedOptionRef = react.useRef(null);
8285
- react.useEffect(() => {
8286
- if (!merchantAuthorization) return;
8287
- let cancelled = false;
8288
- setLoadingSources(true);
8289
- fetchManualTransferSources(apiBaseUrl, { merchantAuthorization, destination }).then((sources) => {
8290
- if (!cancelled) {
8291
- setSourceOptions(sources);
8292
- setLoadingSources(false);
8293
- const defaultOption = sources.find(
8294
- (opt) => opt.tokenSymbol === "USDC" && opt.chainId === 792703809
8295
- );
8296
- if (defaultOption) {
8297
- setSelectedToken("USDC");
8298
- setSelectedChainId("792703809");
8299
- }
8300
- }
8301
- }).catch((err) => {
8302
- if (!cancelled) {
8303
- setError(err instanceof Error ? err.message : String(err));
8304
- setLoadingSources(false);
8305
- }
8306
- });
8307
- return () => {
8308
- cancelled = true;
8309
- };
8310
- }, [apiBaseUrl, destination, merchantAuthorization]);
8311
- const depositAddress = session?.depositAddress;
8312
- react.useEffect(() => {
8313
- if (!depositAddress) {
8314
- setQrReady(false);
8315
- return;
8316
- }
8317
- const timer = window.setTimeout(() => setQrReady(true), 1200);
8318
- return () => window.clearTimeout(timer);
8319
- }, [depositAddress]);
8320
- const lastFeeCopyRef = react.useRef(null);
8321
- const nextFeeCopy = session ? feeCopy() : null;
8322
- const sessionFeeCopy = nextFeeCopy === lastFeeCopyRef.current ? lastFeeCopyRef.current : lastFeeCopyRef.current = nextFeeCopy;
8323
- const advanceMockStatus = react.useCallback((status) => {
8324
- setSession((prev) => prev ? { ...prev, status } : prev);
8325
- }, []);
8326
- react.useEffect(() => {
8327
- if (mock) return;
8328
- if (!session?.sessionId || ["completed", "failed", "refunded", "wrong_token"].includes(session.status)) return;
8329
- const timer = window.setInterval(() => {
8330
- fetchManualTransferSession(apiBaseUrl, session.sessionId).then(setSession).catch((err) => setError(err instanceof Error ? err.message : String(err)));
8331
- }, 2e3);
8332
- return () => window.clearInterval(timer);
8333
- }, [apiBaseUrl, mock, session?.sessionId, session?.status]);
8334
- react.useEffect(() => {
8335
- if (mock) return;
8336
- if (!session?.sessionId || session.status !== "awaiting_deposit") return;
8337
- const sessionId = session.sessionId;
8338
- const timer = window.setInterval(() => {
8339
- refreshManualTransferQuote(apiBaseUrl, sessionId).then(({ quoteValidUntil, minAmountUsd, slippage }) => {
8340
- setSession(
8341
- (prev) => prev && prev.sessionId === sessionId ? { ...prev, quoteValidUntil, minAmountUsd, slippage } : prev
8342
- );
8343
- }).catch((err) => {
8344
- console.warn("[manual-transfer] refresh quote failed", err);
8345
- });
8346
- }, 1e4);
8347
- return () => window.clearInterval(timer);
8348
- }, [apiBaseUrl, mock, session?.sessionId, session?.status]);
8349
- react.useEffect(() => {
8350
- if (session?.status !== "completed") return;
8351
- if (completedRef.current === session.sessionId) return;
8352
- completedRef.current = session.sessionId;
8353
- onComplete?.(toTransfer(session));
8354
- }, [onComplete, session]);
8355
- react.useEffect(() => {
8356
- if (!error) return;
8357
- onError?.(error);
8358
- }, [error, onError]);
8359
- const tokenChoices = react.useMemo(
8360
- () => Array.from(new Set((sourceOptions ?? []).map((opt) => opt.tokenSymbol))),
8361
- [sourceOptions]
8362
- );
8363
- const chainChoices = react.useMemo(() => {
8364
- const seen = /* @__PURE__ */ new Set();
8365
- return (sourceOptions ?? []).filter((opt) => {
8366
- if (seen.has(opt.chainId)) return false;
8367
- seen.add(opt.chainId);
8368
- return true;
8369
- });
8370
- }, [sourceOptions]);
8371
- const tokenLogoUriBySymbol = react.useMemo(() => {
8372
- const out = {};
8373
- for (const opt of sourceOptions ?? []) {
8374
- if (out[opt.tokenSymbol] == null) {
8375
- out[opt.tokenSymbol] = opt.tokenLogoUri;
8376
- }
8377
- }
8378
- return out;
8379
- }, [sourceOptions]);
8380
- const selectedOption = react.useMemo(
8381
- () => (sourceOptions ?? []).find(
8382
- (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === selectedChainId
8383
- ) ?? null,
8384
- [sourceOptions, selectedToken, selectedChainId]
8385
- );
8386
- const tokensForSelectedChain = react.useMemo(() => {
8387
- if (!selectedChainId) return null;
8388
- return new Set(
8389
- (sourceOptions ?? []).filter((opt) => String(opt.chainId) === selectedChainId).map((opt) => opt.tokenSymbol)
8390
- );
8391
- }, [sourceOptions, selectedChainId]);
8392
- const chainsForSelectedToken = react.useMemo(() => {
8393
- if (!selectedToken) return null;
8394
- return new Set(
8395
- (sourceOptions ?? []).filter((opt) => opt.tokenSymbol === selectedToken).map((opt) => opt.chainId)
8396
- );
8397
- }, [sourceOptions, selectedToken]);
8398
- const createSession = react.useCallback(async (option) => {
8399
- if (!merchantAuthorization) return;
8400
- setLoading(true);
8401
- setError(null);
8402
- try {
8403
- if (mock) {
8404
- const mockSession = {
8405
- sessionId: `mock-${Date.now()}`,
8406
- idempotencyKey: idempotencyKey ?? `mock-idem-${Date.now()}`,
8407
- merchantId: "mock-merchant",
8408
- destination,
8409
- source: {
8410
- chainId: option.chainId,
8411
- tokenAddress: option.tokenAddress,
8412
- tokenSymbol: option.tokenSymbol
8413
- },
8414
- refundTo: null,
8415
- depositAddress: "0x" + "a1b2c3d4e5f6".repeat(3).slice(0, 40),
8416
- requestId: `mock-req-${Date.now()}`,
8417
- status: "awaiting_deposit",
8418
- minAmountUsd: option.minAmountUsd,
8419
- quoteValidUntil: new Date(Date.now() + 36e5).toISOString(),
8420
- depositTxHashes: [],
8421
- destinationTxHash: null,
8422
- refundTxHashes: [],
8423
- deliveredAmountUsd: null,
8424
- errorCode: null,
8425
- errorMessage: null,
8426
- slippage: null,
8427
- createDate: (/* @__PURE__ */ new Date()).toISOString(),
8428
- updateDate: (/* @__PURE__ */ new Date()).toISOString()
8429
- };
8430
- setSession(mockSession);
8431
- } else {
8432
- const created = await createManualTransfer(apiBaseUrl, {
8433
- merchantAuthorization,
8434
- destination,
8435
- idempotencyKey,
8436
- source: {
8437
- chainId: option.chainId,
8438
- tokenAddress: option.tokenAddress
8439
- }
8440
- });
8441
- setSession(created);
8442
- }
8443
- } catch (err) {
8444
- setError(err instanceof Error ? err.message : String(err));
8445
- } finally {
8446
- setLoading(false);
8447
- }
8448
- }, [apiBaseUrl, destination, idempotencyKey, merchantAuthorization, mock]);
8449
- const resetSessionForNewSelection = react.useCallback(() => {
8450
- setSession(null);
8451
- lastCreatedOptionRef.current = null;
8452
- }, []);
8453
- const selectToken = react.useCallback((value) => {
8454
- setSelectedToken(value);
8455
- resetSessionForNewSelection();
8456
- const pairValid = (sourceOptions ?? []).some(
8457
- (opt) => opt.tokenSymbol === value && String(opt.chainId) === selectedChainId
8458
- );
8459
- if (!pairValid) {
8460
- const firstChain = (sourceOptions ?? []).find(
8461
- (opt) => opt.tokenSymbol === value
8462
- );
8463
- setSelectedChainId(
8464
- firstChain ? String(firstChain.chainId) : ""
8465
- );
8466
- }
8467
- }, [resetSessionForNewSelection, selectedChainId, sourceOptions]);
8468
- const selectChainId = react.useCallback((value) => {
8469
- setSelectedChainId(value);
8470
- resetSessionForNewSelection();
8471
- const pairValid = (sourceOptions ?? []).some(
8472
- (opt) => opt.tokenSymbol === selectedToken && String(opt.chainId) === value
8473
- );
8474
- if (!pairValid) {
8475
- const firstToken = (sourceOptions ?? []).find(
8476
- (opt) => String(opt.chainId) === value
8477
- );
8478
- setSelectedToken(
8479
- firstToken ? firstToken.tokenSymbol : ""
8480
- );
8481
- }
8482
- }, [resetSessionForNewSelection, selectedToken, sourceOptions]);
8483
- react.useEffect(() => {
8484
- if (!selectedOption) return;
8485
- const optionKey = `${selectedOption.chainId}:${selectedOption.tokenAddress}`;
8486
- if (lastCreatedOptionRef.current === optionKey) return;
8487
- lastCreatedOptionRef.current = optionKey;
8488
- void createSession(selectedOption);
8489
- }, [selectedOption, createSession]);
8490
- const screen = screenForSession(session);
8491
- const copyDepositAddress = react.useCallback(async (address) => {
8492
- try {
8493
- await navigator.clipboard.writeText(address);
8494
- } catch {
8495
- const textarea = document.createElement("textarea");
8496
- textarea.value = address;
8497
- textarea.setAttribute("readonly", "");
8498
- textarea.style.position = "absolute";
8499
- textarea.style.opacity = "0";
8500
- document.body.appendChild(textarea);
8501
- textarea.select();
8502
- try {
8503
- document.execCommand("copy");
8504
- } finally {
8505
- document.body.removeChild(textarea);
8705
+ };
8706
+ case "SYNC_PRIVY_SESSION":
8707
+ if (action.ready && !action.authenticated) {
8708
+ return {
8709
+ ...clearAuthenticatedSessionState(state),
8710
+ privyReady: true,
8711
+ privyAuthenticated: false
8712
+ };
8506
8713
  }
8714
+ return {
8715
+ ...state,
8716
+ privyReady: action.ready,
8717
+ privyAuthenticated: action.authenticated,
8718
+ ...action.authenticated ? { loginRequested: false } : {}
8719
+ };
8720
+ case "SYNC_AMOUNT":
8721
+ return {
8722
+ ...state,
8723
+ amount: action.amount,
8724
+ requireAmountEntry: action.amount === "" ? state.requireAmountEntry : false
8725
+ };
8726
+ case "SET_AMOUNT_INPUT":
8727
+ return { ...state, amount: action.value };
8728
+ case "FINALIZE_AMOUNT":
8729
+ return { ...state, requireAmountEntry: false };
8730
+ case "PAGE_RESUMED":
8731
+ return { ...state, lastResumedAt: Date.now() };
8732
+ // ── Setup deposit (combined first-time flow) ────────────────
8733
+ case "SET_SETUP_DEPOSIT_AMOUNT":
8734
+ return { ...state, setupDepositAmount: action.amount };
8735
+ case "SET_SETUP_DEPOSIT_TOKEN":
8736
+ return {
8737
+ ...state,
8738
+ setupDepositToken: {
8739
+ symbol: action.symbol,
8740
+ chainName: action.chainName,
8741
+ ...action.walletId ? { walletId: action.walletId } : {},
8742
+ ...action.tokenAddress ? { tokenAddress: action.tokenAddress } : {},
8743
+ ...action.chainId != null ? { chainId: action.chainId } : {}
8744
+ }
8745
+ };
8746
+ case "CLEAR_SETUP_DEPOSIT_TOKEN":
8747
+ return { ...state, setupDepositToken: null };
8748
+ case "CONFIRM_SETUP_DEPOSIT":
8749
+ return { ...state, setupDepositConfirmed: true };
8750
+ default:
8751
+ return state;
8752
+ }
8753
+ }
8754
+
8755
+ // src/setupDepositConfirmation.ts
8756
+ function deriveEffectiveSetupSource(setupDepositToken, setupSelectedSourceOption) {
8757
+ if (setupDepositToken) return setupDepositToken;
8758
+ if (setupSelectedSourceOption) {
8759
+ return {
8760
+ symbol: setupSelectedSourceOption.tokenSymbol,
8761
+ chainName: setupSelectedSourceOption.chainName,
8762
+ walletId: setupSelectedSourceOption.walletId,
8763
+ tokenAddress: setupSelectedSourceOption.tokenAddress,
8764
+ chainId: setupSelectedSourceOption.chainId != null ? Number(setupSelectedSourceOption.chainId) : void 0
8765
+ };
8766
+ }
8767
+ return null;
8768
+ }
8769
+ function planConfirmSetupDeposit(input) {
8770
+ const effective = deriveEffectiveSetupSource(
8771
+ input.setupDepositToken,
8772
+ input.setupSelectedSourceOption
8773
+ );
8774
+ if (!effective) {
8775
+ return { kind: "error", error: "Select a source token before continuing." };
8776
+ }
8777
+ if (!effective.walletId) {
8778
+ return {
8779
+ kind: "error",
8780
+ error: "Selected source is not ready yet. Wait for wallet discovery to finish."
8781
+ };
8782
+ }
8783
+ const actions = [
8784
+ { type: "SET_ERROR", error: null },
8785
+ {
8786
+ type: "SET_SETUP_DEPOSIT_TOKEN",
8787
+ symbol: effective.symbol,
8788
+ chainName: effective.chainName,
8789
+ walletId: effective.walletId,
8790
+ tokenAddress: effective.tokenAddress,
8791
+ chainId: effective.chainId
8507
8792
  }
8508
- setCopiedAddress(address);
8509
- window.setTimeout(() => setCopiedAddress((cur) => cur === address ? null : cur), 1500);
8510
- }, []);
8511
- const backToSourceSelector = react.useCallback(() => {
8512
- setSession(null);
8513
- setCopiedAddress(null);
8514
- setError(null);
8515
- lastCreatedOptionRef.current = null;
8516
- if (selectedOption) {
8517
- void createSession(selectedOption);
8518
- }
8519
- }, [selectedOption, createSession]);
8793
+ ];
8794
+ if (input.selectedAccountId) {
8795
+ actions.push({
8796
+ type: "SELECT_ACCOUNT",
8797
+ accountId: input.selectedAccountId,
8798
+ walletId: effective.walletId
8799
+ });
8800
+ actions.push({
8801
+ type: "SELECT_TOKEN",
8802
+ walletId: effective.walletId,
8803
+ tokenSymbol: effective.symbol
8804
+ });
8805
+ }
8806
+ actions.push({ type: "SET_SETUP_FLOW_SCREEN", screen: "one-tap-setup" });
8807
+ actions.push({ type: "CONFIRM_SETUP_DEPOSIT" });
8520
8808
  return {
8521
- sourceOptions,
8522
- loadingSources,
8523
- selectedToken,
8524
- selectedChainId,
8525
- session,
8526
- loading,
8527
- error,
8528
- qrReady,
8529
- copiedAddress,
8530
- tokenChoices,
8531
- chainChoices,
8532
- tokenLogoUriBySymbol,
8533
- selectedOption,
8534
- tokensForSelectedChain,
8535
- chainsForSelectedToken,
8536
- screen,
8537
- sessionFeeCopy,
8538
- depositAddress,
8539
- createSession,
8540
- copyDepositAddress,
8541
- backToSourceSelector,
8542
- resetSessionForNewSelection,
8543
- advanceMockStatus,
8544
- selectToken,
8545
- selectChainId
8809
+ kind: "proceed",
8810
+ actions,
8811
+ resolveSource: {
8812
+ chainName: effective.chainName,
8813
+ tokenSymbol: effective.symbol
8814
+ }
8546
8815
  };
8547
8816
  }
8548
- var MOCK_STATUSES = [
8549
- "awaiting_deposit",
8550
- "deposit_received",
8551
- "routing",
8552
- "completed",
8553
- "failed"
8554
- ];
8555
- function DevMockPanel({
8556
- status,
8557
- onSetStatus,
8558
- hasSession
8559
- }) {
8560
- const [collapsed, setCollapsed] = react.useState(true);
8561
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: devPanelContainerStyle, children: [
8562
- /* @__PURE__ */ jsxRuntime.jsx(
8563
- "button",
8564
- {
8565
- type: "button",
8566
- onClick: () => setCollapsed((c) => !c),
8567
- style: devPanelToggleStyle,
8568
- children: collapsed ? "DEV" : "\u2715"
8569
- }
8570
- ),
8571
- !collapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: devPanelBodyStyle, children: [
8572
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: devPanelLabelStyle, children: hasSession ? `Status: ${status}` : "No session" }),
8573
- MOCK_STATUSES.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
8574
- "button",
8575
- {
8576
- type: "button",
8577
- disabled: !hasSession || status === s,
8578
- onClick: () => onSetStatus(s),
8579
- style: devPanelButtonStyle(status === s),
8580
- children: s.replace(/_/g, " ")
8581
- },
8582
- s
8583
- ))
8584
- ] })
8585
- ] });
8586
- }
8587
- var devPanelContainerStyle = {
8588
- position: "fixed",
8589
- bottom: 16,
8590
- right: 16,
8591
- zIndex: 99999,
8592
- display: "flex",
8593
- flexDirection: "column",
8594
- alignItems: "flex-end",
8595
- gap: 8,
8596
- fontFamily: "monospace",
8597
- fontSize: 12
8598
- };
8599
- var devPanelToggleStyle = {
8600
- background: "#1a1a1a",
8601
- border: "1px solid #333",
8602
- borderRadius: 8,
8603
- color: "#0f0",
8604
- cursor: "pointer",
8605
- fontSize: 11,
8606
- fontFamily: "monospace",
8607
- fontWeight: 700,
8608
- height: 36,
8609
- width: 36,
8610
- display: "flex",
8611
- alignItems: "center",
8612
- justifyContent: "center"
8613
- };
8614
- var devPanelBodyStyle = {
8615
- background: "#1a1a1a",
8616
- border: "1px solid #333",
8617
- borderRadius: 10,
8618
- display: "flex",
8619
- flexDirection: "column",
8620
- gap: 4,
8621
- padding: 10,
8622
- minWidth: 160
8623
- };
8624
- var devPanelLabelStyle = {
8625
- color: "#888",
8626
- fontSize: 10,
8627
- marginBottom: 4,
8628
- textTransform: "uppercase",
8629
- letterSpacing: "0.05em"
8630
- };
8631
- var devPanelButtonStyle = (active) => ({
8632
- background: active ? "#0f0" : "#2a2a2a",
8633
- border: "1px solid #444",
8634
- borderRadius: 6,
8635
- color: active ? "#000" : "#ccc",
8636
- cursor: active ? "default" : "pointer",
8637
- fontFamily: "monospace",
8638
- fontSize: 11,
8639
- fontWeight: active ? 700 : 400,
8640
- padding: "6px 10px",
8641
- textAlign: "left",
8642
- textTransform: "capitalize"
8643
- });
8644
8817
  function ScreenLayout({ children, footer }) {
8645
8818
  const { tokens, theme, isMobileApp } = useBlinkConfig();
8646
8819
  const isRedesign = theme.endsWith("New");
@@ -10355,16 +10528,21 @@ function DepositQrCodeImpl({
10355
10528
  depToken
10356
10529
  }) {
10357
10530
  const { tokens } = useBlinkConfig();
10358
- const [dataUrl, setDataUrl] = react.useState(null);
10359
10531
  const uri = formatDepositUri(address, chainId, depToken);
10532
+ const dark = tokens.text;
10533
+ const light = tokens.bg;
10534
+ const [dataUrl, setDataUrl] = react.useState(
10535
+ () => getCachedQrDataUrl(uri, { dark, light })
10536
+ );
10360
10537
  react.useEffect(() => {
10538
+ const cached = getCachedQrDataUrl(uri, { dark, light });
10539
+ if (cached) {
10540
+ setDataUrl(cached);
10541
+ return;
10542
+ }
10361
10543
  let cancelled = false;
10362
- QRCode__namespace.toDataURL(uri, {
10363
- errorCorrectionLevel: "H",
10364
- margin: 1,
10365
- width: 203,
10366
- color: { dark: tokens.text, light: tokens.bg }
10367
- }).then((url) => {
10544
+ setDataUrl(null);
10545
+ getOrRenderQrDataUrl(uri, { dark, light }).then((url) => {
10368
10546
  if (!cancelled) setDataUrl(url);
10369
10547
  }).catch(() => {
10370
10548
  if (!cancelled) setDataUrl(null);
@@ -10372,7 +10550,7 @@ function DepositQrCodeImpl({
10372
10550
  return () => {
10373
10551
  cancelled = true;
10374
10552
  };
10375
- }, [uri]);
10553
+ }, [uri, dark, light]);
10376
10554
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: qrFrameStyle(tokens.bgCardTranslucent), children: dataUrl ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: qrContainerStyle, children: [
10377
10555
  /* @__PURE__ */ jsxRuntime.jsx("img", { src: dataUrl, alt: "Deposit QR code", style: qrImageStyle }),
10378
10556
  /* @__PURE__ */ jsxRuntime.jsx("img", { src: BLINK_QR_LOGO, alt: "", style: qrLogoStyle })
@@ -11460,7 +11638,7 @@ function EnterAmountScreen({
11460
11638
  )
11461
11639
  ] }),
11462
11640
  children: [
11463
- /* @__PURE__ */ jsxRuntime.jsx(ScreenHeader, { right: headerRight }),
11641
+ /* @__PURE__ */ jsxRuntime.jsx(ScreenHeader, { title: "Deposit", right: headerRight }),
11464
11642
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle4(isDesktop), children: [
11465
11643
  isDesktop ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: desktopHeroRowStyle(heroColor, getDesktopHeroFontSize(value)), children: [
11466
11644
  /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: dollarStyle2(isZero), children: "$" }),
@@ -12801,7 +12979,8 @@ function SetupScreen({
12801
12979
  onLogout,
12802
12980
  loading,
12803
12981
  loadingShimmersEnabled = false,
12804
- error
12982
+ error,
12983
+ selectedTokenSymbol
12805
12984
  }) {
12806
12985
  const { tokens } = useBlinkConfig();
12807
12986
  const [selectedPreset, setSelectedPreset] = react.useState(DEFAULT_MAX);
@@ -12910,7 +13089,8 @@ function SetupScreen({
12910
13089
  style: illustrationStyle3
12911
13090
  }
12912
13091
  ),
12913
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(tokens.text), children: "Next time deposit\nUSDC in one tap" }),
13092
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(tokens.text), children: `Next time deposit
13093
+ ${selectedTokenSymbol ?? "USDC"} in one tap` }),
12914
13094
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: subtitleStyle5(tokens.textSecondary), children: "Set a cap for passkey deposits.\nYou always stay in control." }),
12915
13095
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorBannerStyle5(tokens), children: error }),
12916
13096
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: chipsRowStyle, children: PRESETS.map(({ label, value }) => {
@@ -12938,8 +13118,8 @@ var contentStyle9 = {
12938
13118
  alignItems: "center",
12939
13119
  justifyContent: "center",
12940
13120
  textAlign: "center",
12941
- gap: 16,
12942
- padding: "48px 8px 8px"
13121
+ gap: 12,
13122
+ padding: "8px 0"
12943
13123
  };
12944
13124
  var shimmerContentStyle = {
12945
13125
  display: "flex",
@@ -13310,7 +13490,7 @@ function SetupDepositScreen({
13310
13490
  NotificationBanner,
13311
13491
  {
13312
13492
  variant: "negative",
13313
- title: "Deposit amount exceeds balance",
13493
+ title: "Deposit amount exceeds stablecoin balance",
13314
13494
  description: "Pick another asset or add more funds to your wallet"
13315
13495
  }
13316
13496
  ) : /* @__PURE__ */ jsxRuntime.jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
@@ -13928,7 +14108,7 @@ function DepositScreen({
13928
14108
  NotificationBanner,
13929
14109
  {
13930
14110
  variant: "negative",
13931
- title: "Deposit amount exceeds balance",
14111
+ title: "Deposit amount exceeds stablecoin balance",
13932
14112
  description: "Pick another asset or add more funds to your wallet"
13933
14113
  }
13934
14114
  ) : needsAuthorization ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -14850,12 +15030,12 @@ function DepositAddressScreen({
14850
15030
  ),
14851
15031
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle16, children: [
14852
15032
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: depositTitleStyle(tokens.text), children: "Deposit Address" }),
14853
- qrReady && session ? /* @__PURE__ */ jsxRuntime.jsx(
15033
+ qrReady && session && selectedOption ? /* @__PURE__ */ jsxRuntime.jsx(
14854
15034
  DepositQrCode,
14855
15035
  {
14856
15036
  address: session.depositAddress,
14857
- chainId: session.source.chainId,
14858
- depToken: session.source.tokenAddress
15037
+ chainId: selectedOption.chainId,
15038
+ depToken: selectedOption.tokenAddress
14859
15039
  }
14860
15040
  ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14861
15041
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
@@ -15874,12 +16054,7 @@ var errorBannerStyle8 = (themeTokens) => ({
15874
16054
  textAlign: "left"
15875
16055
  });
15876
16056
  function ManualTransferFlow({
15877
- destination,
15878
- merchantAuthorization,
15879
- idempotencyKey,
15880
- mock = false,
15881
- onComplete,
15882
- onError,
16057
+ hasMerchantAuthorization,
15883
16058
  onCreatePasskey,
15884
16059
  createPasskeyLoading = false,
15885
16060
  createPasskeyError = null,
@@ -15908,17 +16083,9 @@ function ManualTransferFlow({
15908
16083
  depositAddress,
15909
16084
  copyDepositAddress,
15910
16085
  backToSourceSelector,
15911
- advanceMockStatus,
15912
16086
  selectToken,
15913
16087
  selectChainId
15914
- } = useManualTransferSession({
15915
- destination,
15916
- merchantAuthorization,
15917
- idempotencyKey,
15918
- mock,
15919
- onComplete,
15920
- onError
15921
- });
16088
+ } = useManualTransferSessionContext();
15922
16089
  const [passkeyCreated, setPasskeyCreated] = react.useState(false);
15923
16090
  const prevPasskeyLoadingRef = react.useRef(false);
15924
16091
  react.useEffect(() => {
@@ -15927,9 +16094,8 @@ function ManualTransferFlow({
15927
16094
  }
15928
16095
  prevPasskeyLoadingRef.current = createPasskeyLoading;
15929
16096
  }, [createPasskeyLoading, createPasskeyError]);
15930
- const DEV_MOCK_STATUS = mock;
15931
16097
  let screenContent = null;
15932
- if (!merchantAuthorization) {
16098
+ if (!hasMerchantAuthorization) {
15933
16099
  screenContent = /* @__PURE__ */ jsxRuntime.jsxs(ScreenLayout, { footer: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onBack, children: "Back" }), children: [
15934
16100
  /* @__PURE__ */ jsxRuntime.jsx(ScreenHeader, { onBack }),
15935
16101
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle20, children: [
@@ -15999,17 +16165,7 @@ function ManualTransferFlow({
15999
16165
  if (!screenContent) {
16000
16166
  return null;
16001
16167
  }
16002
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
16003
- screenContent,
16004
- DEV_MOCK_STATUS && merchantAuthorization && /* @__PURE__ */ jsxRuntime.jsx(
16005
- DevMockPanel,
16006
- {
16007
- status: session?.status ?? null,
16008
- onSetStatus: advanceMockStatus,
16009
- hasSession: !!session
16010
- }
16011
- )
16012
- ] });
16168
+ return screenContent;
16013
16169
  }
16014
16170
  var contentStyle20 = {
16015
16171
  alignItems: "center",
@@ -16309,6 +16465,7 @@ function buildSetupDepositScreenProps({
16309
16465
  function buildSetupScreenProps({
16310
16466
  flow,
16311
16467
  remote,
16468
+ derived,
16312
16469
  forms,
16313
16470
  handlers
16314
16471
  }) {
@@ -16317,11 +16474,12 @@ function buildSetupScreenProps({
16317
16474
  const waitingForDesktopWalletConnection = flow.isDesktop && remote.authExecutorExecuting && remote.pendingOneTapSetup == null && (remote.authExecutorCurrentAction == null || remote.authExecutorCurrentAction.type === "OPEN_PROVIDER");
16318
16475
  return {
16319
16476
  onSetupOneTap: handlers.onSetupOneTap,
16320
- onBack: handlers.onBackFromSubflow,
16321
- onLogout: handlers.onLogout,
16477
+ onBack: flow.isDesktop ? handlers.onBackFromSubflow : void 0,
16478
+ onLogout: flow.isDesktop ? handlers.onLogout : void 0,
16322
16479
  loading: savingOneTapLimit,
16323
16480
  loadingShimmersEnabled: waitingForDesktopWalletConnection,
16324
- error: state.error
16481
+ error: state.error,
16482
+ selectedTokenSymbol: derived.selectedSource?.token.symbol
16325
16483
  };
16326
16484
  }
16327
16485
  function buildConfirmSignScreenProps({
@@ -16598,12 +16756,7 @@ function StepRendererContent({
16598
16756
  return /* @__PURE__ */ jsxRuntime.jsx(
16599
16757
  ManualTransferFlow,
16600
16758
  {
16601
- destination: flow.destination,
16602
- merchantAuthorization: flow.merchantAuthorization,
16603
- idempotencyKey: flow.idempotencyKey,
16604
- mock: flow.mock,
16605
- onComplete: flow.onComplete,
16606
- onError: flow.onError,
16759
+ hasMerchantAuthorization: flow.merchantAuthorization != null,
16607
16760
  onCreatePasskey: handlers.onSignupWithPasskey,
16608
16761
  createPasskeyLoading: flow.passkeyLoading,
16609
16762
  createPasskeyError: flow.state.error,
@@ -18821,8 +18974,11 @@ function usePasskeyCheckEffect(deps) {
18821
18974
  setupAccountIdRef,
18822
18975
  reauthSessionIdRef,
18823
18976
  reauthTokenRef,
18824
- pollingTransferIdRef
18977
+ pollingTransferIdRef,
18978
+ reloadAccounts
18825
18979
  } = deps;
18980
+ const reloadAccountsRef = react.useRef(reloadAccounts);
18981
+ reloadAccountsRef.current = reloadAccounts;
18826
18982
  const { getAccessToken: privyGetAccessToken, user } = reactAuth.usePrivy();
18827
18983
  const effectiveGetAccessToken = deps.getAccessToken ?? privyGetAccessToken;
18828
18984
  const onCompleteRef = react.useRef(deps.onComplete);
@@ -18952,6 +19108,11 @@ function usePasskeyCheckEffect(deps) {
18952
19108
  mobileSetupFlowRef.current = false;
18953
19109
  setupAccountIdRef.current = null;
18954
19110
  clearMobileFlowState();
19111
+ try {
19112
+ await reloadAccountsRef.current();
19113
+ } catch {
19114
+ }
19115
+ dispatch({ type: "MOBILE_SETUP_COMPLETE" });
18955
19116
  return;
18956
19117
  }
18957
19118
  if (persisted.accountId && !persisted.transferId) {
@@ -18960,7 +19121,14 @@ function usePasskeyCheckEffect(deps) {
18960
19121
  const session = await fetchAuthorizationSession(apiBaseUrl, persisted.sessionId);
18961
19122
  if (cancelled) return;
18962
19123
  if (session.status === "AUTHORIZED") {
19124
+ mobileSetupFlowRef.current = false;
19125
+ setupAccountIdRef.current = null;
18963
19126
  clearMobileFlowState();
19127
+ try {
19128
+ await reloadAccountsRef.current();
19129
+ } catch {
19130
+ }
19131
+ dispatch({ type: "MOBILE_SETUP_COMPLETE" });
18964
19132
  return;
18965
19133
  }
18966
19134
  } catch {
@@ -19425,25 +19593,24 @@ function useMobilePollingEffect(deps) {
19425
19593
  dispatch({ type: "MOBILE_SETUP_COMPLETE" });
19426
19594
  };
19427
19595
  const pollWalletActive = async () => {
19596
+ if (setupSessionId) {
19597
+ try {
19598
+ const session = await fetchAuthorizationSession(apiBaseUrl, setupSessionId);
19599
+ if (cancelled) return;
19600
+ if (session.status === "AUTHORIZED") {
19601
+ await completeSetup();
19602
+ return;
19603
+ }
19604
+ } catch {
19605
+ }
19606
+ }
19428
19607
  try {
19429
19608
  const token = await getAccessTokenRef.current();
19430
19609
  if (!token || cancelled) return;
19431
19610
  const acct = await fetchAccount(apiBaseUrl, token, accountId, credentialId);
19432
19611
  if (cancelled) return;
19433
- const hasActive = acct.wallets.some((w) => w.status === "ACTIVE");
19434
- if (hasActive) {
19612
+ if (acct.wallets.some((w) => w.status === "ACTIVE")) {
19435
19613
  await completeSetup();
19436
- return;
19437
- }
19438
- if (setupSessionId) {
19439
- try {
19440
- const session = await fetchAuthorizationSession(apiBaseUrl, setupSessionId);
19441
- if (cancelled) return;
19442
- if (session.status === "AUTHORIZED") {
19443
- await completeSetup();
19444
- }
19445
- } catch {
19446
- }
19447
19614
  }
19448
19615
  } catch {
19449
19616
  }
@@ -19878,7 +20045,6 @@ function BlinkPayment(props) {
19878
20045
  function BlinkPaymentInner({
19879
20046
  destination,
19880
20047
  initialScreen,
19881
- mock,
19882
20048
  onComplete,
19883
20049
  onError,
19884
20050
  useWalletConnector: useWalletConnectorProp,
@@ -19967,6 +20133,7 @@ function BlinkPaymentInner({
19967
20133
  minTransferAmountUsd: effectiveMinDepositAmountUsd
19968
20134
  });
19969
20135
  const feeEstimateEnabled = effectiveAuthenticated && (state.phase.step === "deposit" || state.phase.step === "wallet-setup" || state.phase.step === "one-tap-setup");
20136
+ const manualTransferActive = screenForPhase(state.phase) === "manual-transfer";
19970
20137
  const setupSelectedSourceOption = react.useMemo(() => {
19971
20138
  const options = sourceSelection.pendingSelectSourceAction?.metadata?.options ?? [];
19972
20139
  return resolveSelectSourceOption(
@@ -20113,13 +20280,14 @@ function BlinkPaymentInner({
20113
20280
  idempotencyKey
20114
20281
  });
20115
20282
  const accountSwitchSessionId = state.setupAuthorizationSessionId;
20283
+ const accountSwitchListenerArmed = accountSwitchSessionId != null && state.desktopWait == null;
20116
20284
  const handleProviderWalletAccountSwitch = provider.handleWalletAccountSwitch;
20117
20285
  const onWalletAccountChanged = react.useCallback(async (change) => {
20118
20286
  if (!accountSwitchSessionId) return;
20119
20287
  await handleProviderWalletAccountSwitch(change, accountSwitchSessionId);
20120
20288
  }, [handleProviderWalletAccountSwitch, accountSwitchSessionId]);
20121
20289
  useWalletAccountSwitchEffect({
20122
- enabled: accountSwitchSessionId != null,
20290
+ enabled: accountSwitchListenerArmed,
20123
20291
  isDesktop,
20124
20292
  onAccountChanged: onWalletAccountChanged
20125
20293
  });
@@ -20215,6 +20383,7 @@ function BlinkPaymentInner({
20215
20383
  reauthTokenRef: mobileFlowRefs.reauthTokenRef,
20216
20384
  pollingTransferIdRef: transfer.pollingTransferIdRef,
20217
20385
  handleAuthorizedMobileReturn: mobileFlow.handleAuthorizedMobileReturn,
20386
+ reloadAccounts: transfer.reloadAccounts,
20218
20387
  onComplete,
20219
20388
  getAccessToken: effectiveGetAccessToken,
20220
20389
  pendingTransferFlowKey
@@ -20381,15 +20550,18 @@ function BlinkPaymentInner({
20381
20550
  dispatch,
20382
20551
  orchestrator
20383
20552
  ]);
20384
- const handleBackFromSetupDeposit = react.useCallback(() => {
20553
+ const handleBackFromSetupDeposit = react.useCallback(async () => {
20385
20554
  orchestrator.cancelPendingFlow();
20386
20555
  authExecutor.cancelPendingExecution();
20387
20556
  clearScreenErrors();
20557
+ if (isDesktop) {
20558
+ await revokeAndDisconnectActiveWagmiConnector(wagmiConfig);
20559
+ }
20388
20560
  dispatch({ type: "RESTORE_SELECTION" });
20389
20561
  dispatch({ type: "CLEAR_SETUP_DEPOSIT_TOKEN" });
20390
20562
  dispatch({ type: "SET_SETUP_FLOW_SCREEN", screen: null });
20391
20563
  dispatch({ type: "SET_USER_INTENT", intent: { step: "wallet-picker", reason: "switch" } });
20392
- }, [authExecutor, clearScreenErrors, orchestrator, dispatch]);
20564
+ }, [authExecutor, clearScreenErrors, orchestrator, dispatch, isDesktop, wagmiConfig]);
20393
20565
  const handleAuthorizationRetry = react.useCallback(() => {
20394
20566
  void (async () => {
20395
20567
  try {
@@ -20429,6 +20601,11 @@ function BlinkPaymentInner({
20429
20601
  clearScreenErrors();
20430
20602
  dispatch({ type: "SET_SETUP_DEPOSIT_AMOUNT", amount });
20431
20603
  dispatch({ type: "SET_USER_INTENT", intent: { step: "wallet-picker", reason: "switch" } });
20604
+ void (async () => {
20605
+ await revokeAndDisconnectActiveWagmiConnector(wagmiConfig);
20606
+ await authExecutor.resetWalletConnect().catch(() => {
20607
+ });
20608
+ })();
20432
20609
  },
20433
20610
  onBackFromSetupDeposit: handleBackFromSetupDeposit,
20434
20611
  onBackFromSubflow: () => {
@@ -20477,65 +20654,76 @@ function BlinkPaymentInner({
20477
20654
  handleSetDepositToken,
20478
20655
  handleConfirmSetupDeposit,
20479
20656
  handleBackFromSetupDeposit,
20480
- handleAuthorizationRetry
20657
+ handleAuthorizationRetry,
20658
+ wagmiConfig
20481
20659
  ]);
20482
20660
  return /* @__PURE__ */ jsxRuntime.jsx(EffectiveDepositAmountProvider, { value: effectiveDepositAmount, children: /* @__PURE__ */ jsxRuntime.jsx(
20483
- StepRenderer,
20661
+ ManualTransferSessionProvider,
20484
20662
  {
20485
- flow: {
20486
- state,
20487
- authenticated: effectiveAuthenticated,
20488
- passkeyLoading: auth.passkeyLoginStatus !== "initial" && auth.passkeyLoginStatus !== "done" && auth.passkeyLoginStatus !== "error" || auth.passkeySignupStatus !== "initial" && auth.passkeySignupStatus !== "done" && auth.passkeySignupStatus !== "error" || auth.passkeySignupPopupActive,
20489
- isDesktop,
20490
- isMobileApp: isMobileApp ?? false,
20491
- merchantName,
20492
- onBack,
20493
- onDismiss,
20494
- depositAmount,
20495
- effectiveDepositAmount,
20496
- minTransferAmountUsd,
20497
- destination,
20498
- merchantAuthorization,
20499
- idempotencyKey,
20500
- mock,
20501
- onComplete,
20502
- onError
20503
- },
20504
- remote: {
20505
- pollingTransfer: polling.transfer,
20506
- pollingError: polling.error,
20507
- authExecutorError: authExecutor.error,
20508
- authExecutorExecuting: authExecutor.executing,
20509
- authExecutorCurrentAction: authExecutor.currentAction,
20510
- pendingOneTapSetup: orchestrator.pendingOneTapAction,
20511
- setupAuthorizationComplete,
20512
- transferSigningSigning: transferSigning.signing,
20513
- transferSigningError: transferSigning.error,
20514
- transferSigningPasskeyDismissed: transferSigning.passkeyDismissed,
20515
- pendingSelectSource: orchestrator.pendingSelectSourceAction
20516
- },
20517
- derived: {
20518
- pendingConnections: derived.pendingConnections,
20519
- depositEligibleAccounts: derived.depositEligibleAccounts,
20520
- sourceName: derived.sourceName,
20521
- maxSourceBalance: derived.maxSourceBalance,
20522
- tokenCount: derived.tokenCount,
20523
- selectedAccount: derived.selectedAccount,
20524
- selectedSource: derived.selectedSource,
20525
- selectSourceChoices: sourceSelection.selectSourceChoices,
20526
- selectSourceRecommended: sourceSelection.selectSourceRecommended,
20527
- selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance
20528
- },
20529
- forms: {
20530
- selectSourceChainName: sourceSelection.selectSourceChainName,
20531
- selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
20532
- savingOneTapLimit: oneTapSetup.savingOneTapLimit,
20533
- depositQuoteId: depositFee.quoteId,
20534
- depositQuoteFee: depositFee.quoteFee,
20535
- depositQuoteLoading: depositFee.quoteLoading,
20536
- depositQuoteError: depositFee.quoteError
20537
- },
20538
- handlers
20663
+ destination,
20664
+ merchantAuthorization,
20665
+ idempotencyKey,
20666
+ onComplete,
20667
+ onError,
20668
+ pollEnabled: manualTransferActive,
20669
+ children: /* @__PURE__ */ jsxRuntime.jsx(
20670
+ StepRenderer,
20671
+ {
20672
+ flow: {
20673
+ state,
20674
+ authenticated: effectiveAuthenticated,
20675
+ passkeyLoading: auth.passkeyLoginStatus !== "initial" && auth.passkeyLoginStatus !== "done" && auth.passkeyLoginStatus !== "error" || auth.passkeySignupStatus !== "initial" && auth.passkeySignupStatus !== "done" && auth.passkeySignupStatus !== "error" || auth.passkeySignupPopupActive,
20676
+ isDesktop,
20677
+ isMobileApp: isMobileApp ?? false,
20678
+ merchantName,
20679
+ onBack,
20680
+ onDismiss,
20681
+ depositAmount,
20682
+ effectiveDepositAmount,
20683
+ minTransferAmountUsd,
20684
+ destination,
20685
+ merchantAuthorization,
20686
+ idempotencyKey,
20687
+ onComplete,
20688
+ onError
20689
+ },
20690
+ remote: {
20691
+ pollingTransfer: polling.transfer,
20692
+ pollingError: polling.error,
20693
+ authExecutorError: authExecutor.error,
20694
+ authExecutorExecuting: authExecutor.executing,
20695
+ authExecutorCurrentAction: authExecutor.currentAction,
20696
+ pendingOneTapSetup: orchestrator.pendingOneTapAction,
20697
+ setupAuthorizationComplete,
20698
+ transferSigningSigning: transferSigning.signing,
20699
+ transferSigningError: transferSigning.error,
20700
+ transferSigningPasskeyDismissed: transferSigning.passkeyDismissed,
20701
+ pendingSelectSource: orchestrator.pendingSelectSourceAction
20702
+ },
20703
+ derived: {
20704
+ pendingConnections: derived.pendingConnections,
20705
+ depositEligibleAccounts: derived.depositEligibleAccounts,
20706
+ sourceName: derived.sourceName,
20707
+ maxSourceBalance: derived.maxSourceBalance,
20708
+ tokenCount: derived.tokenCount,
20709
+ selectedAccount: derived.selectedAccount,
20710
+ selectedSource: derived.selectedSource,
20711
+ selectSourceChoices: sourceSelection.selectSourceChoices,
20712
+ selectSourceRecommended: sourceSelection.selectSourceRecommended,
20713
+ selectSourceAvailableBalance: sourceSelection.selectSourceAvailableBalance
20714
+ },
20715
+ forms: {
20716
+ selectSourceChainName: sourceSelection.selectSourceChainName,
20717
+ selectSourceTokenSymbol: sourceSelection.selectSourceTokenSymbol,
20718
+ savingOneTapLimit: oneTapSetup.savingOneTapLimit,
20719
+ depositQuoteId: depositFee.quoteId,
20720
+ depositQuoteFee: depositFee.quoteFee,
20721
+ depositQuoteLoading: depositFee.quoteLoading,
20722
+ depositQuoteError: depositFee.quoteError
20723
+ },
20724
+ handlers
20725
+ }
20726
+ )
20539
20727
  }
20540
20728
  ) });
20541
20729
  }