@swype-org/react-sdk 0.2.242 → 0.2.261

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