@swype-org/react-sdk 0.2.375 → 0.2.399

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -13,9 +13,12 @@ var core = require('@wagmi/core');
13
13
  var viem = require('viem');
14
14
  var utils = require('viem/utils');
15
15
  var QRCode = require('qrcode');
16
+ var posthog = require('posthog-js');
16
17
  var Select = require('@radix-ui/react-select');
17
18
 
18
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
+
19
22
  function _interopNamespace(e) {
20
23
  if (e && e.__esModule) return e;
21
24
  var n = Object.create(null);
@@ -35,6 +38,7 @@ function _interopNamespace(e) {
35
38
  }
36
39
 
37
40
  var QRCode__namespace = /*#__PURE__*/_interopNamespace(QRCode);
41
+ var posthog__default = /*#__PURE__*/_interopDefault(posthog);
38
42
  var Select__namespace = /*#__PURE__*/_interopNamespace(Select);
39
43
 
40
44
  var __defProp = Object.defineProperty;
@@ -78,7 +82,11 @@ var darkTheme = {
78
82
  shadowLg: "0 18px 44px rgba(0,0,0,0.42)",
79
83
  radius: "14px",
80
84
  radiusLg: "24px",
81
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
85
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
86
+ fontWeightRegular: 400,
87
+ fontWeightMedium: 500,
88
+ fontWeightSemibold: 600,
89
+ fontWeightBold: 700
82
90
  };
83
91
  var lightTheme = {
84
92
  bg: "#ebf9fb",
@@ -110,7 +118,11 @@ var lightTheme = {
110
118
  shadowLg: "0 20px 48px rgba(19, 61, 75, 0.14)",
111
119
  radius: "14px",
112
120
  radiusLg: "24px",
113
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
121
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
122
+ fontWeightRegular: 400,
123
+ fontWeightMedium: 500,
124
+ fontWeightSemibold: 600,
125
+ fontWeightBold: 700
114
126
  };
115
127
  var lightTransparentTheme = {
116
128
  ...lightTheme
@@ -153,7 +165,13 @@ var lightThemeNew = {
153
165
  shadowLg: "0 20px 48px rgba(0, 0, 0, 0.14)",
154
166
  radius: "14px",
155
167
  radiusLg: "24px",
156
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
168
+ // Figma redesign uses Inter; fall back to the system stack when the host
169
+ // page hasn't loaded the Inter webfont.
170
+ fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
171
+ fontWeightRegular: 400,
172
+ fontWeightMedium: 500,
173
+ fontWeightSemibold: 600,
174
+ fontWeightBold: 700
157
175
  };
158
176
  var darkThemeNew = { ...lightThemeNew };
159
177
  var lightTransparentThemeNew = { ...lightThemeNew };
@@ -323,7 +341,7 @@ function resolveDepositPriorityContext(accounts, chains, destination) {
323
341
  for (const account of accounts) {
324
342
  for (const wallet of account.wallets) {
325
343
  if (wallet.chain.name !== destinationChainName) continue;
326
- const source = wallet.sources.find(
344
+ const source = (wallet.sources ?? []).find(
327
345
  (candidate) => candidate.address.toLowerCase() === destinationTokenAddress
328
346
  );
329
347
  if (source) {
@@ -357,6 +375,11 @@ function getWalletAddress(wallet) {
357
375
  const address = wallet.name.trim();
358
376
  return address.length > 0 ? address : null;
359
377
  }
378
+ function truncateMiddle(address, head = 8, tail = 6) {
379
+ const trimmed = address.trim();
380
+ if (trimmed.length <= head + tail + 4) return trimmed;
381
+ return `${trimmed.slice(0, head)}...${trimmed.slice(-tail)}`;
382
+ }
360
383
  function getAddressableWallets(account) {
361
384
  return account.wallets.filter((wallet) => getWalletAddress(wallet) != null);
362
385
  }
@@ -364,7 +387,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
364
387
  const wallets = getAddressableWallets(account);
365
388
  if (wallets.length === 0) return null;
366
389
  const ranked = wallets.map((wallet, walletIndex) => {
367
- const bestSource = wallet.sources.map((source, sourceIndex) => ({
390
+ const bestSource = (wallet.sources ?? []).map((source, sourceIndex) => ({
368
391
  source,
369
392
  sourceIndex
370
393
  })).sort((a, b) => {
@@ -389,7 +412,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
389
412
  if (a.wallet.status !== b.wallet.status) {
390
413
  return a.wallet.status === "ACTIVE" ? -1 : 1;
391
414
  }
392
- const balanceDiff = b.wallet.balance.available.amount - a.wallet.balance.available.amount;
415
+ const balanceDiff = (b.wallet.balance?.available.amount ?? 0) - (a.wallet.balance?.available.amount ?? 0);
393
416
  if (balanceDiff !== 0) return balanceDiff;
394
417
  return a.walletIndex - b.walletIndex;
395
418
  });
@@ -405,7 +428,7 @@ function resolveDepositSelectionAfterRefresh(accounts, transferAmount, prev, pri
405
428
  const wallet = acct?.wallets.find((w) => w.id === selectedWalletId);
406
429
  if (wallet) {
407
430
  if (selectedTokenSymbol) {
408
- const hasToken = wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
431
+ const hasToken = wallet.sources == null || wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
409
432
  if (hasToken) {
410
433
  return {
411
434
  defaults: { accountId: selectedAccountId, walletId: selectedWalletId },
@@ -443,7 +466,9 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
443
466
  const eligibleAccounts = getDepositEligibleAccounts(accounts);
444
467
  if (eligibleAccounts.length === 0) return null;
445
468
  const accountHasSufficientBalanceWallet = (account) => getAddressableWallets(account).some(
446
- (wallet) => wallet.status === "ACTIVE" && wallet.sources.some((source) => source.balance.available.amount >= transferAmount)
469
+ (wallet) => wallet.status === "ACTIVE" && (wallet.sources ?? []).some(
470
+ (source) => source.balance.available.amount >= transferAmount
471
+ )
447
472
  );
448
473
  if (transferAmount <= 0 && selectedAccountId) {
449
474
  const selectedAccount = eligibleAccounts.find((account) => account.id === selectedAccountId);
@@ -465,7 +490,7 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
465
490
  transferAmount,
466
491
  priorityContext
467
492
  );
468
- const preferredSource = preferredWallet ? [...preferredWallet.sources].sort((a, b) => compareDepositSourcePriority(
493
+ const preferredSource = preferredWallet ? [...preferredWallet.sources ?? []].sort((a, b) => compareDepositSourcePriority(
469
494
  sourcePriorityInput(preferredWallet.chain.name, a, transferAmount, priorityContext),
470
495
  sourcePriorityInput(preferredWallet.chain.name, b, transferAmount, priorityContext)
471
496
  ))[0] : void 0;
@@ -531,7 +556,7 @@ function buildNativeUnsupportedEntries(options) {
531
556
  logoURI: option.logoURI ?? null
532
557
  })).filter((entry) => entry.amount > 0);
533
558
  }
534
- function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD) {
559
+ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD, includeUnfundedTokens = false) {
535
560
  const chainChoices = [];
536
561
  const chainIndexByName = /* @__PURE__ */ new Map();
537
562
  for (const option of options) {
@@ -572,7 +597,7 @@ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SE
572
597
  }
573
598
  }
574
599
  return chainChoices.map((chain) => {
575
- const visibleTokens = chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
600
+ const visibleTokens = includeUnfundedTokens ? chain.tokens : chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
576
601
  return {
577
602
  ...chain,
578
603
  balance: visibleTokens.reduce((sum, token) => sum + token.balance, 0),
@@ -1577,11 +1602,11 @@ function detachAccountChangeListeners() {
1577
1602
  accountChangeBinding = null;
1578
1603
  }
1579
1604
  function providerKey(selection) {
1580
- const providerName = selection.providerName?.trim();
1581
- if (!providerName) {
1605
+ const providerName2 = selection.providerName?.trim();
1606
+ if (!providerName2) {
1582
1607
  throw new Error("Solana wallet metadata is missing providerName.");
1583
1608
  }
1584
- return `${selection.providerId ?? ""}:${providerName.toLowerCase()}`;
1609
+ return `${selection.providerId ?? ""}:${providerName2.toLowerCase()}`;
1585
1610
  }
1586
1611
  function assertSupportedProvider(selection) {
1587
1612
  const name = selection.providerName?.trim().toLowerCase();
@@ -1960,464 +1985,197 @@ function screenForPhase(phase) {
1960
1985
  }
1961
1986
  }
1962
1987
 
1963
- // src/passkey-delegation.ts
1964
- var PasskeyIframeBlockedError = class extends Error {
1965
- constructor(message = "Passkey creation is not supported in this browser context.") {
1966
- super(message);
1967
- this.name = "PasskeyIframeBlockedError";
1988
+ // src/api.ts
1989
+ var api_exports = {};
1990
+ __export(api_exports, {
1991
+ createAccount: () => createAccount,
1992
+ createAccountAuthorizationSession: () => createAccountAuthorizationSession,
1993
+ createManualTransfer: () => createManualTransfer,
1994
+ createTransfer: () => createTransfer,
1995
+ fetchAccount: () => fetchAccount,
1996
+ fetchAccountBalances: () => fetchAccountBalances,
1997
+ fetchAccounts: () => fetchAccounts,
1998
+ fetchAuthorizationSession: () => fetchAuthorizationSession,
1999
+ fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
2000
+ fetchChains: () => fetchChains,
2001
+ fetchManualTransferSession: () => fetchManualTransferSession,
2002
+ fetchManualTransferSources: () => fetchManualTransferSources,
2003
+ fetchMerchantPublicKey: () => fetchMerchantPublicKey,
2004
+ fetchProviders: () => fetchProviders,
2005
+ fetchTransfer: () => fetchTransfer,
2006
+ fetchUserConfig: () => fetchUserConfig,
2007
+ postTransferQuote: () => postTransferQuote,
2008
+ probeActionCompletion: () => probeActionCompletion,
2009
+ refreshManualTransferQuote: () => refreshManualTransferQuote,
2010
+ regenerateTransferSignPayload: () => regenerateTransferSignPayload,
2011
+ registerPasskey: () => registerPasskey,
2012
+ reportActionCompletion: () => reportActionCompletion,
2013
+ reportPasskeyActivity: () => reportPasskeyActivity,
2014
+ setAuthorizationSessionPaymentIntentAmount: () => setAuthorizationSessionPaymentIntentAmount,
2015
+ setAuthorizationSessionProvider: () => setAuthorizationSessionProvider,
2016
+ signTransfer: () => signTransfer,
2017
+ updateManualTransferDepositTargetChain: () => updateManualTransferDepositTargetChain,
2018
+ updateUserConfig: () => updateUserConfig,
2019
+ updateUserConfigBySession: () => updateUserConfigBySession,
2020
+ waitForActionTransactionReceipt: () => waitForActionTransactionReceipt
2021
+ });
2022
+ var DEBUG_BUFFER_CAPACITY = 200;
2023
+ var nextId = 1;
2024
+ var entries = [];
2025
+ var listeners = /* @__PURE__ */ new Set();
2026
+ function notify() {
2027
+ for (const listener of listeners) {
2028
+ try {
2029
+ listener();
2030
+ } catch (err) {
2031
+ console.error("[blink-sdk][debug-log] listener threw:", err);
2032
+ }
1968
2033
  }
1969
- };
1970
- function isInCrossOriginIframe() {
1971
- if (typeof window === "undefined") return false;
1972
- if (window.parent === window) return false;
1973
- try {
1974
- void window.parent.location.origin;
1975
- return false;
1976
- } catch {
1977
- return true;
2034
+ }
2035
+ function appendDebug(level, message, data) {
2036
+ const entry = {
2037
+ id: nextId++,
2038
+ ts: Date.now(),
2039
+ level,
2040
+ message,
2041
+ data
2042
+ };
2043
+ const next = entries.length >= DEBUG_BUFFER_CAPACITY ? entries.slice(entries.length - DEBUG_BUFFER_CAPACITY + 1) : entries.slice();
2044
+ next.push(entry);
2045
+ entries = next;
2046
+ const prefix = "[blink-sdk][debug]";
2047
+ const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.info;
2048
+ if (data !== void 0) {
2049
+ sink(`${prefix} ${message}`, data);
2050
+ } else {
2051
+ sink(`${prefix} ${message}`);
1978
2052
  }
2053
+ notify();
1979
2054
  }
1980
- function isSafari() {
1981
- if (typeof navigator === "undefined") return false;
1982
- const ua = navigator.userAgent;
1983
- return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
2055
+ function subscribeDebug(listener) {
2056
+ listeners.add(listener);
2057
+ return () => {
2058
+ listeners.delete(listener);
2059
+ };
1984
2060
  }
1985
- var VERIFY_POPUP_TIMEOUT_MS = 6e4;
1986
- var POPUP_CLOSED_POLL_MS = 500;
1987
- var POPUP_CLOSED_GRACE_MS = 1e3;
1988
- function findDevicePasskeyViaPopup(options) {
1989
- return new Promise((resolve, reject) => {
1990
- const verificationToken = crypto.randomUUID();
1991
- const payload = {
1992
- ...options,
1993
- verificationToken
1994
- };
1995
- const encoded = btoa(JSON.stringify(payload));
1996
- const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
1997
- const popup = window.open(popupUrl, "blink-passkey-verify");
1998
- if (!popup) {
1999
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2000
- return;
2001
- }
2002
- let settled = false;
2003
- const timer = setTimeout(() => {
2004
- cleanup();
2005
- resolve(null);
2006
- }, VERIFY_POPUP_TIMEOUT_MS);
2007
- const closedPoll = setInterval(() => {
2008
- if (popup.closed && !settled) {
2009
- clearInterval(closedPoll);
2010
- setTimeout(() => {
2011
- if (!settled) {
2012
- settled = true;
2013
- cleanup();
2014
- checkServerForPasskeyByToken(
2015
- options.authToken,
2016
- options.apiBaseUrl,
2017
- verificationToken
2018
- ).then((result) => {
2019
- resolve(result?.credentialId ?? null);
2020
- }).catch(() => {
2021
- resolve(null);
2022
- });
2023
- }
2024
- }, POPUP_CLOSED_GRACE_MS);
2061
+ function getDebugEntries() {
2062
+ return entries;
2063
+ }
2064
+ function clearDebugEntries() {
2065
+ entries = [];
2066
+ notify();
2067
+ }
2068
+ function useBlinkDebugLog() {
2069
+ return react.useSyncExternalStore(subscribeDebug, getDebugEntries, getDebugEntries);
2070
+ }
2071
+
2072
+ // src/fetchWithRetry.ts
2073
+ var DEFAULT_MAX_RETRIES = 3;
2074
+ var DEFAULT_BASE_DELAY_MS = 500;
2075
+ var DEFAULT_MAX_JITTER_MS = 200;
2076
+ function isNetworkTypeError(err) {
2077
+ return err instanceof TypeError && /fetch|network|load failed/i.test(err.message);
2078
+ }
2079
+ async function fetchWithRetry(input, init, options) {
2080
+ const maxRetries = DEFAULT_MAX_RETRIES;
2081
+ const baseDelayMs = DEFAULT_BASE_DELAY_MS;
2082
+ const maxJitterMs = DEFAULT_MAX_JITTER_MS;
2083
+ const label = String(input).replace(/https?:\/\/[^/]+/, "");
2084
+ let lastError;
2085
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2086
+ try {
2087
+ return await fetch(input, init);
2088
+ } catch (err) {
2089
+ lastError = err;
2090
+ if (!isNetworkTypeError(err)) {
2091
+ throw err;
2092
+ }
2093
+ if (attempt < maxRetries) {
2094
+ const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * maxJitterMs;
2095
+ appendDebug("warn", `fetchWithRetry: network error, retrying ${label}`, {
2096
+ attempt: attempt + 1,
2097
+ maxRetries,
2098
+ delayMs: Math.round(delay),
2099
+ error: err instanceof Error ? err.message : String(err)
2100
+ });
2101
+ await new Promise((resolve) => setTimeout(resolve, delay));
2025
2102
  }
2026
- }, POPUP_CLOSED_POLL_MS);
2027
- function cleanup() {
2028
- clearTimeout(timer);
2029
- clearInterval(closedPoll);
2030
2103
  }
2104
+ }
2105
+ throw lastError;
2106
+ }
2107
+
2108
+ // src/apiError.ts
2109
+ var ApiError = class extends Error {
2110
+ status;
2111
+ code;
2112
+ constructor(status, code, message) {
2113
+ super(message);
2114
+ this.name = "ApiError";
2115
+ this.status = status;
2116
+ this.code = code;
2117
+ }
2118
+ };
2119
+ function isApiError(err) {
2120
+ if (err instanceof ApiError) return true;
2121
+ return typeof err === "object" && err !== null && "name" in err && err.name === "ApiError";
2122
+ }
2123
+ var SVM_SIGN_PAYLOAD_EXPIRED_CODE = "SVM_SIGN_PAYLOAD_EXPIRED";
2124
+ function isSvmSignExpiredError(err) {
2125
+ return isApiError(err) && err.code === SVM_SIGN_PAYLOAD_EXPIRED_CODE;
2126
+ }
2127
+
2128
+ // src/api.ts
2129
+ async function throwApiError(res) {
2130
+ const body = await res.json().catch(() => null);
2131
+ const detail = body?.error ?? body;
2132
+ const msg = detail?.message ?? res.statusText;
2133
+ const code = detail?.code ?? String(res.status);
2134
+ throw new ApiError(res.status, code, `${res.status} \u2014 ${code}: ${msg}`);
2135
+ }
2136
+ async function fetchProviders(apiBaseUrl, token) {
2137
+ const headers = {};
2138
+ if (token) {
2139
+ headers.Authorization = `Bearer ${token}`;
2140
+ }
2141
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/providers`, { headers });
2142
+ if (!res.ok) await throwApiError(res);
2143
+ const data = await res.json();
2144
+ return data.items;
2145
+ }
2146
+ async function fetchChains(apiBaseUrl, token) {
2147
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/chains`, {
2148
+ headers: { Authorization: `Bearer ${token}` }
2031
2149
  });
2150
+ if (!res.ok) await throwApiError(res);
2151
+ const data = await res.json();
2152
+ return data.items;
2032
2153
  }
2033
- async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
2034
- if (!authToken || !apiBaseUrl) return null;
2035
- const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
2036
- headers: { Authorization: `Bearer ${authToken}` }
2154
+ async function fetchAccounts(apiBaseUrl, token, credentialId) {
2155
+ const params = new URLSearchParams({ credentialId });
2156
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts?${params.toString()}`, {
2157
+ headers: { Authorization: `Bearer ${token}` }
2037
2158
  });
2038
- if (!res.ok) return null;
2039
- const body = await res.json();
2040
- const passkeys = body.config.passkeys ?? [];
2041
- const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
2042
- return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
2159
+ if (!res.ok) await throwApiError(res);
2160
+ const data = await res.json();
2161
+ return data.items;
2043
2162
  }
2044
- function shouldUsePasskeySignupPopup() {
2045
- return isSafari() && isInCrossOriginIframe();
2163
+ async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
2164
+ const params = new URLSearchParams({ credentialId });
2165
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts/${accountId}?${params.toString()}`, {
2166
+ headers: { Authorization: `Bearer ${token}` }
2167
+ });
2168
+ if (!res.ok) await throwApiError(res);
2169
+ return await res.json();
2046
2170
  }
2047
- var SIGNUP_POPUP_TIMEOUT_MS = 12e4;
2048
- function signupWithPasskeyViaPopup() {
2049
- return new Promise((resolve, reject) => {
2050
- const popupUrl = `${window.location.origin}/passkey-signup`;
2051
- const popup = window.open(popupUrl, "blink-passkey-signup");
2052
- if (!popup) {
2053
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2054
- return;
2055
- }
2056
- let settled = false;
2057
- const timer = setTimeout(() => {
2058
- cleanup();
2059
- resolve(null);
2060
- }, SIGNUP_POPUP_TIMEOUT_MS);
2061
- function onMessage(event) {
2062
- if (event.origin !== window.location.origin) return;
2063
- if (event.source !== popup) return;
2064
- const data = event.data;
2065
- if (!data || data.type !== "blink:passkey-signup-complete") return;
2066
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2067
- settled = true;
2068
- cleanup();
2069
- resolve({
2070
- accessToken: data.accessToken,
2071
- credentialId: data.credentialId,
2072
- publicKey: data.publicKey
2073
- });
2074
- }
2075
- window.addEventListener("message", onMessage);
2076
- const closedPoll = setInterval(() => {
2077
- if (popup.closed && !settled) {
2078
- settled = true;
2079
- cleanup();
2080
- resolve(null);
2081
- }
2082
- }, POPUP_CLOSED_POLL_MS);
2083
- function cleanup() {
2084
- clearTimeout(timer);
2085
- clearInterval(closedPoll);
2086
- window.removeEventListener("message", onMessage);
2087
- }
2088
- });
2089
- }
2090
- var LOGIN_POPUP_TIMEOUT_MS = 12e4;
2091
- function loginWithPasskeyViaPopup() {
2092
- return new Promise((resolve, reject) => {
2093
- const popupUrl = `${window.location.origin}/passkey-login`;
2094
- const popup = window.open(popupUrl, "blink-passkey-login");
2095
- if (!popup) {
2096
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2097
- return;
2098
- }
2099
- let settled = false;
2100
- const timer = setTimeout(() => {
2101
- cleanup();
2102
- resolve(null);
2103
- }, LOGIN_POPUP_TIMEOUT_MS);
2104
- function onMessage(event) {
2105
- if (event.origin !== window.location.origin) return;
2106
- if (event.source !== popup) return;
2107
- const data = event.data;
2108
- if (!data || data.type !== "blink:passkey-login-complete") return;
2109
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2110
- settled = true;
2111
- cleanup();
2112
- resolve({
2113
- accessToken: data.accessToken,
2114
- credentialId: data.credentialId,
2115
- publicKey: data.publicKey
2116
- });
2117
- }
2118
- window.addEventListener("message", onMessage);
2119
- const closedPoll = setInterval(() => {
2120
- if (popup.closed && !settled) {
2121
- settled = true;
2122
- cleanup();
2123
- resolve(null);
2124
- }
2125
- }, POPUP_CLOSED_POLL_MS);
2126
- function cleanup() {
2127
- clearTimeout(timer);
2128
- clearInterval(closedPoll);
2129
- window.removeEventListener("message", onMessage);
2130
- }
2131
- });
2132
- }
2133
-
2134
- // src/credentialIdEncoding.ts
2135
- function credentialIdBase64ToBytes(value) {
2136
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2137
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
2138
- const raw = atob(padded);
2139
- const bytes = new Uint8Array(raw.length);
2140
- for (let i = 0; i < raw.length; i++) {
2141
- bytes[i] = raw.charCodeAt(i);
2142
- }
2143
- return bytes;
2144
- }
2145
-
2146
- // src/passkeyRpId.ts
2147
- function normalizeConfiguredDomain(value) {
2148
- return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2149
- }
2150
- function resolveRootDomainFromHostname(hostname) {
2151
- const trimmedHostname = hostname.trim().toLowerCase();
2152
- if (!trimmedHostname) {
2153
- return "localhost";
2154
- }
2155
- if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
2156
- return trimmedHostname;
2157
- }
2158
- const parts = trimmedHostname.split(".").filter(Boolean);
2159
- if (parts.length < 2) {
2160
- return trimmedHostname;
2161
- }
2162
- return parts.slice(-2).join(".");
2163
- }
2164
-
2165
- // src/hooks/passkeyPublic.ts
2166
- function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
2167
- return new Promise((resolve) => {
2168
- if (typeof document === "undefined") {
2169
- resolve();
2170
- return;
2171
- }
2172
- if (document.hasFocus()) {
2173
- resolve();
2174
- return;
2175
- }
2176
- const deadline = Date.now() + timeoutMs;
2177
- const timer = setInterval(() => {
2178
- if (document.hasFocus()) {
2179
- clearInterval(timer);
2180
- resolve();
2181
- } else if (Date.now() >= deadline) {
2182
- clearInterval(timer);
2183
- resolve();
2184
- }
2185
- }, intervalMs);
2186
- });
2187
- }
2188
- function toBase64(buffer) {
2189
- return btoa(String.fromCharCode(...new Uint8Array(buffer)));
2190
- }
2191
- function readEnvValue(name) {
2192
- const meta = ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) });
2193
- const metaValue = meta.env?.[name];
2194
- if (typeof metaValue === "string" && metaValue.trim().length > 0) {
2195
- return metaValue.trim();
2196
- }
2197
- const processValue = globalThis.process?.env?.[name];
2198
- if (typeof processValue === "string" && processValue.trim().length > 0) {
2199
- return processValue.trim();
2200
- }
2201
- return void 0;
2202
- }
2203
- function resolvePasskeyRpId() {
2204
- const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
2205
- if (configuredDomain) {
2206
- return normalizeConfiguredDomain(configuredDomain);
2207
- }
2208
- if (typeof window !== "undefined") {
2209
- return resolveRootDomainFromHostname(window.location.hostname);
2210
- }
2211
- return "localhost";
2212
- }
2213
- async function deviceHasPasskey(credentialId) {
2214
- const found = await findDevicePasskey([credentialId]);
2215
- return found != null;
2216
- }
2217
- async function findDevicePasskey(credentialIds) {
2218
- if (credentialIds.length === 0) return null;
2219
- try {
2220
- const challenge = new Uint8Array(32);
2221
- crypto.getRandomValues(challenge);
2222
- await waitForDocumentFocus();
2223
- const assertion = await navigator.credentials.get({
2224
- publicKey: {
2225
- challenge,
2226
- rpId: resolvePasskeyRpId(),
2227
- allowCredentials: credentialIds.map((id) => ({
2228
- type: "public-key",
2229
- id: credentialIdBase64ToBytes(id)
2230
- })),
2231
- userVerification: "discouraged",
2232
- timeout: 3e4
2233
- }
2234
- });
2235
- if (!assertion) return null;
2236
- return toBase64(assertion.rawId);
2237
- } catch {
2238
- return null;
2239
- }
2240
- }
2241
-
2242
- // src/api.ts
2243
- var api_exports = {};
2244
- __export(api_exports, {
2245
- createAccount: () => createAccount,
2246
- createAccountAuthorizationSession: () => createAccountAuthorizationSession,
2247
- createManualTransfer: () => createManualTransfer,
2248
- createTransfer: () => createTransfer,
2249
- fetchAccount: () => fetchAccount,
2250
- fetchAccounts: () => fetchAccounts,
2251
- fetchAuthorizationSession: () => fetchAuthorizationSession,
2252
- fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
2253
- fetchChains: () => fetchChains,
2254
- fetchManualTransferSession: () => fetchManualTransferSession,
2255
- fetchManualTransferSources: () => fetchManualTransferSources,
2256
- fetchMerchantPublicKey: () => fetchMerchantPublicKey,
2257
- fetchProviders: () => fetchProviders,
2258
- fetchTransfer: () => fetchTransfer,
2259
- fetchUserConfig: () => fetchUserConfig,
2260
- postTransferQuote: () => postTransferQuote,
2261
- probeActionCompletion: () => probeActionCompletion,
2262
- refreshManualTransferQuote: () => refreshManualTransferQuote,
2263
- regenerateTransferSignPayload: () => regenerateTransferSignPayload,
2264
- registerPasskey: () => registerPasskey,
2265
- reportActionCompletion: () => reportActionCompletion,
2266
- reportPasskeyActivity: () => reportPasskeyActivity,
2267
- setAuthorizationSessionPaymentIntentAmount: () => setAuthorizationSessionPaymentIntentAmount,
2268
- setAuthorizationSessionProvider: () => setAuthorizationSessionProvider,
2269
- signTransfer: () => signTransfer,
2270
- updateManualTransferDepositTargetChain: () => updateManualTransferDepositTargetChain,
2271
- updateUserConfig: () => updateUserConfig,
2272
- updateUserConfigBySession: () => updateUserConfigBySession,
2273
- waitForActionTransactionReceipt: () => waitForActionTransactionReceipt
2274
- });
2275
- var DEBUG_BUFFER_CAPACITY = 200;
2276
- var nextId = 1;
2277
- var entries = [];
2278
- var listeners = /* @__PURE__ */ new Set();
2279
- function notify() {
2280
- for (const listener of listeners) {
2281
- try {
2282
- listener();
2283
- } catch (err) {
2284
- console.error("[blink-sdk][debug-log] listener threw:", err);
2285
- }
2286
- }
2287
- }
2288
- function appendDebug(level, message, data) {
2289
- const entry = {
2290
- id: nextId++,
2291
- ts: Date.now(),
2292
- level,
2293
- message,
2294
- data
2295
- };
2296
- const next = entries.length >= DEBUG_BUFFER_CAPACITY ? entries.slice(entries.length - DEBUG_BUFFER_CAPACITY + 1) : entries.slice();
2297
- next.push(entry);
2298
- entries = next;
2299
- const prefix = "[blink-sdk][debug]";
2300
- const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.info;
2301
- if (data !== void 0) {
2302
- sink(`${prefix} ${message}`, data);
2303
- } else {
2304
- sink(`${prefix} ${message}`);
2305
- }
2306
- notify();
2307
- }
2308
- function subscribeDebug(listener) {
2309
- listeners.add(listener);
2310
- return () => {
2311
- listeners.delete(listener);
2312
- };
2313
- }
2314
- function getDebugEntries() {
2315
- return entries;
2316
- }
2317
- function clearDebugEntries() {
2318
- entries = [];
2319
- notify();
2320
- }
2321
- function useBlinkDebugLog() {
2322
- return react.useSyncExternalStore(subscribeDebug, getDebugEntries, getDebugEntries);
2323
- }
2324
-
2325
- // src/fetchWithRetry.ts
2326
- var DEFAULT_MAX_RETRIES = 3;
2327
- var DEFAULT_BASE_DELAY_MS = 500;
2328
- var DEFAULT_MAX_JITTER_MS = 200;
2329
- function isNetworkTypeError(err) {
2330
- return err instanceof TypeError && /fetch|network|load failed/i.test(err.message);
2331
- }
2332
- async function fetchWithRetry(input, init, options) {
2333
- const maxRetries = DEFAULT_MAX_RETRIES;
2334
- const baseDelayMs = DEFAULT_BASE_DELAY_MS;
2335
- const maxJitterMs = DEFAULT_MAX_JITTER_MS;
2336
- const label = String(input).replace(/https?:\/\/[^/]+/, "");
2337
- let lastError;
2338
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
2339
- try {
2340
- return await fetch(input, init);
2341
- } catch (err) {
2342
- lastError = err;
2343
- if (!isNetworkTypeError(err)) {
2344
- throw err;
2345
- }
2346
- if (attempt < maxRetries) {
2347
- const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * maxJitterMs;
2348
- appendDebug("warn", `fetchWithRetry: network error, retrying ${label}`, {
2349
- attempt: attempt + 1,
2350
- maxRetries,
2351
- delayMs: Math.round(delay),
2352
- error: err instanceof Error ? err.message : String(err)
2353
- });
2354
- await new Promise((resolve) => setTimeout(resolve, delay));
2355
- }
2356
- }
2357
- }
2358
- throw lastError;
2359
- }
2360
-
2361
- // src/apiError.ts
2362
- var ApiError = class extends Error {
2363
- status;
2364
- code;
2365
- constructor(status, code, message) {
2366
- super(message);
2367
- this.name = "ApiError";
2368
- this.status = status;
2369
- this.code = code;
2370
- }
2371
- };
2372
- function isApiError(err) {
2373
- if (err instanceof ApiError) return true;
2374
- return typeof err === "object" && err !== null && "name" in err && err.name === "ApiError";
2375
- }
2376
- var SVM_SIGN_PAYLOAD_EXPIRED_CODE = "SVM_SIGN_PAYLOAD_EXPIRED";
2377
- function isSvmSignExpiredError(err) {
2378
- return isApiError(err) && err.code === SVM_SIGN_PAYLOAD_EXPIRED_CODE;
2379
- }
2380
-
2381
- // src/api.ts
2382
- async function throwApiError(res) {
2383
- const body = await res.json().catch(() => null);
2384
- const detail = body?.error ?? body;
2385
- const msg = detail?.message ?? res.statusText;
2386
- const code = detail?.code ?? String(res.status);
2387
- throw new ApiError(res.status, code, `${res.status} \u2014 ${code}: ${msg}`);
2388
- }
2389
- async function fetchProviders(apiBaseUrl, token) {
2390
- const headers = {};
2391
- if (token) {
2392
- headers.Authorization = `Bearer ${token}`;
2393
- }
2394
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/providers`, { headers });
2395
- if (!res.ok) await throwApiError(res);
2396
- const data = await res.json();
2397
- return data.items;
2398
- }
2399
- async function fetchChains(apiBaseUrl, token) {
2400
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/chains`, {
2401
- headers: { Authorization: `Bearer ${token}` }
2402
- });
2403
- if (!res.ok) await throwApiError(res);
2404
- const data = await res.json();
2405
- return data.items;
2406
- }
2407
- async function fetchAccounts(apiBaseUrl, token, credentialId) {
2408
- const params = new URLSearchParams({ credentialId });
2409
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts?${params.toString()}`, {
2410
- headers: { Authorization: `Bearer ${token}` }
2411
- });
2412
- if (!res.ok) await throwApiError(res);
2413
- const data = await res.json();
2414
- return data.items;
2415
- }
2416
- async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
2417
- const params = new URLSearchParams({ credentialId });
2418
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts/${accountId}?${params.toString()}`, {
2419
- headers: { Authorization: `Bearer ${token}` }
2420
- });
2171
+ async function fetchAccountBalances(apiBaseUrl, token, accountId, credentialId) {
2172
+ const params = new URLSearchParams({ credentialId });
2173
+ const res = await fetchWithRetry(
2174
+ `${apiBaseUrl}/v1/accounts/${accountId}/balances?${params.toString()}`,
2175
+ {
2176
+ headers: { Authorization: `Bearer ${token}` }
2177
+ }
2178
+ );
2421
2179
  if (!res.ok) await throwApiError(res);
2422
2180
  return await res.json();
2423
2181
  }
@@ -2738,44 +2496,323 @@ async function waitForActionTransactionReceipt(apiBaseUrl, actionId, txHash) {
2738
2496
  headers: { "Content-Type": "application/json" },
2739
2497
  body: JSON.stringify({ txHash })
2740
2498
  }
2741
- );
2742
- if (!res.ok) await throwApiError(res);
2743
- return await res.json();
2499
+ );
2500
+ if (!res.ok) await throwApiError(res);
2501
+ return await res.json();
2502
+ }
2503
+ async function probeActionCompletion(apiBaseUrl, actionId) {
2504
+ const res = await fetchWithRetry(
2505
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2506
+ {
2507
+ method: "PATCH",
2508
+ headers: { "Content-Type": "application/json" },
2509
+ body: JSON.stringify({ status: "COMPLETED", result: {} })
2510
+ }
2511
+ );
2512
+ if (res.ok) {
2513
+ const session = await res.json();
2514
+ return { detected: true, session };
2515
+ }
2516
+ const body = await res.json().catch(() => null);
2517
+ const detail = body?.error ?? body;
2518
+ const code = detail?.code;
2519
+ const message = detail?.message ?? res.statusText ?? `HTTP ${res.status}`;
2520
+ if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
2521
+ return { detected: false, reason: "not-found", status: res.status, code, message };
2522
+ }
2523
+ const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
2524
+ "APPROVE_NOT_DETECTED",
2525
+ "APPROVE_SPL_NOT_DETECTED",
2526
+ "SPL_DELEGATE_MISSING",
2527
+ "SPL_DELEGATE_INSUFFICIENT",
2528
+ "SPL_DELEGATE_WRONG_OWNER"
2529
+ ]);
2530
+ if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
2531
+ return { detected: false, reason: "not-found", status: res.status, code, message };
2532
+ }
2533
+ if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
2534
+ return { detected: false, reason: "invalid-state", status: res.status, code, message };
2535
+ }
2536
+ return { detected: false, reason: "error", status: res.status, code, message };
2537
+ }
2538
+
2539
+ // src/passkey-delegation.ts
2540
+ var PasskeyIframeBlockedError = class extends Error {
2541
+ constructor(message = "Passkey creation is not supported in this browser context.") {
2542
+ super(message);
2543
+ this.name = "PasskeyIframeBlockedError";
2544
+ }
2545
+ };
2546
+ function isInCrossOriginIframe() {
2547
+ if (typeof window === "undefined") return false;
2548
+ if (window.parent === window) return false;
2549
+ try {
2550
+ void window.parent.location.origin;
2551
+ return false;
2552
+ } catch {
2553
+ return true;
2554
+ }
2555
+ }
2556
+ function isSafari() {
2557
+ if (typeof navigator === "undefined") return false;
2558
+ const ua = navigator.userAgent;
2559
+ return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
2560
+ }
2561
+ var VERIFY_POPUP_TIMEOUT_MS = 6e4;
2562
+ var POPUP_CLOSED_POLL_MS = 500;
2563
+ var POPUP_CLOSED_GRACE_MS = 1e3;
2564
+ function findDevicePasskeyViaPopup(options) {
2565
+ return new Promise((resolve, reject) => {
2566
+ const verificationToken = crypto.randomUUID();
2567
+ const payload = {
2568
+ ...options,
2569
+ verificationToken
2570
+ };
2571
+ const encoded = btoa(JSON.stringify(payload));
2572
+ const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
2573
+ const popup = window.open(popupUrl, "blink-passkey-verify");
2574
+ if (!popup) {
2575
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2576
+ return;
2577
+ }
2578
+ let settled = false;
2579
+ const timer = setTimeout(() => {
2580
+ cleanup();
2581
+ resolve(null);
2582
+ }, VERIFY_POPUP_TIMEOUT_MS);
2583
+ const closedPoll = setInterval(() => {
2584
+ if (popup.closed && !settled) {
2585
+ clearInterval(closedPoll);
2586
+ setTimeout(() => {
2587
+ if (!settled) {
2588
+ settled = true;
2589
+ cleanup();
2590
+ checkServerForPasskeyByToken(
2591
+ options.authToken,
2592
+ options.apiBaseUrl,
2593
+ verificationToken
2594
+ ).then((result) => {
2595
+ resolve(result?.credentialId ?? null);
2596
+ }).catch(() => {
2597
+ resolve(null);
2598
+ });
2599
+ }
2600
+ }, POPUP_CLOSED_GRACE_MS);
2601
+ }
2602
+ }, POPUP_CLOSED_POLL_MS);
2603
+ function cleanup() {
2604
+ clearTimeout(timer);
2605
+ clearInterval(closedPoll);
2606
+ }
2607
+ });
2608
+ }
2609
+ async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
2610
+ if (!authToken || !apiBaseUrl) return null;
2611
+ const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
2612
+ headers: { Authorization: `Bearer ${authToken}` }
2613
+ });
2614
+ if (!res.ok) return null;
2615
+ const body = await res.json();
2616
+ const passkeys = body.config.passkeys ?? [];
2617
+ const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
2618
+ return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
2619
+ }
2620
+ function shouldUsePasskeySignupPopup() {
2621
+ return isSafari() && isInCrossOriginIframe();
2622
+ }
2623
+ var SIGNUP_POPUP_TIMEOUT_MS = 12e4;
2624
+ function signupWithPasskeyViaPopup() {
2625
+ return new Promise((resolve, reject) => {
2626
+ const popupUrl = `${window.location.origin}/passkey-signup`;
2627
+ const popup = window.open(popupUrl, "blink-passkey-signup");
2628
+ if (!popup) {
2629
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2630
+ return;
2631
+ }
2632
+ let settled = false;
2633
+ const timer = setTimeout(() => {
2634
+ cleanup();
2635
+ resolve(null);
2636
+ }, SIGNUP_POPUP_TIMEOUT_MS);
2637
+ function onMessage(event) {
2638
+ if (event.origin !== window.location.origin) return;
2639
+ if (event.source !== popup) return;
2640
+ const data = event.data;
2641
+ if (!data || data.type !== "blink:passkey-signup-complete") return;
2642
+ if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2643
+ settled = true;
2644
+ cleanup();
2645
+ resolve({
2646
+ accessToken: data.accessToken,
2647
+ credentialId: data.credentialId,
2648
+ publicKey: data.publicKey
2649
+ });
2650
+ }
2651
+ window.addEventListener("message", onMessage);
2652
+ const closedPoll = setInterval(() => {
2653
+ if (popup.closed && !settled) {
2654
+ settled = true;
2655
+ cleanup();
2656
+ resolve(null);
2657
+ }
2658
+ }, POPUP_CLOSED_POLL_MS);
2659
+ function cleanup() {
2660
+ clearTimeout(timer);
2661
+ clearInterval(closedPoll);
2662
+ window.removeEventListener("message", onMessage);
2663
+ }
2664
+ });
2665
+ }
2666
+ var LOGIN_POPUP_TIMEOUT_MS = 12e4;
2667
+ function loginWithPasskeyViaPopup() {
2668
+ return new Promise((resolve, reject) => {
2669
+ const popupUrl = `${window.location.origin}/passkey-login`;
2670
+ const popup = window.open(popupUrl, "blink-passkey-login");
2671
+ if (!popup) {
2672
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2673
+ return;
2674
+ }
2675
+ let settled = false;
2676
+ const timer = setTimeout(() => {
2677
+ cleanup();
2678
+ resolve(null);
2679
+ }, LOGIN_POPUP_TIMEOUT_MS);
2680
+ function onMessage(event) {
2681
+ if (event.origin !== window.location.origin) return;
2682
+ if (event.source !== popup) return;
2683
+ const data = event.data;
2684
+ if (!data || data.type !== "blink:passkey-login-complete") return;
2685
+ if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2686
+ settled = true;
2687
+ cleanup();
2688
+ resolve({
2689
+ accessToken: data.accessToken,
2690
+ credentialId: data.credentialId,
2691
+ publicKey: data.publicKey
2692
+ });
2693
+ }
2694
+ window.addEventListener("message", onMessage);
2695
+ const closedPoll = setInterval(() => {
2696
+ if (popup.closed && !settled) {
2697
+ settled = true;
2698
+ cleanup();
2699
+ resolve(null);
2700
+ }
2701
+ }, POPUP_CLOSED_POLL_MS);
2702
+ function cleanup() {
2703
+ clearTimeout(timer);
2704
+ clearInterval(closedPoll);
2705
+ window.removeEventListener("message", onMessage);
2706
+ }
2707
+ });
2708
+ }
2709
+
2710
+ // src/credentialIdEncoding.ts
2711
+ function credentialIdBase64ToBytes(value) {
2712
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2713
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
2714
+ const raw = atob(padded);
2715
+ const bytes = new Uint8Array(raw.length);
2716
+ for (let i = 0; i < raw.length; i++) {
2717
+ bytes[i] = raw.charCodeAt(i);
2718
+ }
2719
+ return bytes;
2720
+ }
2721
+
2722
+ // src/passkeyRpId.ts
2723
+ function normalizeConfiguredDomain(value) {
2724
+ return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2725
+ }
2726
+ function resolveRootDomainFromHostname(hostname) {
2727
+ const trimmedHostname = hostname.trim().toLowerCase();
2728
+ if (!trimmedHostname) {
2729
+ return "localhost";
2730
+ }
2731
+ if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
2732
+ return trimmedHostname;
2733
+ }
2734
+ const parts = trimmedHostname.split(".").filter(Boolean);
2735
+ if (parts.length < 2) {
2736
+ return trimmedHostname;
2737
+ }
2738
+ return parts.slice(-2).join(".");
2739
+ }
2740
+
2741
+ // src/hooks/passkeyPublic.ts
2742
+ function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
2743
+ return new Promise((resolve) => {
2744
+ if (typeof document === "undefined") {
2745
+ resolve();
2746
+ return;
2747
+ }
2748
+ if (document.hasFocus()) {
2749
+ resolve();
2750
+ return;
2751
+ }
2752
+ const deadline = Date.now() + timeoutMs;
2753
+ const timer = setInterval(() => {
2754
+ if (document.hasFocus()) {
2755
+ clearInterval(timer);
2756
+ resolve();
2757
+ } else if (Date.now() >= deadline) {
2758
+ clearInterval(timer);
2759
+ resolve();
2760
+ }
2761
+ }, intervalMs);
2762
+ });
2744
2763
  }
2745
- async function probeActionCompletion(apiBaseUrl, actionId) {
2746
- const res = await fetchWithRetry(
2747
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2748
- {
2749
- method: "PATCH",
2750
- headers: { "Content-Type": "application/json" },
2751
- body: JSON.stringify({ status: "COMPLETED", result: {} })
2752
- }
2753
- );
2754
- if (res.ok) {
2755
- const session = await res.json();
2756
- return { detected: true, session };
2764
+ function toBase64(buffer) {
2765
+ return btoa(String.fromCharCode(...new Uint8Array(buffer)));
2766
+ }
2767
+ function readEnvValue(name) {
2768
+ const meta = ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) });
2769
+ const metaValue = meta.env?.[name];
2770
+ if (typeof metaValue === "string" && metaValue.trim().length > 0) {
2771
+ return metaValue.trim();
2757
2772
  }
2758
- const body = await res.json().catch(() => null);
2759
- const detail = body?.error ?? body;
2760
- const code = detail?.code;
2761
- const message = detail?.message ?? res.statusText ?? `HTTP ${res.status}`;
2762
- if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
2763
- return { detected: false, reason: "not-found", status: res.status, code, message };
2773
+ const processValue = globalThis.process?.env?.[name];
2774
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
2775
+ return processValue.trim();
2764
2776
  }
2765
- const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
2766
- "APPROVE_NOT_DETECTED",
2767
- "APPROVE_SPL_NOT_DETECTED",
2768
- "SPL_DELEGATE_MISSING",
2769
- "SPL_DELEGATE_INSUFFICIENT",
2770
- "SPL_DELEGATE_WRONG_OWNER"
2771
- ]);
2772
- if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
2773
- return { detected: false, reason: "not-found", status: res.status, code, message };
2777
+ return void 0;
2778
+ }
2779
+ function resolvePasskeyRpId() {
2780
+ const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
2781
+ if (configuredDomain) {
2782
+ return normalizeConfiguredDomain(configuredDomain);
2774
2783
  }
2775
- if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
2776
- return { detected: false, reason: "invalid-state", status: res.status, code, message };
2784
+ if (typeof window !== "undefined") {
2785
+ return resolveRootDomainFromHostname(window.location.hostname);
2786
+ }
2787
+ return "localhost";
2788
+ }
2789
+ async function deviceHasPasskey(credentialId) {
2790
+ const found = await findDevicePasskey([credentialId]);
2791
+ return found != null;
2792
+ }
2793
+ async function findDevicePasskey(credentialIds) {
2794
+ if (credentialIds.length === 0) return null;
2795
+ try {
2796
+ const challenge = new Uint8Array(32);
2797
+ crypto.getRandomValues(challenge);
2798
+ await waitForDocumentFocus();
2799
+ const assertion = await navigator.credentials.get({
2800
+ publicKey: {
2801
+ challenge,
2802
+ rpId: resolvePasskeyRpId(),
2803
+ allowCredentials: credentialIds.map((id) => ({
2804
+ type: "public-key",
2805
+ id: credentialIdBase64ToBytes(id)
2806
+ })),
2807
+ userVerification: "discouraged",
2808
+ timeout: 3e4
2809
+ }
2810
+ });
2811
+ if (!assertion) return null;
2812
+ return toBase64(assertion.rawId);
2813
+ } catch {
2814
+ return null;
2777
2815
  }
2778
- return { detected: false, reason: "error", status: res.status, code, message };
2779
2816
  }
2780
2817
 
2781
2818
  // src/transferPolling.ts
@@ -4149,14 +4186,14 @@ function isBatchableAction(action) {
4149
4186
  return BATCHABLE_ACTION_TYPES.has(action.type);
4150
4187
  }
4151
4188
  function getSolanaWalletSelection(action) {
4152
- const providerName = action.metadata?.providerName;
4153
- if (typeof providerName !== "string" || providerName.trim() === "") {
4189
+ const providerName2 = action.metadata?.providerName;
4190
+ if (typeof providerName2 !== "string" || providerName2.trim() === "") {
4154
4191
  throw new Error(`${action.type} metadata is missing providerName.`);
4155
4192
  }
4156
4193
  const providerId = action.metadata?.providerId;
4157
4194
  return {
4158
4195
  ...typeof providerId === "string" && providerId.trim() !== "" ? { providerId } : {},
4159
- providerName
4196
+ providerName: providerName2
4160
4197
  };
4161
4198
  }
4162
4199
  function isPhantomEvmProviderAvailable() {
@@ -5806,6 +5843,43 @@ function useAuthorizationExecutor(options) {
5806
5843
  }),
5807
5844
  []
5808
5845
  );
5846
+ const [awaitingApproval, setAwaitingApproval] = react.useState(false);
5847
+ const [approvalDestinationAddress, setApprovalDestinationAddress] = react.useState(null);
5848
+ const approvalResolverRef = react.useRef(null);
5849
+ const approvalRejectRef = react.useRef(null);
5850
+ const approvalInitiatedRef = react.useRef(false);
5851
+ const waitForApproval = react.useCallback(
5852
+ (action, destinationAddress) => {
5853
+ if (approvalInitiatedRef.current) return Promise.resolve();
5854
+ return new Promise((resolve, reject) => {
5855
+ approvalResolverRef.current = resolve;
5856
+ approvalRejectRef.current = reject;
5857
+ setCurrentAction(action);
5858
+ setApprovalDestinationAddress(destinationAddress);
5859
+ setAwaitingApproval(true);
5860
+ });
5861
+ },
5862
+ []
5863
+ );
5864
+ const approveAuthorization = react.useCallback(() => {
5865
+ approvalInitiatedRef.current = true;
5866
+ setAwaitingApproval(false);
5867
+ if (approvalResolverRef.current) {
5868
+ approvalResolverRef.current();
5869
+ approvalResolverRef.current = null;
5870
+ approvalRejectRef.current = null;
5871
+ }
5872
+ }, []);
5873
+ const resetApprovalGate = react.useCallback(() => {
5874
+ approvalInitiatedRef.current = false;
5875
+ setAwaitingApproval(false);
5876
+ setApprovalDestinationAddress(null);
5877
+ if (approvalRejectRef.current) {
5878
+ approvalRejectRef.current(new AuthorizationSessionCancelledError());
5879
+ approvalRejectRef.current = null;
5880
+ approvalResolverRef.current = null;
5881
+ }
5882
+ }, []);
5809
5883
  const cancelPendingExecution = react.useCallback(() => {
5810
5884
  if (selectSourceRejectRef.current) {
5811
5885
  selectSourceRejectRef.current(new AuthorizationSessionCancelledError());
@@ -5813,6 +5887,12 @@ function useAuthorizationExecutor(options) {
5813
5887
  selectSourceResolverRef.current = null;
5814
5888
  setPendingSelectSource(null);
5815
5889
  }
5890
+ if (approvalRejectRef.current) {
5891
+ approvalRejectRef.current(new AuthorizationSessionCancelledError());
5892
+ approvalRejectRef.current = null;
5893
+ approvalResolverRef.current = null;
5894
+ }
5895
+ setAwaitingApproval(false);
5816
5896
  setError(null);
5817
5897
  setCurrentAction(null);
5818
5898
  setApproveSplConfirming(null);
@@ -6215,6 +6295,11 @@ function useAuthorizationExecutor(options) {
6215
6295
  currentAction,
6216
6296
  approveSplConfirming,
6217
6297
  pendingSelectSource,
6298
+ awaitingApproval,
6299
+ approvalDestinationAddress,
6300
+ waitForApproval,
6301
+ approveAuthorization,
6302
+ resetApprovalGate,
6218
6303
  resolveSelectSource,
6219
6304
  cancelPendingExecution,
6220
6305
  resetWalletConnect,
@@ -6509,6 +6594,7 @@ function useAuthorizationOrchestrator(deps) {
6509
6594
  const [pendingSelectSourceAction, setPendingSelectSourceAction] = react.useState(null);
6510
6595
  const [orchestratorCompleted, setOrchestratorCompleted] = react.useState(false);
6511
6596
  const [sourceSelectionResolved, setSourceSelectionResolved] = react.useState(false);
6597
+ const [approvalSmartAccountAddress, setApprovalSmartAccountAddress] = react.useState(null);
6512
6598
  const selectSourceResolverRef = react.useRef(null);
6513
6599
  const selectSourceRejectRef = react.useRef(null);
6514
6600
  const submittedApprovePermit2ActionIdsRef = react.useRef(/* @__PURE__ */ new Set());
@@ -6534,6 +6620,7 @@ function useAuthorizationOrchestrator(deps) {
6534
6620
  const cancellation = new AuthorizationSessionCancelledError();
6535
6621
  setOrchestratorCompleted(false);
6536
6622
  setSourceSelectionResolved(false);
6623
+ setApprovalSmartAccountAddress(null);
6537
6624
  if (selectSourceRejectRef.current) {
6538
6625
  selectSourceRejectRef.current(cancellation);
6539
6626
  selectSourceRejectRef.current = null;
@@ -6556,6 +6643,10 @@ function useAuthorizationOrchestrator(deps) {
6556
6643
  lastRunRef.current = { sessionId, options };
6557
6644
  setOrchestratorCompleted(false);
6558
6645
  setSourceSelectionResolved(false);
6646
+ setApprovalSmartAccountAddress(null);
6647
+ if (!options?.keepApprovalGate) {
6648
+ authExecutor.resetApprovalGate();
6649
+ }
6559
6650
  if (!authExecutor.beginExecution()) {
6560
6651
  appendDebug("warn", "orchestrator:run aborted \u2014 already executing");
6561
6652
  return { status: "cancelled" };
@@ -6584,6 +6675,8 @@ function useAuthorizationOrchestrator(deps) {
6584
6675
  let action = mergedPending[0];
6585
6676
  if (completedIds.has(action.id)) break;
6586
6677
  const ownerSessionId = actionSessionMap.get(action.id) ?? sessionId;
6678
+ const ownerSessionSca = sessions.find((s) => s.id === ownerSessionId)?.session.smartAccountAddress;
6679
+ if (ownerSessionSca) setApprovalSmartAccountAddress(ownerSessionSca);
6587
6680
  console.info("[blink-sdk][orchestrator] Next pending action.", {
6588
6681
  actionId: action.id,
6589
6682
  actionType: action.type,
@@ -6608,6 +6701,7 @@ function useAuthorizationOrchestrator(deps) {
6608
6701
  const autoResult = createSelectSourceResult(action, options.autoResolveSource);
6609
6702
  completedIds.add(action.id);
6610
6703
  authExecutor.addResult(autoResult);
6704
+ setSourceSelectionResolved(true);
6611
6705
  const reportedSession3 = await reportActionCompletionWithLogging(
6612
6706
  apiBaseUrl,
6613
6707
  action,
@@ -6701,6 +6795,13 @@ function useAuthorizationOrchestrator(deps) {
6701
6795
  throw new Error(SIGN_PERMIT2_CONFIRMING_MESSAGE);
6702
6796
  }
6703
6797
  }
6798
+ if (isApprovalGatedAction(action)) {
6799
+ const sessionSca = sessions.find((s) => s.id === ownerSessionId)?.session.smartAccountAddress;
6800
+ await authExecutor.waitForApproval(
6801
+ action,
6802
+ sessionSca ?? readActionMetadataString(action.metadata, "smartAccountAddress") ?? readActionMetadataString(action.metadata, "ownerPubkey")
6803
+ );
6804
+ }
6704
6805
  appendDebug("info", `orchestrator:executeAction start ${action.type}`, {
6705
6806
  actionId: action.id,
6706
6807
  ownerSessionId
@@ -6897,7 +6998,8 @@ function useAuthorizationOrchestrator(deps) {
6897
6998
  authExecutor.setError(null);
6898
6999
  return run(lastRun.sessionId, {
6899
7000
  ...lastRun.options,
6900
- probeBeforePrompt: true
7001
+ probeBeforePrompt: true,
7002
+ keepApprovalGate: true
6901
7003
  });
6902
7004
  }, [authExecutor, run, cancelPendingFlow]);
6903
7005
  return {
@@ -6907,9 +7009,17 @@ function useAuthorizationOrchestrator(deps) {
6907
7009
  resolveSelectSource,
6908
7010
  sourceSelectionResolved,
6909
7011
  orchestratorCompleted,
7012
+ approvalSmartAccountAddress,
6910
7013
  cancelPendingFlow
6911
7014
  };
6912
7015
  }
7016
+ function readActionMetadataString(metadata, key) {
7017
+ const value = metadata?.[key];
7018
+ return typeof value === "string" && value.trim() !== "" ? value : null;
7019
+ }
7020
+ function isApprovalGatedAction(action) {
7021
+ return action.type === "APPROVE_PERMIT2" || action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL";
7022
+ }
6913
7023
  function createSelectSourceResult(action, selection) {
6914
7024
  return {
6915
7025
  actionId: action.id,
@@ -7880,6 +7990,26 @@ function deriveSourceTypeAndId(state) {
7880
7990
  }
7881
7991
  return { sourceType: "accountId", sourceId: "" };
7882
7992
  }
7993
+ function hasAnyBalances(accounts) {
7994
+ return accounts.some(
7995
+ (account) => account.wallets.some(
7996
+ (wallet) => wallet.balance !== void 0 || wallet.sources !== void 0
7997
+ )
7998
+ );
7999
+ }
8000
+ function carryOverBalances(prev, next) {
8001
+ const prevWallets = new Map(
8002
+ prev.flatMap((account) => account.wallets.map((wallet) => [wallet.id, wallet]))
8003
+ );
8004
+ return next.map((account) => ({
8005
+ ...account,
8006
+ wallets: account.wallets.map((wallet) => {
8007
+ if (wallet.balance !== void 0 || wallet.sources !== void 0) return wallet;
8008
+ const prior = prevWallets.get(wallet.id);
8009
+ return prior && (prior.balance !== void 0 || prior.sources !== void 0) ? { ...wallet, balance: prior.balance, sources: prior.sources } : wallet;
8010
+ })
8011
+ }));
8012
+ }
7883
8013
  function clearStaleSelection(state) {
7884
8014
  if (state.selectedAccountId == null) return state;
7885
8015
  if (state.desktopWait != null || state.standardDesktopInlineOpenWallet) return state;
@@ -7901,6 +8031,7 @@ function createInitialState(config) {
7901
8031
  accounts: [],
7902
8032
  chains: [],
7903
8033
  loadingData: false,
8034
+ balancesLoading: false,
7904
8035
  depositSelectionRefreshing: false,
7905
8036
  selectedProviderId: null,
7906
8037
  selectedAccountId: null,
@@ -7930,6 +8061,7 @@ function createInitialState(config) {
7930
8061
  privyAuthenticated: false,
7931
8062
  lastResumedAt: 0,
7932
8063
  setupDepositToken: null,
8064
+ setupSpendingLimit: null,
7933
8065
  guestWalletPrepared: null,
7934
8066
  guestWalletDeeplinksPreparing: false,
7935
8067
  amountTooLow: null,
@@ -7949,6 +8081,7 @@ function clearAuthenticatedSessionState(state) {
7949
8081
  accounts: [],
7950
8082
  chains: [],
7951
8083
  loadingData: false,
8084
+ balancesLoading: false,
7952
8085
  depositSelectionRefreshing: false,
7953
8086
  selectedProviderId: null,
7954
8087
  selectedAccountId: null,
@@ -7974,6 +8107,7 @@ function clearAuthenticatedSessionState(state) {
7974
8107
  mobileTokenAuthorizationPending: false,
7975
8108
  linkSettling: false,
7976
8109
  setupDepositToken: null,
8110
+ setupSpendingLimit: null,
7977
8111
  guestWalletPrepared: null,
7978
8112
  guestWalletDeeplinksPreparing: false,
7979
8113
  amountTooLow: null,
@@ -8042,6 +8176,8 @@ function applyAction(state, action) {
8042
8176
  providers: action.providers,
8043
8177
  accounts: action.accounts,
8044
8178
  chains: action.chains,
8179
+ // Accounts arrive balance-free; balances follow via BALANCES_LOADED.
8180
+ balancesLoading: true,
8045
8181
  depositSelectionRefreshing: false,
8046
8182
  initialDataLoaded: true
8047
8183
  };
@@ -8065,10 +8201,12 @@ function applyAction(state, action) {
8065
8201
  depositSelectionRefreshing: false
8066
8202
  };
8067
8203
  case "ACCOUNTS_RELOADED": {
8204
+ const hadBalances = hasAnyBalances(state.accounts);
8068
8205
  const next = {
8069
8206
  ...state,
8070
- accounts: action.accounts,
8207
+ accounts: carryOverBalances(state.accounts, action.accounts),
8071
8208
  providers: action.providers,
8209
+ balancesLoading: !hadBalances,
8072
8210
  initialDataLoaded: true
8073
8211
  };
8074
8212
  if (action.defaults) {
@@ -8080,6 +8218,24 @@ function applyAction(state, action) {
8080
8218
  }
8081
8219
  return clearStaleSelection(next);
8082
8220
  }
8221
+ case "BALANCES_LOADED": {
8222
+ const { balancesByAccountId } = action;
8223
+ const accounts = state.accounts.map((account) => {
8224
+ const accountBalances = balancesByAccountId[account.id];
8225
+ if (!accountBalances) return account;
8226
+ const balancesByWalletId = new Map(
8227
+ accountBalances.wallets.map((w) => [w.id, w])
8228
+ );
8229
+ return {
8230
+ ...account,
8231
+ wallets: account.wallets.map((wallet) => {
8232
+ const merged = balancesByWalletId.get(wallet.id);
8233
+ return merged ? { ...wallet, balance: merged.balance, sources: merged.sources } : wallet;
8234
+ })
8235
+ };
8236
+ });
8237
+ return { ...state, accounts, balancesLoading: false };
8238
+ }
8083
8239
  case "SET_DEPOSIT_SELECTION_REFRESHING":
8084
8240
  return { ...state, depositSelectionRefreshing: action.value };
8085
8241
  case "SAVE_SELECTION":
@@ -8151,6 +8307,7 @@ function applyAction(state, action) {
8151
8307
  mobileFlow: false,
8152
8308
  mobileTokenAuthorizationPending: false,
8153
8309
  setupDepositToken: null,
8310
+ setupSpendingLimit: null,
8154
8311
  setupFlowScreen: null
8155
8312
  };
8156
8313
  case "PAY_ENDED":
@@ -8396,6 +8553,7 @@ function applyAction(state, action) {
8396
8553
  linkSettling: false,
8397
8554
  savedSelection: null,
8398
8555
  setupDepositToken: null,
8556
+ setupSpendingLimit: null,
8399
8557
  setupFlowScreen: null,
8400
8558
  guestWalletPrepared: null,
8401
8559
  guestWalletDeeplinksPreparing: false,
@@ -8461,12 +8619,217 @@ function applyAction(state, action) {
8461
8619
  ...action.chainId != null ? { chainId: action.chainId } : {}
8462
8620
  }
8463
8621
  };
8622
+ case "SET_SETUP_SPENDING_LIMIT":
8623
+ return { ...state, setupSpendingLimit: action.limit };
8464
8624
  case "CLEAR_SETUP_DEPOSIT_TOKEN":
8465
- return { ...state, setupDepositToken: null };
8625
+ return { ...state, setupDepositToken: null, setupSpendingLimit: null };
8466
8626
  default:
8467
8627
  return state;
8468
8628
  }
8469
8629
  }
8630
+ var BLOCKED_SUBSTRINGS = [
8631
+ "bearer",
8632
+ "signature",
8633
+ "signedpayload",
8634
+ "userop",
8635
+ "jwt",
8636
+ "secret",
8637
+ "password",
8638
+ "privatekey",
8639
+ "mnemonic",
8640
+ "seedphrase"
8641
+ ];
8642
+ function isBlockedKey(key) {
8643
+ const lower = key.toLowerCase();
8644
+ if (lower === "token" || lower.endsWith("token")) return true;
8645
+ return BLOCKED_SUBSTRINGS.some((blocked) => lower.includes(blocked));
8646
+ }
8647
+ function isReady() {
8648
+ return typeof posthog__default.default !== "undefined" && posthog__default.default.__loaded === true;
8649
+ }
8650
+ function sanitizeProps(props) {
8651
+ if (!props) return props;
8652
+ const clean = {};
8653
+ for (const [key, value] of Object.entries(props)) {
8654
+ if (isBlockedKey(key)) continue;
8655
+ if (value === void 0) continue;
8656
+ clean[key] = value;
8657
+ }
8658
+ return clean;
8659
+ }
8660
+ function registerSuperProps(props) {
8661
+ if (!isReady()) return;
8662
+ try {
8663
+ posthog__default.default.register(sanitizeProps(props) ?? {});
8664
+ } catch {
8665
+ }
8666
+ }
8667
+ function track(event, props) {
8668
+ if (!isReady()) return;
8669
+ try {
8670
+ posthog__default.default.capture(event, sanitizeProps(props));
8671
+ } catch {
8672
+ }
8673
+ }
8674
+ function identifyUser(privyUserId, props) {
8675
+ if (!isReady() || !privyUserId) return;
8676
+ try {
8677
+ posthog__default.default.identify(privyUserId, sanitizeProps(props));
8678
+ } catch {
8679
+ }
8680
+ }
8681
+ function resolveAnalyticsEnvironment(opts) {
8682
+ const explicit = opts.explicit?.trim();
8683
+ if (explicit) return explicit;
8684
+ const raw = opts.apiBaseUrl ?? "";
8685
+ let host = raw;
8686
+ try {
8687
+ host = new URL(raw).hostname;
8688
+ } catch {
8689
+ }
8690
+ const h = host.toLowerCase();
8691
+ if (h === "" || h.includes("localhost") || h.startsWith("127.")) return "development";
8692
+ if (h.includes("smokebox")) return "smokebox";
8693
+ if (h.includes("sandbox")) return "sandbox";
8694
+ if (h.includes("staging")) return "staging";
8695
+ return "production";
8696
+ }
8697
+ function resetUser() {
8698
+ if (!isReady()) return;
8699
+ try {
8700
+ posthog__default.default.reset();
8701
+ } catch {
8702
+ }
8703
+ }
8704
+
8705
+ // src/analyticsEvents.ts
8706
+ function providerName(state, providerId) {
8707
+ return state.providers.find((p) => p.id === providerId)?.name;
8708
+ }
8709
+ function amountUsd(transfer) {
8710
+ return transfer.amount?.currency === "USD" ? transfer.amount.amount : transfer.amount?.amount;
8711
+ }
8712
+ function sourceChain(transfer) {
8713
+ return transfer.sources?.[0]?.provider?.chainFamily;
8714
+ }
8715
+ function trackAction(action, state, ctx) {
8716
+ const device = ctx.device;
8717
+ switch (action.type) {
8718
+ case "PASSKEY_ACTIVATED":
8719
+ track("auth_succeeded", { method: "passkey", device });
8720
+ break;
8721
+ case "SELECT_PROVIDER":
8722
+ track("wallet_provider_selected", {
8723
+ device,
8724
+ providerId: action.providerId,
8725
+ providerName: providerName(state, action.providerId),
8726
+ transport: device === "desktop" ? "inline" : "deeplink"
8727
+ });
8728
+ break;
8729
+ case "MOBILE_DEEPLINK_READY":
8730
+ track("wallet_deeplink_opened", { device });
8731
+ break;
8732
+ case "SET_SETUP_DEPOSIT_TOKEN":
8733
+ track("token_selected", {
8734
+ device,
8735
+ tokenSymbol: action.symbol,
8736
+ chainName: action.chainName
8737
+ });
8738
+ break;
8739
+ case "FINALIZE_AMOUNT": {
8740
+ const parsed = Number(state.amount);
8741
+ track("amount_entered", {
8742
+ device,
8743
+ amountUsd: Number.isFinite(parsed) ? parsed : void 0
8744
+ });
8745
+ break;
8746
+ }
8747
+ case "PAY_STARTED":
8748
+ track("deposit_clicked", { device });
8749
+ break;
8750
+ case "TRANSFER_CREATED":
8751
+ track("transfer_created", {
8752
+ device,
8753
+ chainFamily: sourceChain(action.transfer)
8754
+ });
8755
+ break;
8756
+ case "TRANSFER_SIGNED":
8757
+ track("transfer_signed", { device });
8758
+ break;
8759
+ case "TRANSFER_COMPLETED":
8760
+ track("transfer_completed", {
8761
+ device,
8762
+ amountUsd: amountUsd(action.transfer),
8763
+ chainFamily: sourceChain(action.transfer)
8764
+ });
8765
+ break;
8766
+ case "CONFIRM_SIGN_SUCCESS":
8767
+ track("confirm_sign_success", { device });
8768
+ break;
8769
+ case "MOBILE_SETUP_COMPLETE":
8770
+ track("wallet_setup_complete", { device });
8771
+ break;
8772
+ case "TRANSFER_FAILED":
8773
+ track("transfer_error", {
8774
+ device,
8775
+ type: "transfer_failed",
8776
+ stage: "processing",
8777
+ message: action.error
8778
+ });
8779
+ break;
8780
+ case "PROCESSING_TIMEOUT":
8781
+ track("transfer_error", {
8782
+ device,
8783
+ type: "processing_timeout",
8784
+ stage: "processing",
8785
+ message: action.error
8786
+ });
8787
+ break;
8788
+ case "SIGN_PAYLOAD_EXPIRED":
8789
+ track("transfer_error", {
8790
+ device,
8791
+ type: "sign_payload_expired",
8792
+ stage: "signing",
8793
+ message: action.error
8794
+ });
8795
+ break;
8796
+ case "PAY_ERROR":
8797
+ track("transfer_error", {
8798
+ device,
8799
+ type: "pay_error",
8800
+ stage: "creation",
8801
+ message: action.error
8802
+ });
8803
+ break;
8804
+ }
8805
+ }
8806
+ function useAnalyticsScreenView(phase, device) {
8807
+ const lastScreenRef = react.useRef(null);
8808
+ const screen = screenForPhase(phase);
8809
+ react.useEffect(() => {
8810
+ if (lastScreenRef.current === screen) return;
8811
+ lastScreenRef.current = screen;
8812
+ track("screen_viewed", { screen, phase: phase.step, device });
8813
+ }, [screen, phase.step, device]);
8814
+ }
8815
+ function usePostHogIdentify() {
8816
+ const { ready, authenticated, user } = reactAuth.usePrivy();
8817
+ const identifiedIdRef = react.useRef(null);
8818
+ react.useEffect(() => {
8819
+ if (!ready) return;
8820
+ if (authenticated && user?.id) {
8821
+ if (identifiedIdRef.current !== user.id) {
8822
+ identifyUser(user.id);
8823
+ identifiedIdRef.current = user.id;
8824
+ }
8825
+ return;
8826
+ }
8827
+ if (identifiedIdRef.current !== null) {
8828
+ resetUser();
8829
+ identifiedIdRef.current = null;
8830
+ }
8831
+ }, [ready, authenticated, user?.id]);
8832
+ }
8470
8833
 
8471
8834
  // src/setupDepositConfirmation.ts
8472
8835
  function deriveEffectiveSetupSource(setupDepositToken, setupSelectedSourceOption) {
@@ -9045,9 +9408,12 @@ function PrimaryButton({
9045
9408
  }
9046
9409
  );
9047
9410
  }
9411
+ var BUTTON_MIN_HEIGHT = 60;
9048
9412
  var progressButtonStyle = (tokens, paused) => ({
9049
9413
  position: "relative",
9050
9414
  width: "100%",
9415
+ minHeight: BUTTON_MIN_HEIGHT,
9416
+ boxSizing: "border-box",
9051
9417
  padding: "18px 24px",
9052
9418
  background: `linear-gradient(180deg, ${tokens.accent}88, ${tokens.accentHover}88)`,
9053
9419
  color: tokens.accentText,
@@ -9084,6 +9450,8 @@ var buttonStyle2 = (tokens, state) => {
9084
9450
  const gradient = `linear-gradient(180deg, ${tokens.accent}, ${tokens.accentHover})`;
9085
9451
  return {
9086
9452
  width: "100%",
9453
+ minHeight: BUTTON_MIN_HEIGHT,
9454
+ boxSizing: "border-box",
9087
9455
  padding: "18px 24px",
9088
9456
  // Layer a faint white film over the gradient on hover rather than using
9089
9457
  // `filter: brightness()` — the accent gradient is near-black in the new
@@ -9331,6 +9699,7 @@ var USDH_LOGO = svgToDataUri(USDH_SVG);
9331
9699
  var ETH_LOGO = "https://assets.relay.link/icons/currencies/eth.png";
9332
9700
  var SOL_LOGO = "https://assets.relay.link/icons/currencies/sol.png";
9333
9701
  var MON_LOGO = "https://assets.relay.link/icons/currencies/mon.png";
9702
+ var BNB_LOGO = "https://assets.relay.link/icons/currencies/bnb.png";
9334
9703
  var KNOWN_LOGOS = {
9335
9704
  metamask: METAMASK_LOGO,
9336
9705
  base: BASE_LOGO,
@@ -9349,7 +9718,8 @@ var TOKEN_LOGOS = {
9349
9718
  USDH: USDH_LOGO,
9350
9719
  ETH: ETH_LOGO,
9351
9720
  SOL: SOL_LOGO,
9352
- MON: MON_LOGO
9721
+ MON: MON_LOGO,
9722
+ BNB: BNB_LOGO
9353
9723
  };
9354
9724
  var CHAIN_LOGOS = {
9355
9725
  base: BASE_CHAIN_LOGO,
@@ -10008,6 +10378,8 @@ function SourcePill({
10008
10378
  logo,
10009
10379
  name,
10010
10380
  balance,
10381
+ nameSlot,
10382
+ balanceSlot,
10011
10383
  expanded,
10012
10384
  showChevron = true,
10013
10385
  icon
@@ -10015,8 +10387,8 @@ function SourcePill({
10015
10387
  const { tokens } = useBlinkConfig();
10016
10388
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: pillStyle2(tokens.bgCardTranslucent), children: [
10017
10389
  icon ?? (logo ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logo, alt: "", "aria-hidden": "true", style: logoStyle }) : name ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: logoFallbackStyle(tokens.bgInput, tokens.textMuted), children: name.charAt(0).toUpperCase() }) : null),
10018
- name && /* @__PURE__ */ jsxRuntime.jsx("span", { style: labelStyle2(tokens.text), children: name }),
10019
- balance && /* @__PURE__ */ jsxRuntime.jsx("span", { style: balanceStyle(tokens.text), children: balance }),
10390
+ nameSlot ?? (name && /* @__PURE__ */ jsxRuntime.jsx("span", { style: labelStyle2(tokens.text), children: name })),
10391
+ balanceSlot ?? (balance && /* @__PURE__ */ jsxRuntime.jsx("span", { style: balanceStyle(tokens.text), children: balance })),
10020
10392
  showChevron && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "17", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(
10021
10393
  "path",
10022
10394
  {
@@ -10079,6 +10451,37 @@ var balanceStyle = (color) => ({
10079
10451
  color,
10080
10452
  whiteSpace: "nowrap"
10081
10453
  });
10454
+ var SHIMMER_KEYFRAMES = `
10455
+ @keyframes blink-shimmer-sweep {
10456
+ 0% { background-position: 200% 0; }
10457
+ 100% { background-position: -200% 0; }
10458
+ }`;
10459
+ function Shimmer({
10460
+ width,
10461
+ height,
10462
+ borderRadius = 999,
10463
+ baseColor,
10464
+ highlightColor,
10465
+ style
10466
+ }) {
10467
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-hidden": "true", style: { display: "inline-block", ...style }, children: [
10468
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: SHIMMER_KEYFRAMES }),
10469
+ /* @__PURE__ */ jsxRuntime.jsx(
10470
+ "span",
10471
+ {
10472
+ style: {
10473
+ display: "block",
10474
+ width,
10475
+ height,
10476
+ borderRadius,
10477
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
10478
+ backgroundSize: "200% 100%",
10479
+ animation: "blink-shimmer-sweep 1.4s ease-in-out infinite"
10480
+ }
10481
+ }
10482
+ )
10483
+ ] });
10484
+ }
10082
10485
  function formatUsdTwoDecimals(value) {
10083
10486
  const n = Number(value);
10084
10487
  return (Number.isFinite(n) ? n : 0).toLocaleString("en-US", {
@@ -10779,6 +11182,14 @@ function LockIcon({ size = 24 }) {
10779
11182
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V8a4 4 0 1 1 8 0v3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" })
10780
11183
  ] });
10781
11184
  }
11185
+ function KeyIcon({ size = 24 }) {
11186
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
11187
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "7", cy: "12", r: "3.6", stroke: "currentColor", strokeWidth: "2" }),
11188
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.6 12H20", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
11189
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 12v3.2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
11190
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 12v3.2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
11191
+ ] });
11192
+ }
10782
11193
  function FaceIdIcon({ size = 24 }) {
10783
11194
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
10784
11195
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9V6a2 2 0 0 1 2-2h3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }),
@@ -10792,6 +11203,21 @@ function FaceIdIcon({ size = 24 }) {
10792
11203
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.5 16c.7.6 1.6 1 2.5 1s1.8-.4 2.5-1", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" })
10793
11204
  ] });
10794
11205
  }
11206
+ function PencilIcon({ size = 18 } = {}) {
11207
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
11208
+ /* @__PURE__ */ jsxRuntime.jsx(
11209
+ "path",
11210
+ {
11211
+ d: "M4 20h4l10.5-10.5a2 2 0 0 0 0-2.83l-1.17-1.17a2 2 0 0 0-2.83 0L4 16v4Z",
11212
+ stroke: "currentColor",
11213
+ strokeWidth: "1.7",
11214
+ strokeLinecap: "round",
11215
+ strokeLinejoin: "round"
11216
+ }
11217
+ ),
11218
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.5 6.5l4 4", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round" })
11219
+ ] });
11220
+ }
10795
11221
  function WalletIcon() {
10796
11222
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
10797
11223
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -11163,7 +11589,7 @@ function CopyAddressButton({
11163
11589
  }) {
11164
11590
  const { tokens } = useBlinkConfig();
11165
11591
  const [hovered, setHovered] = react.useState(false);
11166
- const short = address.length > 18 ? `${address.slice(0, 8)}...${address.slice(-6)}` : address;
11592
+ const short = truncateMiddle(address);
11167
11593
  return /* @__PURE__ */ jsxRuntime.jsxs(
11168
11594
  "button",
11169
11595
  {
@@ -13123,7 +13549,7 @@ var lockBannerTextStyle = (color) => ({
13123
13549
  function ManualTransferPasskeyScreen({
13124
13550
  onCreatePasskey,
13125
13551
  onNoThanks,
13126
- amountUsd,
13552
+ amountUsd: amountUsd2,
13127
13553
  loading = false,
13128
13554
  error,
13129
13555
  onBack,
@@ -13321,7 +13747,6 @@ var errorBannerStyle6 = (tokens) => ({
13321
13747
  textAlign: "left",
13322
13748
  boxSizing: "border-box"
13323
13749
  });
13324
- var SHIMMER_ROWS = 3;
13325
13750
  function LinkTokensScreen({
13326
13751
  entries: entries2,
13327
13752
  selectedIndex,
@@ -13334,44 +13759,75 @@ function LinkTokensScreen({
13334
13759
  approving = false
13335
13760
  }) {
13336
13761
  const { tokens: t } = useBlinkConfig();
13762
+ const [view, setView] = react.useState("summary");
13763
+ const [pendingIndex, setPendingIndex] = react.useState(selectedIndex);
13764
+ const [limit, setLimit] = react.useState("unlimited");
13765
+ const [editing, setEditing] = react.useState(false);
13337
13766
  const showShimmer = loading && entries2.length === 0;
13338
13767
  const showEmpty = !loading && entries2.length === 0;
13339
13768
  const selected = entries2[selectedIndex];
13769
+ const limitUsd = limit === "unlimited" ? null : parseAmount(limit);
13770
+ const isUnlimited = limitUsd == null;
13771
+ const limitLabel = isUnlimited ? "Unlimited" : `$${limit}`;
13772
+ const currentSelection = () => limitUsd == null ? { unlimited: true } : { usd: limitUsd };
13340
13773
  const approveDisabled = loading || approving || entries2.length === 0 || selectedIndex < 0 || selectedIndex >= entries2.length || !!selected?.notSupported;
13341
- const ctaLabel = selected ? `Authorize ${selected.tokenSymbol} on ${shortChainName(selected.chainName)}` : "Approve";
13342
- return /* @__PURE__ */ jsxRuntime.jsxs(
13343
- ScreenLayout,
13344
- {
13345
- scrollableBody: false,
13346
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
13347
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: lockBannerStyle2, children: [
13348
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: lockIconWrapStyle2(t.text), children: /* @__PURE__ */ jsxRuntime.jsx(LockIcon3, {}) }),
13349
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: lockBannerTextStyle2(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
13350
- ] }),
13351
- /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onApprove, disabled: approveDisabled, loading: approving, children: ctaLabel })
13352
- ] }),
13353
- children: [
13354
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
13355
- @keyframes blink-link-tokens-shimmer {
13356
- 0% { background-position: 200% 0; }
13357
- 100% { background-position: -200% 0; }
13358
- }
13359
- .blink-link-tokens-list {
13360
- scrollbar-width: thin;
13361
- scrollbar-color: ${t.textTertiary} transparent;
13362
- }
13363
- .blink-link-tokens-list::-webkit-scrollbar {
13364
- width: 4px;
13365
- }
13366
- .blink-link-tokens-list::-webkit-scrollbar-track {
13367
- background: transparent;
13368
- margin: 8px 0;
13369
- }
13370
- .blink-link-tokens-list::-webkit-scrollbar-thumb {
13371
- background: ${t.textTertiary};
13372
- border-radius: 999px;
13373
- }
13374
- ` }),
13774
+ function openSelectToken() {
13775
+ setPendingIndex(selectedIndex);
13776
+ setView("selectToken");
13777
+ }
13778
+ function commitSelectToken() {
13779
+ const row = entries2[pendingIndex];
13780
+ if (row && !row.notSupported) onSelect(pendingIndex);
13781
+ setView("summary");
13782
+ }
13783
+ if (view === "selectToken") {
13784
+ const pending = entries2[pendingIndex];
13785
+ const continueDisabled = !pending || !!pending.notSupported || entries2.length === 0;
13786
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13787
+ ScreenLayout,
13788
+ {
13789
+ scrollableBody: false,
13790
+ footer: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: commitSelectToken, disabled: continueDisabled, children: "Continue" }),
13791
+ children: [
13792
+ /* @__PURE__ */ jsxRuntime.jsx(ListScrollbarStyles, { textTertiary: t.textTertiary }),
13793
+ /* @__PURE__ */ jsxRuntime.jsx(
13794
+ ScreenHeader,
13795
+ {
13796
+ onBack: () => setView("summary"),
13797
+ center: /* @__PURE__ */ jsxRuntime.jsx("img", { src: BLINK_WORDMARK, alt: "Blink", style: wordmarkImgStyle3 })
13798
+ }
13799
+ ),
13800
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle10, children: [
13801
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(t.text, t.fontWeightBold), children: "Select token" }),
13802
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "blink-link-tokens-list", style: listCardStyle(t.bgRecessed), children: [
13803
+ entries2.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsx(
13804
+ TokenSourceRow,
13805
+ {
13806
+ symbol: entry.tokenSymbol,
13807
+ chainName: entry.chainName,
13808
+ tokenLogoUri: entry.tokenLogoUri,
13809
+ balance: entry.balanceLabel == null ? entry.balanceUsd : void 0,
13810
+ balanceLabel: entry.balanceLabel,
13811
+ selected: i === pendingIndex,
13812
+ notSupported: entry.notSupported,
13813
+ onClick: () => setPendingIndex(i)
13814
+ },
13815
+ `${entry.tokenSymbol}-${entry.chainName}-${i}`
13816
+ )),
13817
+ showEmpty && /* @__PURE__ */ jsxRuntime.jsx("div", { style: emptyStyle(t.textMuted), children: "No tokens available to link. Switch wallets or fund this account to continue." })
13818
+ ] })
13819
+ ] })
13820
+ ]
13821
+ }
13822
+ );
13823
+ }
13824
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13825
+ ScreenLayout,
13826
+ {
13827
+ scrollableBody: false,
13828
+ footer: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: () => onApprove(currentSelection()), disabled: approveDisabled, loading: approving, children: "Review" }),
13829
+ children: [
13830
+ /* @__PURE__ */ jsxRuntime.jsx(ListScrollbarStyles, { textTertiary: t.textTertiary }),
13375
13831
  /* @__PURE__ */ jsxRuntime.jsx(
13376
13832
  ScreenHeader,
13377
13833
  {
@@ -13381,71 +13837,198 @@ function LinkTokensScreen({
13381
13837
  }
13382
13838
  ),
13383
13839
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle10, children: [
13384
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(t.text), children: "Pick your stablecoin for passkey deposits" }),
13385
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "blink-link-tokens-list", style: listCardStyle(t.bgRecessed), children: [
13386
- showShimmer ? Array.from({ length: SHIMMER_ROWS }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs(
13387
- "div",
13388
- {
13389
- "data-testid": "link-tokens-shimmer-row",
13390
- "aria-hidden": "true",
13391
- style: shimmerRowStyle,
13392
- children: [
13393
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerCircleStyle(t.bgInput, t.border) }),
13394
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shimmerInfoStyle, children: [
13395
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(72, t.bgInput, t.border) }),
13396
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(48, t.bgInput, t.border) })
13397
- ] }),
13840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: flexSpacerStyle }),
13841
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: topGroupStyle, children: [
13842
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(t.text, t.fontWeightBold), children: "Let your passkey spend" }),
13843
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: pillsGroupStyle, children: [
13844
+ showShimmer ? /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-testid": "link-tokens-summary-shimmer", "aria-hidden": "true", style: pillStyle3(t.bgRecessed), children: [
13845
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerCircleStyle(t.bgInput, t.border) }),
13846
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shimmerInfoStyle, children: [
13847
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(96, t.bgInput, t.border) }),
13398
13848
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(56, t.bgInput, t.border) })
13399
- ]
13400
- },
13401
- `shimmer-${i}`
13402
- )) : entries2.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsx(
13403
- TokenSourceRow,
13404
- {
13405
- symbol: entry.tokenSymbol,
13406
- chainName: entry.chainName,
13407
- tokenLogoUri: entry.tokenLogoUri,
13408
- balance: entry.balanceLabel == null ? entry.balanceUsd : void 0,
13409
- balanceLabel: entry.balanceLabel,
13410
- selected: i === selectedIndex,
13411
- notSupported: entry.notSupported,
13412
- onClick: () => onSelect(i)
13413
- },
13414
- `${entry.tokenSymbol}-${entry.chainName}-${i}`
13415
- )),
13416
- showEmpty && /* @__PURE__ */ jsxRuntime.jsx("div", { style: emptyStyle(t.textMuted), children: "No tokens available to link. Switch wallets or fund this account to continue." })
13849
+ ] })
13850
+ ] }) : selected ? /* @__PURE__ */ jsxRuntime.jsxs(
13851
+ "button",
13852
+ {
13853
+ type: "button",
13854
+ onClick: openSelectToken,
13855
+ style: pillButtonStyle(t.bgRecessed),
13856
+ "aria-label": `Selected token ${selected.tokenSymbol} on ${selected.chainName}. Change token.`,
13857
+ "data-testid": "link-tokens-selected-pill",
13858
+ children: [
13859
+ /* @__PURE__ */ jsxRuntime.jsx(TokenPillLogo, { entry: selected, fallbackBg: t.bgRecessed, fallbackColor: t.textMuted }),
13860
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: pillTextColStyle, children: [
13861
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: pillTitleStyle(t.text, t.fontWeightRegular), children: [
13862
+ selected.tokenSymbol,
13863
+ " on ",
13864
+ selected.chainName
13865
+ ] }),
13866
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillSubStyle(t.textMuted, t.fontWeightRegular), children: selected.balanceLabel ?? `$${formatUsdTwoDecimals2(selected.balanceUsd)}` })
13867
+ ] }),
13868
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillChevronStyle(t.textMuted), children: /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { color: t.textMuted }) })
13869
+ ]
13870
+ }
13871
+ ) : null,
13872
+ editing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: pillStyle3(t.bgRecessed), children: [
13873
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: limitEditLabelStyle(t.text, t.fontWeightRegular), children: "Spending limit" }),
13874
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: limitEditRightStyle, children: [
13875
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: limitAmountStyle(t.text, t.fontWeightRegular), children: [
13876
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", style: limitDollarStyle(isUnlimited, t.text), children: "$" }),
13877
+ /* @__PURE__ */ jsxRuntime.jsx(
13878
+ "input",
13879
+ {
13880
+ type: "text",
13881
+ inputMode: "decimal",
13882
+ autoFocus: true,
13883
+ placeholder: "0",
13884
+ "aria-label": "Lifetime spending limit",
13885
+ value: limit === "unlimited" ? "" : limit,
13886
+ onChange: (e) => setLimit(sanitizeRawInput(e.target.value)),
13887
+ onBlur: () => setEditing(false),
13888
+ "data-testid": "link-tokens-limit-input",
13889
+ style: limitInputStyle(t.text, t.textMuted)
13890
+ }
13891
+ )
13892
+ ] }),
13893
+ /* @__PURE__ */ jsxRuntime.jsx(
13894
+ "button",
13895
+ {
13896
+ type: "button",
13897
+ onMouseDown: (e) => e.preventDefault(),
13898
+ onClick: () => {
13899
+ setLimit("unlimited");
13900
+ setEditing(false);
13901
+ },
13902
+ style: limitResetStyle(t.border, t.textMuted, t.fontWeightMedium),
13903
+ "data-testid": "link-tokens-limit-reset",
13904
+ children: "Unlimited"
13905
+ }
13906
+ )
13907
+ ] })
13908
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
13909
+ "button",
13910
+ {
13911
+ type: "button",
13912
+ onClick: () => setEditing(true),
13913
+ style: pillButtonStyle(t.bgRecessed),
13914
+ "aria-label": `Lifetime spending limit: ${limitLabel}. Edit.`,
13915
+ "data-testid": "link-tokens-limit-pill",
13916
+ children: [
13917
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillTitleStyle(t.text, t.fontWeightRegular), children: "Lifetime spending limit" }),
13918
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: limitValueStyle, children: [
13919
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillSubStyle(isUnlimited ? t.textMuted : t.text, t.fontWeightRegular), children: limitLabel }),
13920
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillChevronStyle(t.textMuted), children: /* @__PURE__ */ jsxRuntime.jsx(PencilIcon, { size: 16 }) })
13921
+ ] })
13922
+ ]
13923
+ }
13924
+ )
13925
+ ] })
13417
13926
  ] }),
13418
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: bannerSlotStyle, children: /* @__PURE__ */ jsxRuntime.jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
13927
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: flexSpacerStyle }),
13928
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: bottomGroupStyle, children: [
13929
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: infoCardStyle3(t.bgRecessed), children: [
13930
+ /* @__PURE__ */ jsxRuntime.jsx(
13931
+ InfoRow,
13932
+ {
13933
+ icon: /* @__PURE__ */ jsxRuntime.jsx(LockIcon, { size: 22 }),
13934
+ title: "Blink is non-custodial",
13935
+ body: "Your passkey moves money, not Blink.",
13936
+ t
13937
+ }
13938
+ ),
13939
+ /* @__PURE__ */ jsxRuntime.jsx(
13940
+ InfoRow,
13941
+ {
13942
+ icon: /* @__PURE__ */ jsxRuntime.jsx(FaceIdIcon, { size: 22 }),
13943
+ title: "Every transaction is signed",
13944
+ body: "Nothing can move without your direct approval.",
13945
+ t
13946
+ }
13947
+ )
13948
+ ] }),
13949
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: bannerSlotStyle, children: /* @__PURE__ */ jsxRuntime.jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
13950
+ ] })
13419
13951
  ] })
13420
13952
  ]
13421
13953
  }
13422
13954
  );
13423
13955
  }
13424
- function shortChainName(chainName) {
13425
- return chainName.replace(/\s+One$/i, "");
13956
+ function TokenPillLogo({
13957
+ entry,
13958
+ fallbackBg,
13959
+ fallbackColor
13960
+ }) {
13961
+ const logo = entry.tokenLogoUri ?? TOKEN_LOGOS[entry.tokenSymbol];
13962
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillLogoSlotStyle, children: logo ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logo, alt: "", "aria-hidden": "true", style: pillLogoImgStyle }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: pillLogoFallbackStyle(fallbackBg, fallbackColor), children: entry.tokenSymbol.charAt(0).toUpperCase() }) });
13426
13963
  }
13427
- function LockIcon3() {
13428
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
13429
- "path",
13430
- {
13431
- d: "M7 10V8a5 5 0 0 1 10 0v2m-12 0h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1z",
13432
- stroke: "currentColor",
13433
- strokeWidth: "2",
13434
- strokeLinecap: "round",
13435
- strokeLinejoin: "round"
13436
- }
13437
- ) });
13964
+ function InfoRow({
13965
+ icon,
13966
+ title,
13967
+ body,
13968
+ t
13969
+ }) {
13970
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: infoRowStyle2, children: [
13971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: infoIconStyle(t.text), children: icon }),
13972
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: infoTextColStyle, children: [
13973
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: infoTitleStyle2(t.text, t.fontWeightSemibold), children: title }),
13974
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: infoBodyStyle(t.textMuted, t.fontWeightMedium), children: body })
13975
+ ] })
13976
+ ] });
13977
+ }
13978
+ function ListScrollbarStyles({ textTertiary }) {
13979
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
13980
+ @keyframes blink-link-tokens-shimmer {
13981
+ 0% { background-position: 200% 0; }
13982
+ 100% { background-position: -200% 0; }
13983
+ }
13984
+ .blink-link-tokens-list {
13985
+ scrollbar-width: thin;
13986
+ scrollbar-color: ${textTertiary} transparent;
13987
+ }
13988
+ .blink-link-tokens-list::-webkit-scrollbar { width: 4px; }
13989
+ .blink-link-tokens-list::-webkit-scrollbar-track {
13990
+ background: transparent;
13991
+ margin: 8px 0;
13992
+ }
13993
+ .blink-link-tokens-list::-webkit-scrollbar-thumb {
13994
+ background: ${textTertiary};
13995
+ border-radius: 999px;
13996
+ }
13997
+ input[data-testid="link-tokens-limit-input"]::placeholder {
13998
+ color: var(--link-limit-placeholder);
13999
+ opacity: 1;
14000
+ }
14001
+ ` });
13438
14002
  }
13439
14003
  var contentStyle10 = {
13440
14004
  display: "flex",
13441
14005
  flexDirection: "column",
13442
- alignItems: "center",
13443
- gap: 16,
13444
- paddingTop: 8,
13445
14006
  flex: 1,
13446
14007
  minHeight: 0,
13447
14008
  width: "100%"
13448
14009
  };
14010
+ var flexSpacerStyle = {
14011
+ flex: 1,
14012
+ minHeight: 16
14013
+ };
14014
+ var topGroupStyle = {
14015
+ display: "flex",
14016
+ flexDirection: "column",
14017
+ gap: 24,
14018
+ width: "100%"
14019
+ };
14020
+ var bottomGroupStyle = {
14021
+ display: "flex",
14022
+ flexDirection: "column",
14023
+ gap: 12,
14024
+ width: "100%"
14025
+ };
14026
+ var pillsGroupStyle = {
14027
+ display: "flex",
14028
+ flexDirection: "column",
14029
+ gap: 8,
14030
+ width: "100%"
14031
+ };
13449
14032
  var wordmarkImgStyle3 = {
13450
14033
  height: 22,
13451
14034
  width: "auto",
@@ -13453,9 +14036,9 @@ var wordmarkImgStyle3 = {
13453
14036
  pointerEvents: "none",
13454
14037
  userSelect: "none"
13455
14038
  };
13456
- var headingStyle7 = (color) => ({
14039
+ var headingStyle7 = (color, weight) => ({
13457
14040
  fontSize: 24,
13458
- fontWeight: 700,
14041
+ fontWeight: weight,
13459
14042
  lineHeight: "normal",
13460
14043
  letterSpacing: 0,
13461
14044
  color,
@@ -13475,68 +14058,155 @@ var listCardStyle = (bg) => ({
13475
14058
  minHeight: 0,
13476
14059
  overflowY: "auto"
13477
14060
  });
13478
- var shimmerRowStyle = {
14061
+ var pillStyle3 = (bg) => ({
13479
14062
  display: "flex",
13480
14063
  alignItems: "center",
13481
14064
  gap: 16,
13482
- padding: "12px 16px 12px 12px",
13483
- background: "transparent",
13484
- border: "none",
13485
- borderRadius: 16,
13486
14065
  width: "100%",
13487
- boxSizing: "border-box"
14066
+ boxSizing: "border-box",
14067
+ padding: "14px 16px",
14068
+ borderRadius: 20,
14069
+ background: bg
14070
+ });
14071
+ var pillButtonStyle = (bg) => ({
14072
+ ...pillStyle3(bg),
14073
+ border: "none",
14074
+ cursor: "pointer",
14075
+ fontFamily: "inherit",
14076
+ textAlign: "left"
14077
+ });
14078
+ var pillTextColStyle = {
14079
+ display: "flex",
14080
+ flexDirection: "column",
14081
+ flex: 1,
14082
+ minWidth: 0,
14083
+ gap: 2
13488
14084
  };
13489
- var shimmerCircleStyle = (baseColor, highlightColor) => ({
14085
+ var pillTitleStyle = (color, weight) => ({
14086
+ fontSize: "1rem",
14087
+ fontWeight: weight,
14088
+ lineHeight: "normal",
14089
+ color,
14090
+ whiteSpace: "nowrap",
14091
+ overflow: "hidden",
14092
+ textOverflow: "ellipsis"
14093
+ });
14094
+ var pillSubStyle = (color, weight) => ({
14095
+ fontSize: "0.95rem",
14096
+ fontWeight: weight,
14097
+ color
14098
+ });
14099
+ var pillChevronStyle = (color) => ({
14100
+ flexShrink: 0,
14101
+ display: "inline-flex",
14102
+ alignItems: "center",
14103
+ color
14104
+ });
14105
+ var limitValueStyle = {
14106
+ marginLeft: "auto",
14107
+ display: "inline-flex",
14108
+ alignItems: "center",
14109
+ gap: 8
14110
+ };
14111
+ var pillLogoSlotStyle = {
14112
+ position: "relative",
13490
14113
  width: 40,
13491
14114
  height: 40,
13492
- borderRadius: "50%",
13493
14115
  flexShrink: 0,
13494
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13495
- backgroundSize: "200% 100%",
13496
- animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14116
+ display: "inline-flex",
14117
+ alignItems: "center",
14118
+ justifyContent: "center"
14119
+ };
14120
+ var pillLogoImgStyle = {
14121
+ width: "100%",
14122
+ height: "100%",
14123
+ borderRadius: "50%",
14124
+ objectFit: "cover"
14125
+ };
14126
+ var pillLogoFallbackStyle = (bg, color) => ({
14127
+ width: "100%",
14128
+ height: "100%",
14129
+ borderRadius: "50%",
14130
+ background: bg,
14131
+ color,
14132
+ display: "inline-flex",
14133
+ alignItems: "center",
14134
+ justifyContent: "center",
14135
+ fontSize: "0.95rem",
14136
+ fontWeight: 700
13497
14137
  });
13498
- var shimmerInfoStyle = {
14138
+ var limitEditLabelStyle = (color, weight) => ({
14139
+ fontSize: "1rem",
14140
+ fontWeight: weight,
14141
+ lineHeight: "normal",
14142
+ color,
14143
+ whiteSpace: "nowrap",
14144
+ flexShrink: 0
14145
+ });
14146
+ var limitEditRightStyle = {
14147
+ marginLeft: "auto",
13499
14148
  display: "flex",
13500
- flexDirection: "column",
13501
- gap: 6,
13502
- flex: 1,
14149
+ alignItems: "center",
14150
+ gap: 8,
13503
14151
  minWidth: 0
13504
14152
  };
13505
- var shimmerLineStyle = (width, baseColor, highlightColor) => ({
13506
- width,
13507
- height: 12,
13508
- borderRadius: 6,
13509
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13510
- backgroundSize: "200% 100%",
13511
- animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14153
+ var limitAmountStyle = (color, weight) => ({
14154
+ display: "inline-flex",
14155
+ alignItems: "baseline",
14156
+ gap: 1,
14157
+ color,
14158
+ fontSize: "1rem",
14159
+ fontWeight: weight
13512
14160
  });
13513
- var emptyStyle = (color) => ({
13514
- padding: "32px 16px",
13515
- textAlign: "center",
14161
+ var limitDollarStyle = (isZero, color) => ({
14162
+ opacity: isZero ? 0.5 : 1,
14163
+ color
14164
+ });
14165
+ var limitInputStyle = (color, mutedColor) => ({
14166
+ background: "transparent",
14167
+ border: "none",
14168
+ outline: "none",
13516
14169
  color,
13517
- fontSize: "0.92rem"
14170
+ font: "inherit",
14171
+ textAlign: "right",
14172
+ padding: 0,
14173
+ margin: 0,
14174
+ width: "auto",
14175
+ minWidth: "1ch",
14176
+ ...{ fieldSizing: "content" },
14177
+ fontVariantNumeric: "tabular-nums",
14178
+ caretColor: color,
14179
+ ["--link-limit-placeholder"]: mutedColor
13518
14180
  });
13519
- var bannerSlotStyle = {
13520
- display: "flex",
13521
- justifyContent: "center",
13522
- width: "100%"
13523
- };
13524
- var footerStackStyle4 = {
14181
+ var limitResetStyle = (borderColor, color, weight) => ({
14182
+ flexShrink: 0,
14183
+ padding: "5px 12px",
14184
+ borderRadius: 999,
14185
+ fontSize: "0.8rem",
14186
+ fontWeight: weight,
14187
+ fontFamily: "inherit",
14188
+ cursor: "pointer",
14189
+ border: `1.5px solid ${borderColor}`,
14190
+ background: "transparent",
14191
+ color
14192
+ });
14193
+ var infoCardStyle3 = (bg) => ({
13525
14194
  display: "flex",
13526
14195
  flexDirection: "column",
13527
14196
  gap: 16,
13528
- width: "100%"
13529
- };
13530
- var lockBannerStyle2 = {
14197
+ width: "100%",
14198
+ boxSizing: "border-box",
14199
+ padding: 16,
14200
+ borderRadius: 20,
14201
+ background: bg
14202
+ });
14203
+ var infoRowStyle2 = {
13531
14204
  display: "flex",
13532
14205
  alignItems: "flex-start",
13533
- gap: 16,
13534
- padding: "12px 16px 12px 12px",
13535
- borderRadius: 16,
13536
- width: "100%",
13537
- boxSizing: "border-box"
14206
+ gap: 14,
14207
+ width: "100%"
13538
14208
  };
13539
- var lockIconWrapStyle2 = (color) => ({
14209
+ var infoIconStyle = (color) => ({
13540
14210
  flexShrink: 0,
13541
14211
  width: 24,
13542
14212
  height: 24,
@@ -13545,14 +14215,60 @@ var lockIconWrapStyle2 = (color) => ({
13545
14215
  justifyContent: "center",
13546
14216
  color
13547
14217
  });
13548
- var lockBannerTextStyle2 = (color) => ({
13549
- margin: 0,
13550
- flex: 1,
13551
- fontSize: 12,
13552
- fontWeight: 500,
13553
- lineHeight: "normal",
13554
- color
14218
+ var infoTextColStyle = {
14219
+ display: "flex",
14220
+ flexDirection: "column",
14221
+ gap: 2,
14222
+ flex: 1,
14223
+ minWidth: 0
14224
+ };
14225
+ var infoTitleStyle2 = (color, weight) => ({
14226
+ fontSize: "1rem",
14227
+ fontWeight: weight,
14228
+ lineHeight: "normal",
14229
+ color
14230
+ });
14231
+ var infoBodyStyle = (color, weight) => ({
14232
+ fontSize: "0.85rem",
14233
+ fontWeight: weight,
14234
+ lineHeight: 1.4,
14235
+ color
14236
+ });
14237
+ var shimmerCircleStyle = (baseColor, highlightColor) => ({
14238
+ width: 40,
14239
+ height: 40,
14240
+ borderRadius: "50%",
14241
+ flexShrink: 0,
14242
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
14243
+ backgroundSize: "200% 100%",
14244
+ animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14245
+ });
14246
+ var shimmerInfoStyle = {
14247
+ display: "flex",
14248
+ flexDirection: "column",
14249
+ gap: 6,
14250
+ flex: 1,
14251
+ minWidth: 0
14252
+ };
14253
+ var shimmerLineStyle = (width, baseColor, highlightColor) => ({
14254
+ width,
14255
+ height: 12,
14256
+ borderRadius: 6,
14257
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
14258
+ backgroundSize: "200% 100%",
14259
+ animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14260
+ });
14261
+ var emptyStyle = (color) => ({
14262
+ padding: "32px 16px",
14263
+ textAlign: "center",
14264
+ color,
14265
+ fontSize: "0.92rem"
13555
14266
  });
14267
+ var bannerSlotStyle = {
14268
+ display: "flex",
14269
+ justifyContent: "center",
14270
+ width: "100%"
14271
+ };
13556
14272
  function DepositCompleteScreen({ amount }) {
13557
14273
  const { tokens } = useBlinkConfig();
13558
14274
  return /* @__PURE__ */ jsxRuntime.jsxs(ScreenLayout, { children: [
@@ -13614,6 +14330,7 @@ function SelectDepositSourceScreen({
13614
14330
  tokenOptions,
13615
14331
  selectedTokenSymbol,
13616
14332
  selectedChainName,
14333
+ balancesLoading,
13617
14334
  selectedWalletId,
13618
14335
  onPickToken,
13619
14336
  useDeeplink,
@@ -13630,8 +14347,9 @@ function SelectDepositSourceScreen({
13630
14347
  const [pendingMobileSelection, setPendingMobileSelection] = react.useState(null);
13631
14348
  const [preparedAuthorization, setPreparedAuthorization] = react.useState(null);
13632
14349
  const [preparingAuthorization, setPreparingAuthorization] = react.useState(false);
14350
+ const isAuthorizedOption = (opt) => !opt.status || opt.status === "AUTHORIZED";
13633
14351
  const selectableOptions = tokenOptions.filter(
13634
- (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
14352
+ (opt) => isAuthorizedOption(opt) || opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
13635
14353
  );
13636
14354
  const authorized = selectableOptions.filter(
13637
14355
  (opt) => !opt.status || opt.status === "AUTHORIZED"
@@ -13785,6 +14503,30 @@ function SelectDepositSourceScreen({
13785
14503
  "data-testid": "select-deposit-source-scroll-content",
13786
14504
  style: contentStyle12,
13787
14505
  children: [
14506
+ balancesLoading && tokenOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: accountSectionStyle, "data-testid": "select-source-shimmer", children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: groupCardStyle(tokens.bgRecessed), children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs(
14507
+ "div",
14508
+ {
14509
+ style: { display: "flex", alignItems: "center", gap: 12, padding: 12 },
14510
+ children: [
14511
+ /* @__PURE__ */ jsxRuntime.jsx(
14512
+ Shimmer,
14513
+ {
14514
+ width: 36,
14515
+ height: 36,
14516
+ borderRadius: "50%",
14517
+ baseColor: tokens.bgInput,
14518
+ highlightColor: tokens.border
14519
+ }
14520
+ ),
14521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: 1 }, children: [
14522
+ /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 96, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
14523
+ /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 56, height: 10, baseColor: tokens.bgInput, highlightColor: tokens.border })
14524
+ ] }),
14525
+ /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 48, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border })
14526
+ ]
14527
+ },
14528
+ `shimmer-row-${i}`
14529
+ )) }) }),
13788
14530
  orderedAccounts.map((account) => {
13789
14531
  const authRows = authorized.filter((opt) => rowMatchesSection(opt, account));
13790
14532
  const reqRows = requiresAuth.filter((opt) => rowMatchesSection(opt, account)).sort((a, b) => Number(!!a.notSupported) - Number(!!b.notSupported));
@@ -13999,6 +14741,7 @@ function ShimmerBlock({
13999
14741
  );
14000
14742
  }
14001
14743
  function DepositScreen({
14744
+ balancesLoading,
14002
14745
  availableBalance,
14003
14746
  remainingLimit,
14004
14747
  initialAmount,
@@ -14052,6 +14795,10 @@ function DepositScreen({
14052
14795
  const showMobileKeypad = isEntryMode && !isDesktop;
14053
14796
  const [keypadOpen, setKeypadOpen] = react.useState(showMobileKeypad);
14054
14797
  const [tokenPickerOpen, setTokenPickerOpen] = react.useState(false);
14798
+ const hasLoadedQuoteRef = react.useRef(false);
14799
+ if (!quoteLoading) {
14800
+ hasLoadedQuoteRef.current = true;
14801
+ }
14055
14802
  const selectableTokenOptions = tokenOptions?.filter(
14056
14803
  (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minDepositFloor)
14057
14804
  ) ?? [];
@@ -14069,11 +14816,11 @@ function DepositScreen({
14069
14816
  }, []);
14070
14817
  const selectedAccount = accounts?.find((a) => a.id === selectedAccountId);
14071
14818
  const selectedProviderName = selectedAccount?.name ?? "Wallet";
14072
- const isLowBalance = availableBalance < minDepositFloor;
14073
- const insufficientFunds = availableBalance < validationAmount;
14819
+ const isLowBalance = !balancesLoading && availableBalance < minDepositFloor;
14820
+ const insufficientFunds = !balancesLoading && availableBalance < validationAmount;
14074
14821
  const needsAuthorization = selectedTokenStatus != null && selectedTokenStatus !== "AUTHORIZED" && !insufficientFunds && !isLowBalance;
14075
14822
  const exceedsLimit = remainingLimit != null && validationAmount > remainingLimit && !isLowBalance && !needsAuthorization;
14076
- const canDeposit = validationAmount >= minDepositFloor && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14823
+ const canDeposit = validationAmount >= minDepositFloor && !balancesLoading && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14077
14824
  const hasAccountPill = !!accounts && accounts.length > 0;
14078
14825
  const pillClickable = canOpenInlineSheet || !!onSelectToken;
14079
14826
  const formattedBalance = selectedTokenSymbol != null ? `$${formatUsdTwoDecimals2(availableBalance)}` : void 0;
@@ -14096,6 +14843,7 @@ function DepositScreen({
14096
14843
  accounts: depositSourceAccounts,
14097
14844
  selectedAccountId: selectedAccountId ?? null,
14098
14845
  tokenOptions: selectableTokenOptions,
14846
+ balancesLoading,
14099
14847
  selectedTokenSymbol,
14100
14848
  selectedChainName,
14101
14849
  selectedWalletId: selectedWalletId ?? null,
@@ -14117,7 +14865,7 @@ function DepositScreen({
14117
14865
  const mobileEntryWithKeypad = isEntryMode && !isDesktop && keypadOpen;
14118
14866
  const showLiveMobileHero = isEntryMode && !isDesktop && keypadOpen;
14119
14867
  const showDesktopInputHero = isEntryMode && isDesktop;
14120
- const showFormattedHero = !showLiveMobileHero && !showDesktopInputHero;
14868
+ const showFeeRow = validationAmount > 0;
14121
14869
  let primaryButton;
14122
14870
  if (mobileEntryWithKeypad) {
14123
14871
  const canConfirmEntry = canContinue(amountEntryValue ?? "");
@@ -14247,7 +14995,32 @@ function DepositScreen({
14247
14995
  "aria-label": tokenAriaLabel,
14248
14996
  "aria-expanded": canOpenInlineSheet ? tokenPickerOpen : void 0,
14249
14997
  "aria-haspopup": canOpenInlineSheet ? "listbox" : void 0,
14250
- children: /* @__PURE__ */ jsxRuntime.jsx(
14998
+ children: balancesLoading ? (
14999
+ // Until balances merge in we don't know the selected token or its
15000
+ // balance, so the whole pill (icon + name + balance) is a shimmer
15001
+ // placeholder rather than showing the account name as a default.
15002
+ // Render through SourcePill so the placeholder inherits the exact
15003
+ // same container (fill, radius, padding, chevron) as the loaded
15004
+ // pill and does not change size when balances arrive.
15005
+ /* @__PURE__ */ jsxRuntime.jsx(
15006
+ "span",
15007
+ {
15008
+ "data-testid": "deposit-pill-shimmer",
15009
+ "aria-label": "Loading balance",
15010
+ "aria-busy": "true",
15011
+ style: { display: "inline-flex" },
15012
+ children: /* @__PURE__ */ jsxRuntime.jsx(
15013
+ SourcePill,
15014
+ {
15015
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 28, height: 28, borderRadius: "50%", baseColor: tokens.bgInput, highlightColor: tokens.border }),
15016
+ nameSlot: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 72, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
15017
+ balanceSlot: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { width: 48, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
15018
+ showChevron: pillClickable
15019
+ }
15020
+ )
15021
+ }
15022
+ )
15023
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
14251
15024
  SourcePill,
14252
15025
  {
14253
15026
  icon: /* @__PURE__ */ jsxRuntime.jsx(
@@ -14267,7 +15040,7 @@ function DepositScreen({
14267
15040
  )
14268
15041
  }
14269
15042
  ),
14270
- showFormattedHero && /* @__PURE__ */ jsxRuntime.jsx("div", { style: redesignFeeRowStyle, children: quoteLoading ? /* @__PURE__ */ jsxRuntime.jsx(
15043
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "deposit-fee-row", style: redesignFeeRowStyle, children: showFeeRow && (quoteLoading && !hasLoadedQuoteRef.current ? /* @__PURE__ */ jsxRuntime.jsx(
14271
15044
  "div",
14272
15045
  {
14273
15046
  "data-testid": "deposit-fee-shimmer",
@@ -14285,7 +15058,7 @@ function DepositScreen({
14285
15058
  }
14286
15059
  )
14287
15060
  }
14288
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" }) })
15061
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" })) })
14289
15062
  ] }),
14290
15063
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: bannerSlotStyle2, children: mobileEntryWithKeypad ? null : showLowFunds ? /* @__PURE__ */ jsxRuntime.jsx(
14291
15064
  NotificationBanner,
@@ -15662,7 +16435,7 @@ function OpenWalletScreen({
15662
16435
  return /* @__PURE__ */ jsxRuntime.jsxs(
15663
16436
  ScreenLayout,
15664
16437
  {
15665
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
16438
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
15666
16439
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
15667
16440
  /* @__PURE__ */ jsxRuntime.jsxs(
15668
16441
  SecondaryButton,
@@ -15709,7 +16482,7 @@ function OpenWalletScreen({
15709
16482
  return /* @__PURE__ */ jsxRuntime.jsxs(
15710
16483
  ScreenLayout,
15711
16484
  {
15712
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
16485
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
15713
16486
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
15714
16487
  /* @__PURE__ */ jsxRuntime.jsxs(
15715
16488
  SecondaryButton,
@@ -15793,7 +16566,7 @@ var primaryClusterStyle = {
15793
16566
  textAlign: "center",
15794
16567
  gap: 12
15795
16568
  };
15796
- var footerStackStyle5 = {
16569
+ var footerStackStyle4 = {
15797
16570
  display: "flex",
15798
16571
  flexDirection: "column",
15799
16572
  gap: 8
@@ -15921,6 +16694,12 @@ function ApprovingInWalletScreen({
15921
16694
  chainName,
15922
16695
  step,
15923
16696
  complete,
16697
+ signing,
16698
+ spendingLimitLabel,
16699
+ destinationAddress,
16700
+ tokenLogoUri,
16701
+ awaitingApproval,
16702
+ onApprove,
15924
16703
  error,
15925
16704
  onRetry,
15926
16705
  onBack,
@@ -15930,10 +16709,11 @@ function ApprovingInWalletScreen({
15930
16709
  onOpenWallet
15931
16710
  }) {
15932
16711
  const { tokens: t } = useBlinkConfig();
15933
- const showDelayedRetry = useDelayedRetry(error == null && onRetry != null);
16712
+ const showDelayedRetry = useDelayedRetry(!awaitingApproval && error == null && onRetry != null);
15934
16713
  const retryVisible = onRetry != null && (error != null || showDelayedRetry);
15935
16714
  const token = tokenSymbol ?? "token";
15936
- const steps = buildSteps(step, tokenSymbol, chainName, complete);
16715
+ const chain = chainName ?? "chain";
16716
+ const steps = buildSteps(step, tokenSymbol, complete, signing);
15937
16717
  const foregroundNavigation = foregroundDeeplink ? classifyWalletDeeplinkNavigation(foregroundDeeplink) : null;
15938
16718
  const foregroundWithJs = foregroundNavigation ? shouldOpenWithJavaScript(foregroundNavigation) : false;
15939
16719
  const showOpenWalletButton = !!foregroundDeeplink && onOpenWallet != null;
@@ -15941,11 +16721,7 @@ function ApprovingInWalletScreen({
15941
16721
  ScreenLayout,
15942
16722
  {
15943
16723
  scrollableBody: false,
15944
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle6, children: [
15945
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: lockBannerStyle3, children: [
15946
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: lockIconWrapStyle3(t.text), children: /* @__PURE__ */ jsxRuntime.jsx(LockIcon4, {}) }),
15947
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: lockBannerTextStyle3(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
15948
- ] }),
16724
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
15949
16725
  showOpenWalletButton && /* @__PURE__ */ jsxRuntime.jsxs(
15950
16726
  SecondaryButton,
15951
16727
  {
@@ -15962,7 +16738,7 @@ function ApprovingInWalletScreen({
15962
16738
  retryVisible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: retryStackStyle, children: [
15963
16739
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
15964
16740
  /* @__PURE__ */ jsxRuntime.jsx(OutlineButton, { onClick: onRetry, children: "Retry" })
15965
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { spinnerOnly: true })
16741
+ ] }) : awaitingApproval ? /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onApprove, children: "Approve" }) : /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { spinnerOnly: true })
15966
16742
  ] }),
15967
16743
  children: [
15968
16744
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -15974,7 +16750,39 @@ function ApprovingInWalletScreen({
15974
16750
  }
15975
16751
  ),
15976
16752
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle16, children: [
15977
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle13(t.text), children: `Authorizing ${token} for passkey deposits` }),
16753
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle13(t.text, t.fontWeightBold), children: "Approve in wallet" }),
16754
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: transferCardStyle(t.bgCardTranslucent, t.radiusLg), children: [
16755
+ /* @__PURE__ */ jsxRuntime.jsx(
16756
+ AddressRow,
16757
+ {
16758
+ circle: /* @__PURE__ */ jsxRuntime.jsx(
16759
+ LogoCircle,
16760
+ {
16761
+ src: tokenLogoUri ?? TOKEN_LOGOS[token] ?? void 0,
16762
+ fallback: token.slice(0, 1).toUpperCase(),
16763
+ size: 40
16764
+ }
16765
+ ),
16766
+ title: `${token} on ${chain}`,
16767
+ subtitle: spendingLimitLabel
16768
+ }
16769
+ ),
16770
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: arrowRowStyle, children: /* @__PURE__ */ jsxRuntime.jsx(ArrowDownIcon, { color: t.textMuted }) }),
16771
+ /* @__PURE__ */ jsxRuntime.jsx(
16772
+ AddressRow,
16773
+ {
16774
+ circle: /* @__PURE__ */ jsxRuntime.jsx("span", { style: passkeyCircleStyle(t.accent, t.highlight), children: /* @__PURE__ */ jsxRuntime.jsx(KeyIcon, { size: 22 }) }),
16775
+ title: "Your Blink Passkey",
16776
+ address: destinationAddress,
16777
+ addressLoading: destinationAddress == null
16778
+ }
16779
+ )
16780
+ ] }),
16781
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: dividerRowStyle3, children: [
16782
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: dividerLineStyle(t.textMuted) }),
16783
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: dividerLabelStyle3(t.textMuted, t.fontWeightSemibold), children: "Continue in wallet" }),
16784
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: dividerLineStyle(t.textMuted) })
16785
+ ] }),
15978
16786
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle2(t.bgRecessed, t.radiusLg), children: [
15979
16787
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: SPIN_KEYFRAMES_CSS2 }),
15980
16788
  steps.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(ChecklistRow, { step: s }, i))
@@ -15984,6 +16792,32 @@ function ApprovingInWalletScreen({
15984
16792
  }
15985
16793
  );
15986
16794
  }
16795
+ function AddressRow({
16796
+ circle,
16797
+ title,
16798
+ address,
16799
+ subtitle,
16800
+ addressLoading
16801
+ }) {
16802
+ const { tokens: t } = useBlinkConfig();
16803
+ const sub = subtitle ?? (address ? truncateMiddle(address) : null);
16804
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: addressRowStyle, children: [
16805
+ circle,
16806
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: addressTextColStyle, children: [
16807
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: addressTitleStyle(t.text, t.fontWeightSemibold), children: title }),
16808
+ (sub || addressLoading) && /* @__PURE__ */ jsxRuntime.jsx("span", { style: addressSubSlotStyle, children: sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: addressSubStyle(t.textMuted, t.fontWeightRegular), children: sub }) : /* @__PURE__ */ jsxRuntime.jsx(
16809
+ Shimmer,
16810
+ {
16811
+ width: 132,
16812
+ height: 12,
16813
+ borderRadius: 4,
16814
+ baseColor: t.bgInput,
16815
+ highlightColor: t.border
16816
+ }
16817
+ ) })
16818
+ ] })
16819
+ ] });
16820
+ }
15987
16821
  function ChecklistRow({ step }) {
15988
16822
  const { tokens: t } = useBlinkConfig();
15989
16823
  const isComplete = step.status === "complete";
@@ -15996,7 +16830,7 @@ function ChecklistRow({ step }) {
15996
16830
  fill: t.accentText
15997
16831
  }
15998
16832
  ) }) }) : isActive ? /* @__PURE__ */ jsxRuntime.jsx(RowSpinner, { color: t.accent }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: pendingBadgeStyle(t.bgRecessed) }),
15999
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: labelStyle7(isComplete || isActive ? t.text : t.textMuted), children: step.label })
16833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: labelStyle7(isComplete || isActive ? t.text : t.textMuted, t.fontWeightRegular), children: step.label })
16000
16834
  ] });
16001
16835
  }
16002
16836
  function RowSpinner({ color }) {
@@ -16031,22 +16865,28 @@ function RowSpinner({ color }) {
16031
16865
  }
16032
16866
  );
16033
16867
  }
16034
- function buildSteps(step, tokenSymbol, chainName, complete) {
16868
+ function ArrowDownIcon({ color }) {
16869
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: { color }, children: [
16870
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
16871
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 13l6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
16872
+ ] });
16873
+ }
16874
+ function buildSteps(step, tokenSymbol, complete, signing) {
16035
16875
  const token = tokenSymbol ?? "token";
16036
- const chain = chainName ?? "chain";
16037
- const passkeyLabel = `Approve ${token} for passkey`;
16876
+ const passkeyLabel = "Assign spending right to your passkey";
16877
+ const activeStatus = signing ? "active" : "pending";
16038
16878
  if (step === "spl") {
16039
- return [{ label: passkeyLabel, status: complete ? "complete" : "active" }];
16879
+ return [{ label: passkeyLabel, status: complete ? "complete" : activeStatus }];
16040
16880
  }
16041
- const approveLabel = `Approve ${token} on ${chain}`;
16881
+ const approveLabel = `Approve ${token} for spending via Permit2`;
16042
16882
  return [
16043
16883
  {
16044
16884
  label: approveLabel,
16045
- status: complete || step !== "approve" ? "complete" : "active"
16885
+ status: complete || step !== "approve" ? "complete" : activeStatus
16046
16886
  },
16047
16887
  {
16048
16888
  label: passkeyLabel,
16049
- status: complete ? "complete" : step === "approve" ? "pending" : "active"
16889
+ status: complete ? "complete" : step === "approve" ? "pending" : activeStatus
16050
16890
  }
16051
16891
  ];
16052
16892
  }
@@ -16062,18 +16902,6 @@ function useDelayedRetry(enabled) {
16062
16902
  }, [enabled]);
16063
16903
  return visible;
16064
16904
  }
16065
- function LockIcon4() {
16066
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
16067
- "path",
16068
- {
16069
- d: "M7 10V8a5 5 0 0 1 10 0v2m-12 0h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1z",
16070
- stroke: "currentColor",
16071
- strokeWidth: "2",
16072
- strokeLinecap: "round",
16073
- strokeLinejoin: "round"
16074
- }
16075
- ) });
16076
- }
16077
16905
  var wordmarkImgStyle5 = {
16078
16906
  height: 22,
16079
16907
  width: "auto",
@@ -16091,15 +16919,84 @@ var contentStyle16 = {
16091
16919
  minHeight: 0,
16092
16920
  width: "100%"
16093
16921
  };
16094
- var headingStyle13 = (color) => ({
16922
+ var headingStyle13 = (color, fontWeight) => ({
16095
16923
  fontSize: 24,
16096
- fontWeight: 700,
16924
+ fontWeight,
16097
16925
  lineHeight: "normal",
16098
16926
  letterSpacing: 0,
16099
16927
  color,
16100
- margin: 0,
16928
+ margin: "8px 0 8px",
16101
16929
  textAlign: "center"
16102
16930
  });
16931
+ var transferCardStyle = (bg, radius) => ({
16932
+ background: bg,
16933
+ borderRadius: radius,
16934
+ padding: "20px 16px",
16935
+ width: "100%",
16936
+ boxSizing: "border-box",
16937
+ display: "flex",
16938
+ flex: 1,
16939
+ flexDirection: "column",
16940
+ justifyContent: "space-between",
16941
+ gap: 12
16942
+ });
16943
+ var dividerRowStyle3 = {
16944
+ display: "flex",
16945
+ alignItems: "center",
16946
+ gap: 12,
16947
+ width: "100%"
16948
+ };
16949
+ var dividerLineStyle = (color) => ({
16950
+ flex: 1,
16951
+ height: 1,
16952
+ background: color
16953
+ });
16954
+ var dividerLabelStyle3 = (color, fontWeight) => ({
16955
+ fontSize: 11,
16956
+ fontWeight,
16957
+ letterSpacing: "0.08em",
16958
+ textTransform: "uppercase",
16959
+ color,
16960
+ whiteSpace: "nowrap"
16961
+ });
16962
+ var addressRowStyle = {
16963
+ display: "flex",
16964
+ alignItems: "center",
16965
+ gap: 12,
16966
+ width: "100%"
16967
+ };
16968
+ var addressTextColStyle = {
16969
+ display: "flex",
16970
+ flexDirection: "column",
16971
+ gap: 2,
16972
+ minWidth: 0
16973
+ };
16974
+ var addressTitleStyle = (color, fontWeight) => ({
16975
+ fontSize: 16,
16976
+ fontWeight,
16977
+ lineHeight: 1.25,
16978
+ color
16979
+ });
16980
+ var addressSubSlotStyle = {
16981
+ display: "flex",
16982
+ alignItems: "center",
16983
+ minHeight: 18
16984
+ };
16985
+ var addressSubStyle = (color, fontWeight) => ({
16986
+ fontSize: 14,
16987
+ fontWeight,
16988
+ lineHeight: 1.25,
16989
+ color,
16990
+ overflow: "hidden",
16991
+ textOverflow: "ellipsis",
16992
+ whiteSpace: "nowrap"
16993
+ });
16994
+ var arrowRowStyle = {
16995
+ display: "flex",
16996
+ alignItems: "center",
16997
+ height: 20,
16998
+ paddingLeft: 10
16999
+ };
16103
17000
  var cardStyle2 = (bg, radius) => ({
16104
17001
  background: bg,
16105
17002
  borderRadius: radius,
@@ -16107,7 +17004,9 @@ var cardStyle2 = (bg, radius) => ({
16107
17004
  width: "100%",
16108
17005
  boxSizing: "border-box",
16109
17006
  display: "flex",
17007
+ flex: 1,
16110
17008
  flexDirection: "column",
17009
+ justifyContent: "center",
16111
17010
  gap: 4
16112
17011
  });
16113
17012
  var rowStyle6 = {
@@ -16115,10 +17014,25 @@ var rowStyle6 = {
16115
17014
  alignItems: "center",
16116
17015
  gap: 16,
16117
17016
  padding: "12px 16px 12px 12px",
17017
+ // Reserve a stable height (two label lines + vertical padding) so swapping the
17018
+ // leading badge between the pending circle, the spinner, and the completion
17019
+ // checkmark — and the muted→active label restyle — never reflows the card.
17020
+ minHeight: 64,
16118
17021
  borderRadius: 16,
16119
17022
  width: "100%",
16120
17023
  boxSizing: "border-box"
16121
17024
  };
17025
+ var passkeyCircleStyle = (bg, iconColor) => ({
17026
+ width: 40,
17027
+ height: 40,
17028
+ borderRadius: "50%",
17029
+ background: bg,
17030
+ color: iconColor,
17031
+ display: "inline-flex",
17032
+ alignItems: "center",
17033
+ justifyContent: "center",
17034
+ flexShrink: 0
17035
+ });
16122
17036
  var completeBadgeStyle = (color) => ({
16123
17037
  width: 24,
16124
17038
  height: 24,
@@ -16136,13 +17050,13 @@ var pendingBadgeStyle = (color) => ({
16136
17050
  background: color,
16137
17051
  flexShrink: 0
16138
17052
  });
16139
- var labelStyle7 = (color) => ({
17053
+ var labelStyle7 = (color, fontWeight) => ({
16140
17054
  fontSize: "1rem",
16141
- fontWeight: 400,
17055
+ fontWeight,
16142
17056
  lineHeight: 1.25,
16143
17057
  color
16144
17058
  });
16145
- var footerStackStyle6 = {
17059
+ var footerStackStyle5 = {
16146
17060
  display: "flex",
16147
17061
  flexDirection: "column",
16148
17062
  gap: 16,
@@ -16154,32 +17068,6 @@ var retryStackStyle = {
16154
17068
  gap: 8,
16155
17069
  width: "100%"
16156
17070
  };
16157
- var lockBannerStyle3 = {
16158
- display: "flex",
16159
- alignItems: "flex-start",
16160
- gap: 16,
16161
- padding: "12px 16px 12px 12px",
16162
- borderRadius: 16,
16163
- width: "100%",
16164
- boxSizing: "border-box"
16165
- };
16166
- var lockIconWrapStyle3 = (color) => ({
16167
- flexShrink: 0,
16168
- width: 24,
16169
- height: 24,
16170
- display: "inline-flex",
16171
- alignItems: "center",
16172
- justifyContent: "center",
16173
- color
16174
- });
16175
- var lockBannerTextStyle3 = (color) => ({
16176
- margin: 0,
16177
- flex: 1,
16178
- fontSize: 12,
16179
- fontWeight: 500,
16180
- lineHeight: "normal",
16181
- color
16182
- });
16183
17071
  function ConfirmSignScreen({
16184
17072
  walletName,
16185
17073
  chainFamily,
@@ -16305,7 +17193,7 @@ function TokenPickerScreen({
16305
17193
  }
16306
17194
  const entries2 = [];
16307
17195
  for (const wallet of account.wallets) {
16308
- for (const source of wallet.sources) {
17196
+ for (const source of wallet.sources ?? []) {
16309
17197
  const visibleBalance = source.balance.available.amount;
16310
17198
  if (!isSelectableDepositSourceAmountUsd(visibleBalance, minDepositFloor)) continue;
16311
17199
  entries2.push({
@@ -17146,22 +18034,45 @@ function buildOpenWalletScreenProps({
17146
18034
  function isApprovingInWalletAction(actionType) {
17147
18035
  return actionType === "APPROVE_PERMIT2" || actionType === "SIGN_PERMIT2" || actionType === "APPROVE_SPL";
17148
18036
  }
18037
+ function formatUsdTwoDecimals3(value) {
18038
+ return (Number.isFinite(value) ? value : 0).toLocaleString("en-US", {
18039
+ minimumFractionDigits: 2,
18040
+ maximumFractionDigits: 2
18041
+ });
18042
+ }
18043
+ function readMetadataString(metadata, key) {
18044
+ const value = metadata?.[key];
18045
+ return typeof value === "string" && value.trim() !== "" ? value : null;
18046
+ }
17149
18047
  function buildApprovingInWalletScreenProps({
17150
18048
  flow,
17151
18049
  remote,
18050
+ derived,
17152
18051
  handlers
17153
18052
  }) {
17154
18053
  const { state } = flow;
17155
18054
  const setupToken = state.setupDepositToken;
17156
- const actionType = remote.authExecutorCurrentAction?.type ?? null;
18055
+ const currentAction = remote.authExecutorCurrentAction;
18056
+ const actionType = currentAction?.type ?? null;
18057
+ const limit = state.setupSpendingLimit;
18058
+ const spendingLimitLabel = limit == null ? null : "unlimited" in limit ? "Unlimited lifetime spending limit" : `$${formatUsdTwoDecimals3(limit.usd)} lifetime spending limit`;
18059
+ const destinationAddress = remote.authApprovalSmartAccountAddress ?? remote.authExecutorApprovalDestinationAddress ?? readMetadataString(currentAction?.metadata, "smartAccountAddress") ?? readMetadataString(currentAction?.metadata, "ownerPubkey");
17157
18060
  const isSolana = setupToken != null && state.chains.find((c) => c.name === setupToken.chainName)?.chainFamily === "svm";
17158
18061
  const step = actionType === "SIGN_PERMIT2" ? "sign" : actionType === "APPROVE_SPL" ? "spl" : actionType == null && isSolana ? "spl" : "approve";
17159
18062
  const complete = remote.authExecutorCompleted && actionType == null && !remote.authExecutorExecuting;
18063
+ const awaitingApproval = remote.authExecutorAwaitingApproval ?? false;
18064
+ const signing = isApprovingInWalletAction(actionType) && !awaitingApproval && !complete;
17160
18065
  return {
17161
18066
  tokenSymbol: setupToken?.symbol ?? null,
17162
18067
  chainName: setupToken?.chainName ?? null,
17163
18068
  step,
17164
18069
  complete,
18070
+ signing,
18071
+ spendingLimitLabel,
18072
+ destinationAddress,
18073
+ tokenLogoUri: derived.selectedSource?.token.logoURI ?? null,
18074
+ awaitingApproval,
18075
+ onApprove: handlers.onApprove,
17165
18076
  error: flow.state.error || remote.authExecutorError,
17166
18077
  onRetry: handlers.onRetryAuthorization,
17167
18078
  onLogout: flow.isDesktop ? handlers.onLogout : void 0,
@@ -17180,7 +18091,7 @@ function buildLinkTokensScreenProps({
17180
18091
  handlers
17181
18092
  }) {
17182
18093
  const sourceOptions = remote.pendingSelectSource?.metadata?.options ?? [];
17183
- const rowContexts = derived.selectSourceChoices.flatMap(
18094
+ const supportedRows = derived.selectSourceChoices.flatMap(
17184
18095
  (chain) => chain.tokens.map((t) => {
17185
18096
  const rawOption = sourceOptions.find(
17186
18097
  (option) => option.chainName === chain.chainName && option.tokenSymbol === t.tokenSymbol
@@ -17200,6 +18111,10 @@ function buildLinkTokensScreenProps({
17200
18111
  };
17201
18112
  })
17202
18113
  );
18114
+ const rowContexts = [
18115
+ ...supportedRows.filter((row) => isVisibleUsdAmountAtTwoDecimals(row.balanceUsd)),
18116
+ ...supportedRows.filter((row) => !isVisibleUsdAmountAtTwoDecimals(row.balanceUsd))
18117
+ ];
17203
18118
  rowContexts.push(
17204
18119
  ...derived.selectSourceUnsupportedChoices.flatMap(
17205
18120
  (chain) => chain.tokens.map((t) => ({
@@ -17221,6 +18136,17 @@ function buildLinkTokensScreenProps({
17221
18136
  balanceLabel: `${formatNativeAmount(native.amount)} ${native.tokenSymbol}`
17222
18137
  }))
17223
18138
  );
18139
+ const selectedTokenSymbol = flow.state.setupDepositToken?.symbol ?? derived.selectSourceRecommended?.tokenSymbol;
18140
+ const selectedChainName = flow.state.setupDepositToken?.chainName ?? derived.selectSourceRecommended?.chainName;
18141
+ if (selectedTokenSymbol && selectedChainName) {
18142
+ const match = rowContexts.findIndex(
18143
+ (row) => row.symbol === selectedTokenSymbol && row.chainName === selectedChainName
18144
+ );
18145
+ if (match >= 0 && !rowContexts[match].notSupported) {
18146
+ const [pinned] = rowContexts.splice(match, 1);
18147
+ rowContexts.unshift(pinned);
18148
+ }
18149
+ }
17224
18150
  const entries2 = rowContexts.map(
17225
18151
  ({ symbol, chainName, balanceUsd, notSupported, tokenLogoUri, balanceLabel }) => ({
17226
18152
  tokenSymbol: symbol,
@@ -17233,8 +18159,6 @@ function buildLinkTokensScreenProps({
17233
18159
  })
17234
18160
  );
17235
18161
  const firstSupportedIndex = rowContexts.findIndex((row) => !row.notSupported);
17236
- const selectedTokenSymbol = flow.state.setupDepositToken?.symbol ?? derived.selectSourceRecommended?.tokenSymbol;
17237
- const selectedChainName = flow.state.setupDepositToken?.chainName ?? derived.selectSourceRecommended?.chainName;
17238
18162
  const selectedIndex = (() => {
17239
18163
  if (!selectedTokenSymbol || !selectedChainName) {
17240
18164
  return firstSupportedIndex;
@@ -17326,9 +18250,10 @@ function buildDepositScreenProps({
17326
18250
  const registryChainIds = derived.walletConnectChainIdsByAccount[account.id] ?? null;
17327
18251
  const approvedChainIds = account.walletConnect?.approvedChainIds ?? null;
17328
18252
  for (const wallet of account.wallets) {
17329
- for (const source of wallet.sources) {
18253
+ for (const source of wallet.sources ?? []) {
17330
18254
  const balance = source.balance.available.amount;
17331
- if (!isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
18255
+ const isAuthorized = source.token.status === "AUTHORIZED";
18256
+ if (!isAuthorized && !isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
17332
18257
  const chain = flow.state.chains.find((c) => c.name === wallet.chain.name);
17333
18258
  const requiresAuth = source.token.status != null && source.token.status !== "AUTHORIZED";
17334
18259
  const notSupported = requiresAuth && chain?.commonId != null && registryChainIds != null && !registryChainIds.includes(chain.commonId) && !(approvedChainIds?.includes(chain.commonId) ?? false);
@@ -17350,7 +18275,10 @@ function buildDepositScreenProps({
17350
18275
  }
17351
18276
  return {
17352
18277
  merchantName,
17353
- availableBalance: selectedSource ? selectedSource.balance.available.amount : selectedAccount ? selectedAccount.wallets.reduce((sum, w) => sum + w.balance.available.amount, 0) : maxSourceBalance,
18278
+ // Wallet balances arrive after the (balance-free) accounts load; while they
18279
+ // do, the source pill / token rows shimmer instead of showing $0.00.
18280
+ balancesLoading: state.balancesLoading,
18281
+ availableBalance: selectedSource ? selectedSource.balance.available.amount : selectedAccount ? selectedAccount.wallets.reduce((sum, w) => sum + (w.balance?.available.amount ?? 0), 0) : maxSourceBalance,
17354
18282
  remainingLimit: selectedSource != null ? selectedSource.remainingAllowance ?? null : selectedAccount?.remainingAllowance ?? null,
17355
18283
  tokenCount,
17356
18284
  initialAmount: parsedAmt,
@@ -17541,6 +18469,28 @@ function StepRendererContent({
17541
18469
  screen
17542
18470
  }) {
17543
18471
  const input = { flow, remote, derived, forms, handlers };
18472
+ const s = flow.state;
18473
+ react.useEffect(() => {
18474
+ if (screen !== "loading") return;
18475
+ appendDebug("warn", "stuck on loading shimmer", {
18476
+ phaseStep: s.phase.step,
18477
+ privyReady: s.privyReady,
18478
+ privyAuthenticated: s.privyAuthenticated,
18479
+ activeCredentialId: s.activeCredentialId,
18480
+ passkeyConfigLoaded: s.passkeyConfigLoaded,
18481
+ loadingData: s.loadingData,
18482
+ initialDataLoaded: s.initialDataLoaded,
18483
+ loginRequested: s.loginRequested,
18484
+ enableFullWidget: s.enableFullWidget,
18485
+ authenticatedOnOpen: s.authenticatedOnOpen,
18486
+ welcomeBackAcknowledged: s.welcomeBackAcknowledged,
18487
+ mobileFlow: s.mobileFlow,
18488
+ error: s.error,
18489
+ accountsCount: s.accounts.length,
18490
+ hasActiveWallet: s.accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE")),
18491
+ providersCount: s.providers.length
18492
+ });
18493
+ }, [screen, s]);
17544
18494
  switch (screen) {
17545
18495
  case "loading":
17546
18496
  return /* @__PURE__ */ jsxRuntime.jsx(BlinkLoadingScreen, {});
@@ -17569,11 +18519,9 @@ function StepRendererContent({
17569
18519
  case "wallet-picker":
17570
18520
  return /* @__PURE__ */ jsxRuntime.jsx(WalletPickerScreen, { ...buildWalletPickerScreenProps(input) });
17571
18521
  case "open-wallet": {
17572
- const currentActionType = input.remote.authExecutorCurrentAction?.type;
17573
- const inSigningAction = isApprovingInWalletAction(currentActionType);
17574
- const pastPairingTransient = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && currentActionType != null && (currentActionType !== "OPEN_PROVIDER" || input.remote.sourceSelectionResolved);
18522
+ const pastSourceSelection = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && input.remote.sourceSelectionResolved;
17575
18523
  const settlingPostSign = input.flow.state.linkSettling;
17576
- if (inSigningAction || pastPairingTransient || settlingPostSign) {
18524
+ if (pastSourceSelection || settlingPostSign) {
17577
18525
  return /* @__PURE__ */ jsxRuntime.jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17578
18526
  }
17579
18527
  return /* @__PURE__ */ jsxRuntime.jsx(OpenWalletScreen, { ...buildOpenWalletScreenProps(input) });
@@ -17583,12 +18531,9 @@ function StepRendererContent({
17583
18531
  if (phase.step === "wallet-setup" && phase.pendingSourceWait) {
17584
18532
  return /* @__PURE__ */ jsxRuntime.jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
17585
18533
  }
17586
- const inSigningAction = isApprovingInWalletAction(
17587
- input.remote.authExecutorCurrentAction?.type
17588
- );
17589
- const justResolvedSelectSource = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null;
18534
+ const pastSourceSelection = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && input.remote.sourceSelectionResolved;
17590
18535
  const settlingPostSign = input.flow.state.linkSettling;
17591
- if (inSigningAction || justResolvedSelectSource || settlingPostSign) {
18536
+ if (pastSourceSelection || settlingPostSign) {
17592
18537
  return /* @__PURE__ */ jsxRuntime.jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17593
18538
  }
17594
18539
  return /* @__PURE__ */ jsxRuntime.jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
@@ -17672,11 +18617,12 @@ var PaymentErrorBoundary = class extends react.Component {
17672
18617
  };
17673
18618
  function selectedSourceForWallet(selectedWallet, selectedTokenSymbol, depositAmount, priorityContext) {
17674
18619
  if (!selectedWallet) return null;
18620
+ const walletSources = selectedWallet.sources ?? [];
17675
18621
  if (selectedTokenSymbol) {
17676
- return selectedWallet.sources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
18622
+ return walletSources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
17677
18623
  }
17678
18624
  const walletChainName = selectedWallet.chain.name;
17679
- const ranked = selectedWallet.sources.map((source, index) => {
18625
+ const ranked = walletSources.map((source, index) => {
17680
18626
  return { source, index };
17681
18627
  }).sort((a, b) => {
17682
18628
  const priority = compareDepositSourcePriority(
@@ -17724,7 +18670,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17724
18670
  let maxSourceBalance = 0;
17725
18671
  for (const acct of state.accounts) {
17726
18672
  for (const wallet of acct.wallets) {
17727
- for (const source of wallet.sources) {
18673
+ for (const source of wallet.sources ?? []) {
17728
18674
  if (source.balance.available.amount > maxSourceBalance) {
17729
18675
  maxSourceBalance = source.balance.available.amount;
17730
18676
  }
@@ -17734,7 +18680,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17734
18680
  let tokenCount = 0;
17735
18681
  for (const acct of state.accounts) {
17736
18682
  for (const wallet of acct.wallets) {
17737
- tokenCount += wallet.sources.filter((s) => s.balance.available.amount > 0).length;
18683
+ tokenCount += (wallet.sources ?? []).filter((s) => s.balance.available.amount > 0).length;
17738
18684
  }
17739
18685
  }
17740
18686
  return {
@@ -17827,6 +18773,7 @@ function useAuthHandlers(dispatch, apiBaseUrl, popupAuthRef) {
17827
18773
  dispatch({ type: "PASSKEY_ACTIVATED", credentialId: result.credentialId, publicKey: result.publicKey });
17828
18774
  dispatch({ type: "SYNC_PRIVY_SESSION", ready: true, authenticated: true });
17829
18775
  window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, result.credentialId);
18776
+ track("login_succeeded", { method: "passkey", popup: true });
17830
18777
  } catch (err) {
17831
18778
  captureException(err);
17832
18779
  dispatch({
@@ -17839,6 +18786,7 @@ function useAuthHandlers(dispatch, apiBaseUrl, popupAuthRef) {
17839
18786
  }, [dispatch, popupAuthRef]);
17840
18787
  const { loginWithPasskey, state: loginState } = reactAuth.useLoginWithPasskey({
17841
18788
  onComplete: (params) => {
18789
+ track("login_succeeded", { method: "passkey", popup: false });
17842
18790
  handleAuthComplete(params.user);
17843
18791
  },
17844
18792
  onError: (error) => {
@@ -17867,6 +18815,7 @@ function useAuthHandlers(dispatch, apiBaseUrl, popupAuthRef) {
17867
18815
  dispatch({ type: "PASSKEY_ACTIVATED", credentialId: result.credentialId, publicKey: result.publicKey });
17868
18816
  dispatch({ type: "SYNC_PRIVY_SESSION", ready: true, authenticated: true });
17869
18817
  window.localStorage.setItem(ACTIVE_CREDENTIAL_STORAGE_KEY, result.credentialId);
18818
+ track("signup_succeeded", { method: "passkey", popup: true });
17870
18819
  } catch (err) {
17871
18820
  captureException(err);
17872
18821
  dispatch({
@@ -17879,6 +18828,7 @@ function useAuthHandlers(dispatch, apiBaseUrl, popupAuthRef) {
17879
18828
  }, [dispatch, popupAuthRef]);
17880
18829
  const { signupWithPasskey, state: signupState } = reactAuth.useSignupWithPasskey({
17881
18830
  onComplete: (params) => {
18831
+ track("signup_succeeded", { method: "passkey", popup: false });
17882
18832
  handleAuthComplete(params.user);
17883
18833
  },
17884
18834
  onError: (error) => {
@@ -17950,6 +18900,31 @@ function usePasskeyHandlers(dispatch, apiBaseUrl) {
17950
18900
  };
17951
18901
  }
17952
18902
 
18903
+ // src/balancesLoad.ts
18904
+ async function fetchBalancesByAccountId(apiBaseUrl, token, credentialId, accounts) {
18905
+ const entries2 = await Promise.all(
18906
+ accounts.map(async (account) => {
18907
+ try {
18908
+ const balances = await fetchAccountBalances(
18909
+ apiBaseUrl,
18910
+ token,
18911
+ account.id,
18912
+ credentialId
18913
+ );
18914
+ return [account.id, balances];
18915
+ } catch (err) {
18916
+ captureException(err);
18917
+ return null;
18918
+ }
18919
+ })
18920
+ );
18921
+ const result = {};
18922
+ for (const entry of entries2) {
18923
+ if (entry) result[entry[0]] = entry[1];
18924
+ }
18925
+ return result;
18926
+ }
18927
+
17953
18928
  // src/transferSourceResolution.ts
17954
18929
  var STANDARD_TRANSFER_SOURCE_UNAVAILABLE_ERROR = "Selected payment source is no longer available. Choose or link an active wallet before depositing.";
17955
18930
  function resolveStandardTransferSource({
@@ -18001,14 +18976,14 @@ function useTransferHandlers(deps) {
18001
18976
  const processingStartedAtRef = react.useRef(null);
18002
18977
  const pollingTransferIdRef = react.useRef(null);
18003
18978
  const depositSelectionReloadCountRef = react.useRef(0);
18004
- const reloadAccounts = react.useCallback(async () => {
18979
+ const reloadAccounts = react.useCallback(async (opts) => {
18005
18980
  depositSelectionReloadCountRef.current += 1;
18006
18981
  if (depositSelectionReloadCountRef.current === 1) {
18007
18982
  dispatch({ type: "SET_DEPOSIT_SELECTION_REFRESHING", value: true });
18008
18983
  }
18009
18984
  try {
18010
18985
  const token = await getAccessToken();
18011
- if (!token || !activeCredentialId) return;
18986
+ if (!token || !activeCredentialId) return null;
18012
18987
  const [accts, prov] = await Promise.all([
18013
18988
  fetchAccounts(apiBaseUrl, token, activeCredentialId),
18014
18989
  fetchProviders(apiBaseUrl, token)
@@ -18032,6 +19007,20 @@ function useTransferHandlers(deps) {
18032
19007
  defaults,
18033
19008
  resetSelectedTokenSymbol
18034
19009
  });
19010
+ const balancesPromise = fetchBalancesByAccountId(
19011
+ apiBaseUrl,
19012
+ token,
19013
+ activeCredentialId,
19014
+ accts
19015
+ ).then((balancesByAccountId) => {
19016
+ dispatch({ type: "BALANCES_LOADED", balancesByAccountId });
19017
+ return balancesByAccountId;
19018
+ }).catch(() => null);
19019
+ if (opts?.awaitBalances) {
19020
+ return await balancesPromise;
19021
+ }
19022
+ void balancesPromise;
19023
+ return null;
18035
19024
  } finally {
18036
19025
  depositSelectionReloadCountRef.current = Math.max(
18037
19026
  0,
@@ -18227,7 +19216,7 @@ function useSourceSelectionHandlers(_dispatch, orchestrator, options) {
18227
19216
  const selectSourceChoices = react.useMemo(() => {
18228
19217
  if (!pendingSelectSourceAction) return [];
18229
19218
  const sourceOptions = pendingSelectSourceAction.metadata?.options ?? [];
18230
- return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd);
19219
+ return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd, true);
18231
19220
  }, [pendingSelectSourceAction, minTransferAmountUsd]);
18232
19221
  const selectSourceUnsupportedChoices = react.useMemo(() => {
18233
19222
  if (!pendingSelectSourceAction) return [];
@@ -18413,13 +19402,6 @@ function buildReauthorizationSessionOptions(params) {
18413
19402
  return options;
18414
19403
  }
18415
19404
 
18416
- // src/tokenAuthorizationRunOptions.ts
18417
- function buildDesktopTokenAuthorizationOnlyRunOptions(chainName, tokenSymbol) {
18418
- return {
18419
- autoResolveSource: { chainName, tokenSymbol }
18420
- };
18421
- }
18422
-
18423
19405
  // src/oneTapDefaults.ts
18424
19406
  var DEFAULT_ONE_TAP_ALLOWANCE_USD = 1e4;
18425
19407
  async function ensureDefaultOneTapAllowance(apiBaseUrl, token) {
@@ -18440,7 +19422,7 @@ function resolveReauthorizationTarget(input) {
18440
19422
  };
18441
19423
  }
18442
19424
  const wallet = input.account?.wallets.find((candidate) => candidate.id === input.selectedWalletId);
18443
- const source = wallet?.sources.find(
19425
+ const source = wallet?.sources?.find(
18444
19426
  (candidate) => input.selectedTokenSymbol ? candidate.token.symbol === input.selectedTokenSymbol : candidate.token.status === "AUTHORIZED"
18445
19427
  );
18446
19428
  return {
@@ -18451,6 +19433,28 @@ function resolveReauthorizationTarget(input) {
18451
19433
  };
18452
19434
  }
18453
19435
 
19436
+ // src/depositTokenSelection.ts
19437
+ function isSourceAuthorized(balancesByAccountId, target) {
19438
+ const account = target.accountId != null ? balancesByAccountId?.[target.accountId] : void 0;
19439
+ if (!account) return false;
19440
+ const wallet = account.wallets.find((w) => w.id === target.walletId);
19441
+ if (!wallet) return false;
19442
+ const source = wallet.sources.find((s) => s.token.symbol === target.tokenSymbol);
19443
+ return source?.token.status === "AUTHORIZED";
19444
+ }
19445
+ var defaultSettleDelay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
19446
+ async function settleUntilTokenAuthorized(reloadAccounts, target, opts = {}) {
19447
+ const maxAttempts = opts.maxAttempts ?? 4;
19448
+ const backoffMs = opts.backoffMs ?? 400;
19449
+ const delay = opts.delay ?? defaultSettleDelay;
19450
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
19451
+ const balances = await reloadAccounts({ awaitBalances: true });
19452
+ if (isSourceAuthorized(balances, target)) return true;
19453
+ if (attempt < maxAttempts - 1) await delay(backoffMs);
19454
+ }
19455
+ return false;
19456
+ }
19457
+
18454
19458
  // src/walletConnectLinks.ts
18455
19459
  function buildWalletConnectDeeplink(links, walletConnectUri) {
18456
19460
  const baseLink = links?.native ?? links?.universal;
@@ -18582,6 +19586,8 @@ function useProviderHandlers(deps) {
18582
19586
  setupDepositToken,
18583
19587
  tryStartExtensionConnectForReownWallet
18584
19588
  } = deps;
19589
+ const setupDepositTokenRef = react.useRef(setupDepositToken);
19590
+ setupDepositTokenRef.current = setupDepositToken;
18585
19591
  const checkWalletConnectChainSupport = react.useMemo(
18586
19592
  () => deps.checkWalletConnectChainSupport ?? ((reownWalletId, chainId) => checkReownWalletChainSupport(BLINK_WC_PROJECT_ID, reownWalletId, chainId)),
18587
19593
  [deps.checkWalletConnectChainSupport]
@@ -18649,7 +19655,7 @@ function useProviderHandlers(deps) {
18649
19655
  return null;
18650
19656
  }
18651
19657
  const provider = providers.find((p) => p.id === providerId);
18652
- const providerName = provider?.name ?? "Wallet";
19658
+ const providerName2 = provider?.name ?? "Wallet";
18653
19659
  try {
18654
19660
  const token = await getAccessToken();
18655
19661
  if (!token) throw new Error("Not authenticated");
@@ -18657,7 +19663,7 @@ function useProviderHandlers(deps) {
18657
19663
  const accountId = crypto.randomUUID();
18658
19664
  const account = await createAccount(apiBaseUrl, token, {
18659
19665
  id: accountId,
18660
- name: providerName,
19666
+ name: providerName2,
18661
19667
  credentialId: activeCredentialId,
18662
19668
  providerId,
18663
19669
  ...merchantAuthorization ? {
@@ -18706,7 +19712,7 @@ function useProviderHandlers(deps) {
18706
19712
  return;
18707
19713
  }
18708
19714
  const provider = providers.find((p) => p.id === providerId);
18709
- const providerName = provider?.name ?? "Wallet";
19715
+ const providerName2 = provider?.name ?? "Wallet";
18710
19716
  let shouldRestoreDesktopSelectionOnError = shouldSnapshotDesktopSelection;
18711
19717
  try {
18712
19718
  await resetWalletConnect();
@@ -18729,7 +19735,7 @@ function useProviderHandlers(deps) {
18729
19735
  const newAccountId = crypto.randomUUID();
18730
19736
  const account = await createAccount(apiBaseUrl, token, {
18731
19737
  id: newAccountId,
18732
- name: providerName,
19738
+ name: providerName2,
18733
19739
  credentialId: activeCredentialId,
18734
19740
  providerId,
18735
19741
  ...merchantAuthorization ? {
@@ -18797,7 +19803,7 @@ function useProviderHandlers(deps) {
18797
19803
  }
18798
19804
  dispatch({ type: "BEGIN_LINK_SETTLING" });
18799
19805
  try {
18800
- await reloadAccounts();
19806
+ await reloadAccounts({ awaitBalances: true });
18801
19807
  } finally {
18802
19808
  dispatch({ type: "END_LINK_SETTLING" });
18803
19809
  }
@@ -18924,7 +19930,7 @@ function useProviderHandlers(deps) {
18924
19930
  }
18925
19931
  dispatch({ type: "BEGIN_LINK_SETTLING" });
18926
19932
  try {
18927
- await reloadAccounts();
19933
+ await reloadAccounts({ awaitBalances: true });
18928
19934
  } finally {
18929
19935
  dispatch({ type: "END_LINK_SETTLING" });
18930
19936
  }
@@ -19148,7 +20154,7 @@ function useProviderHandlers(deps) {
19148
20154
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19149
20155
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19150
20156
  try {
19151
- await reloadAccounts();
20157
+ await reloadAccounts({ awaitBalances: true });
19152
20158
  } finally {
19153
20159
  dispatch({ type: "END_LINK_SETTLING" });
19154
20160
  }
@@ -19250,7 +20256,9 @@ function useProviderHandlers(deps) {
19250
20256
  const result = await orchestrator.run(
19251
20257
  sessionId,
19252
20258
  {
19253
- ...buildDesktopTokenAuthorizationOnlyRunOptions(inlineChain.name, tokenSymbol),
20259
+ // No autoResolveSource: pause at SELECT_SOURCE so LinkTokensScreen
20260
+ // renders with the token preselected (SET_SETUP_DEPOSIT_TOKEN pin
20261
+ // above) and the user can set a spending limit before approving.
19254
20262
  // Pin the existing account id so pairing and signing share one
19255
20263
  // runtime (see handleSelectWalletConnectWallet).
19256
20264
  walletConnectRuntimeKey: runtimeAccountId,
@@ -19258,10 +20266,11 @@ function useProviderHandlers(deps) {
19258
20266
  // Require the target token's chain at pairing. A reused session
19259
20267
  // that doesn't approve it re-pairs with the chain required; a
19260
20268
  // wallet that approves the connection without it fails
19261
- // OPEN_PROVIDER with a clear error instead of the auto-resolved
19262
- // SELECT_SOURCE 422 (INVALID_SELECT_SOURCE_SELECTION) — e.g.
19263
- // authorizing USDC on HyperEVM (999, a WC-optional chain) with a
19264
- // wallet that dropped or doesn't support that chain.
20269
+ // OPEN_PROVIDER with a clear error up front instead of stranding the
20270
+ // user on LinkTokensScreen whose only option (SELECT_SOURCE) would
20271
+ // 422 (INVALID_SELECT_SOURCE_SELECTION) on Approve e.g. authorizing
20272
+ // USDC on HyperEVM (999, a WC-optional chain) with a wallet that
20273
+ // dropped or doesn't support that chain.
19265
20274
  walletConnectRequiredChainId: chainId,
19266
20275
  walletConnectRequiredChainName: inlineChain.name,
19267
20276
  onWalletConnectDisplayUri: (uri) => {
@@ -19297,11 +20306,17 @@ function useProviderHandlers(deps) {
19297
20306
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19298
20307
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19299
20308
  try {
19300
- await reloadAccounts();
20309
+ await reloadAccounts({ awaitBalances: true });
19301
20310
  } finally {
19302
20311
  dispatch({ type: "END_LINK_SETTLING" });
19303
20312
  }
19304
- dispatch({ type: "SELECT_TOKEN", walletId, tokenSymbol, accountId: selectTokenAccountId });
20313
+ const confirmed = setupDepositTokenRef.current;
20314
+ dispatch({
20315
+ type: "SELECT_TOKEN",
20316
+ walletId: confirmed?.walletId ?? walletId,
20317
+ tokenSymbol: confirmed?.symbol ?? tokenSymbol,
20318
+ accountId: selectTokenAccountId
20319
+ });
19305
20320
  dispatch({ type: "DISCARD_SAVED_SELECTION" });
19306
20321
  } catch (err) {
19307
20322
  captureException(err);
@@ -19616,10 +20631,7 @@ function useProviderHandlers(deps) {
19616
20631
  walletDeeplinks: walletDeeplinks ?? null,
19617
20632
  providerId: matchedProvider?.id ?? null
19618
20633
  });
19619
- const result = await orchestrator.run(
19620
- session.id,
19621
- buildDesktopTokenAuthorizationOnlyRunOptions(inlineChain.name, tokenSymbol)
19622
- );
20634
+ const result = await orchestrator.run(session.id);
19623
20635
  if (result.status === "cancelled") {
19624
20636
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19625
20637
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
@@ -19631,14 +20643,25 @@ function useProviderHandlers(deps) {
19631
20643
  }
19632
20644
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19633
20645
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
20646
+ const confirmed = setupDepositTokenRef.current;
20647
+ const settleTarget = {
20648
+ accountId: _accountId,
20649
+ walletId: confirmed?.walletId ?? _walletId,
20650
+ tokenSymbol: confirmed?.symbol ?? tokenSymbol
20651
+ };
19634
20652
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19635
20653
  try {
19636
- await reloadAccounts();
20654
+ await settleUntilTokenAuthorized(reloadAccounts, settleTarget);
20655
+ dispatch({
20656
+ type: "SELECT_TOKEN",
20657
+ walletId: settleTarget.walletId,
20658
+ tokenSymbol: settleTarget.tokenSymbol,
20659
+ accountId: _accountId
20660
+ });
20661
+ dispatch({ type: "DISCARD_SAVED_SELECTION" });
19637
20662
  } finally {
19638
20663
  dispatch({ type: "END_LINK_SETTLING" });
19639
20664
  }
19640
- dispatch({ type: "SELECT_TOKEN", walletId: _walletId, tokenSymbol, accountId: _accountId });
19641
- dispatch({ type: "DISCARD_SAVED_SELECTION" });
19642
20665
  } catch (err) {
19643
20666
  captureException(err);
19644
20667
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
@@ -20098,6 +21121,15 @@ function useDataLoadEffect(deps) {
20098
21121
  hasActiveCredential: !!state.activeCredentialId,
20099
21122
  loading: loadingDataRef.current
20100
21123
  });
21124
+ appendDebug("warn", "dataLoadEffect run", {
21125
+ loadAction,
21126
+ authenticated,
21127
+ accountsCount: state.accounts.length,
21128
+ hasActiveCredential: !!state.activeCredentialId,
21129
+ loadingRef: loadingDataRef.current,
21130
+ privyAuthenticated: state.privyAuthenticated,
21131
+ activeCredentialId: state.activeCredentialId ? `${state.activeCredentialId.slice(0, 8)}\u2026` : null
21132
+ });
20101
21133
  if (loadAction === "reset") {
20102
21134
  loadingDataRef.current = false;
20103
21135
  dispatch({ type: "DATA_LOAD_END" });
@@ -20115,13 +21147,21 @@ function useDataLoadEffect(deps) {
20115
21147
  const load = async () => {
20116
21148
  dispatch({ type: "DATA_LOAD_START" });
20117
21149
  try {
21150
+ appendDebug("warn", "dataLoad: requesting access token");
20118
21151
  const token = await getAccessTokenRef.current();
21152
+ appendDebug("warn", "dataLoad: token result", { hasToken: !!token, cancelled });
20119
21153
  if (!token) throw new Error("Not authenticated");
20120
21154
  const [prov, accts, chn] = await Promise.all([
20121
21155
  fetchProviders(apiBaseUrl, token),
20122
21156
  fetchAccounts(apiBaseUrl, token, credentialId),
20123
21157
  fetchChains(apiBaseUrl, token)
20124
21158
  ]);
21159
+ appendDebug("warn", "dataLoad: fetch resolved", {
21160
+ cancelled,
21161
+ providers: prov.length,
21162
+ accounts: accts.length,
21163
+ chains: chn.length
21164
+ });
20125
21165
  if (cancelled) return;
20126
21166
  const parsedAmt = depositAmountRef.current != null ? depositAmountRef.current : 0;
20127
21167
  const priorityContext = resolveDepositPriorityContext(accts, chn, destination);
@@ -20147,7 +21187,16 @@ function useDataLoadEffect(deps) {
20147
21187
  resetSelectedTokenSymbol
20148
21188
  });
20149
21189
  if (clearMobile) clearMobileFlowState();
21190
+ void fetchBalancesByAccountId(apiBaseUrl, token, credentialId, accts).then(
21191
+ (balancesByAccountId) => {
21192
+ dispatch({ type: "BALANCES_LOADED", balancesByAccountId });
21193
+ }
21194
+ );
20150
21195
  } catch (err) {
21196
+ appendDebug("error", "dataLoad: threw", {
21197
+ cancelled,
21198
+ message: err instanceof Error ? err.message : String(err)
21199
+ });
20151
21200
  if (!cancelled) {
20152
21201
  captureException(err);
20153
21202
  dispatch({
@@ -20164,6 +21213,7 @@ function useDataLoadEffect(deps) {
20164
21213
  };
20165
21214
  load();
20166
21215
  return () => {
21216
+ appendDebug("warn", "dataLoadEffect cleanup (cancelling in-flight load)");
20167
21217
  cancelled = true;
20168
21218
  loadingDataRef.current = false;
20169
21219
  dispatch({ type: "DATA_LOAD_END" });
@@ -20907,7 +21957,7 @@ function BlinkPaymentInner({
20907
21957
  useWalletConnector: useWalletConnectorProp,
20908
21958
  userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent
20909
21959
  });
20910
- const [state, dispatch] = react.useReducer(
21960
+ const [state, rawDispatch] = react.useReducer(
20911
21961
  paymentReducer,
20912
21962
  {
20913
21963
  depositAmount,
@@ -20917,6 +21967,30 @@ function BlinkPaymentInner({
20917
21967
  },
20918
21968
  createInitialState
20919
21969
  );
21970
+ const device = isDesktop ? "desktop" : "mobile";
21971
+ const stateRef = react.useRef(state);
21972
+ stateRef.current = state;
21973
+ const dispatch = react.useCallback(
21974
+ (action) => {
21975
+ trackAction(action, stateRef.current, { device });
21976
+ rawDispatch(action);
21977
+ },
21978
+ [device]
21979
+ );
21980
+ usePostHogIdentify();
21981
+ useAnalyticsScreenView(state.phase, device);
21982
+ const flowOpenedRef = react.useRef(false);
21983
+ react.useEffect(() => {
21984
+ if (flowOpenedRef.current) return;
21985
+ flowOpenedRef.current = true;
21986
+ registerSuperProps({ device });
21987
+ track("flow_opened", {
21988
+ device,
21989
+ isMobileApp: isMobileApp ?? false,
21990
+ fullWidget: enableFullWidget ?? false,
21991
+ hasAmount: depositAmount != null
21992
+ });
21993
+ }, []);
20920
21994
  const polling = useTransferPolling(3e3, effectiveGetAccessToken);
20921
21995
  const transferSigning = useTransferSigning(2e3, { getAccessToken: effectiveGetAccessToken });
20922
21996
  const mobileFlowRefs = {
@@ -21275,7 +22349,7 @@ function BlinkPaymentInner({
21275
22349
  const handleSetDepositToken = react.useCallback((symbol, chainName, walletId, tokenAddress, chainId) => {
21276
22350
  dispatch({ type: "SET_SETUP_DEPOSIT_TOKEN", symbol, chainName, walletId, tokenAddress, chainId });
21277
22351
  }, []);
21278
- const handleConfirmSetupDeposit = react.useCallback(async () => {
22352
+ const handleConfirmSetupDeposit = react.useCallback(async (limit) => {
21279
22353
  const plan = planConfirmSetupDeposit({
21280
22354
  setupDepositToken: state.setupDepositToken,
21281
22355
  setupSelectedSourceOption,
@@ -21285,6 +22359,19 @@ function BlinkPaymentInner({
21285
22359
  dispatch({ type: "SET_ERROR", error: plan.error });
21286
22360
  return;
21287
22361
  }
22362
+ const token = await effectiveGetAccessToken();
22363
+ if (!token) {
22364
+ dispatch({ type: "SET_ERROR", error: "Your session expired. Please try again." });
22365
+ return;
22366
+ }
22367
+ const config = "unlimited" in limit ? { unlimitedAllowance: true } : { defaultAllowance: limit.usd, unlimitedAllowance: false };
22368
+ try {
22369
+ await updateUserConfig(apiBaseUrl, token, config);
22370
+ } catch {
22371
+ dispatch({ type: "SET_ERROR", error: "Could not save your spending limit. Please try again." });
22372
+ return;
22373
+ }
22374
+ dispatch({ type: "SET_SETUP_SPENDING_LIMIT", limit });
21288
22375
  for (const action of plan.actions) {
21289
22376
  dispatch(action);
21290
22377
  }
@@ -21293,6 +22380,8 @@ function BlinkPaymentInner({
21293
22380
  state.selectedAccountId,
21294
22381
  state.setupDepositToken,
21295
22382
  setupSelectedSourceOption,
22383
+ effectiveGetAccessToken,
22384
+ apiBaseUrl,
21296
22385
  dispatch,
21297
22386
  orchestrator
21298
22387
  ]);
@@ -21353,6 +22442,9 @@ function BlinkPaymentInner({
21353
22442
  }
21354
22443
  })();
21355
22444
  }, [orchestrator]);
22445
+ const handleApprove = react.useCallback(() => {
22446
+ authExecutor.approveAuthorization();
22447
+ }, [authExecutor]);
21356
22448
  const handleSetPhase = react.useCallback((phase) => {
21357
22449
  clearScreenErrors();
21358
22450
  if (phase.step === "deposit") {
@@ -21374,6 +22466,7 @@ function BlinkPaymentInner({
21374
22466
  onConfirmSign: transfer.handleConfirmSign,
21375
22467
  onRetryMobileStatus: mobileFlow.handleRetryMobileStatus,
21376
22468
  onRetryAuthorization: handleAuthorizationRetry,
22469
+ onApprove: handleApprove,
21377
22470
  onRetryTransferSigning: transfer.handleRetryTransferSigning,
21378
22471
  onBackFromOpenWallet: handleBackFromOpenWallet,
21379
22472
  onLogout: handleLogout,
@@ -21444,6 +22537,7 @@ function BlinkPaymentInner({
21444
22537
  handleConfirmSetupDeposit,
21445
22538
  handleBackFromSetupDeposit,
21446
22539
  handleAuthorizationRetry,
22540
+ handleApprove,
21447
22541
  disconnectWallets,
21448
22542
  state.desktopWait?.walletForegroundLink
21449
22543
  ]);
@@ -21483,6 +22577,9 @@ function BlinkPaymentInner({
21483
22577
  authExecutorError: authExecutor.error,
21484
22578
  authExecutorExecuting: authExecutor.executing,
21485
22579
  authExecutorCurrentAction: authExecutor.currentAction,
22580
+ authExecutorAwaitingApproval: authExecutor.awaitingApproval,
22581
+ authExecutorApprovalDestinationAddress: authExecutor.approvalDestinationAddress,
22582
+ authApprovalSmartAccountAddress: orchestrator.approvalSmartAccountAddress,
21486
22583
  authExecutorCompleted: orchestrator.orchestratorCompleted,
21487
22584
  transferSigningSigning: transferSigning.signing,
21488
22585
  transferSigningError: transferSigning.error,
@@ -21590,6 +22687,7 @@ exports.getDeviceBiometricUnlockText = getDeviceBiometricUnlockText;
21590
22687
  exports.getTheme = getTheme;
21591
22688
  exports.getThemeBase = getThemeBase;
21592
22689
  exports.getWalletCapabilities = getWalletCapabilities;
22690
+ exports.identifyUser = identifyUser;
21593
22691
  exports.isAuthorizationSessionCancelled = isAuthorizationSessionCancelled;
21594
22692
  exports.isExpectedAuthorizationCancellation = isExpectedAuthorizationCancellation;
21595
22693
  exports.isTerminalTransferStatus = isTerminalTransferStatus;
@@ -21603,11 +22701,15 @@ exports.lightTransparentTheme = lightTransparentTheme;
21603
22701
  exports.lightTransparentThemeNew = lightTransparentThemeNew;
21604
22702
  exports.mapGuestPickerEntries = mapGuestPickerEntries;
21605
22703
  exports.replaceOpenProviderForAccountSwitch = replaceOpenProviderForAccountSwitch;
22704
+ exports.resetUser = resetUser;
22705
+ exports.resolveAnalyticsEnvironment = resolveAnalyticsEnvironment;
21606
22706
  exports.resolvePasskeyRpId = resolvePasskeyRpId;
22707
+ exports.sanitizeProps = sanitizeProps;
21607
22708
  exports.screenForPhase = screenForPhase;
21608
22709
  exports.subscribeDebug = subscribeDebug;
21609
22710
  exports.supportsAtomicBatch = supportsAtomicBatch;
21610
22711
  exports.supportsPaymasterService = supportsPaymasterService;
22712
+ exports.track = track;
21611
22713
  exports.useAuthorizationExecutor = useAuthorizationExecutor;
21612
22714
  exports.useAuthorizationOrchestrator = useAuthorizationOrchestrator;
21613
22715
  exports.useBlinkConfig = useBlinkConfig;