@swype-org/react-sdk 0.2.377 → 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
@@ -82,7 +82,11 @@ var darkTheme = {
82
82
  shadowLg: "0 18px 44px rgba(0,0,0,0.42)",
83
83
  radius: "14px",
84
84
  radiusLg: "24px",
85
- 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
86
90
  };
87
91
  var lightTheme = {
88
92
  bg: "#ebf9fb",
@@ -114,7 +118,11 @@ var lightTheme = {
114
118
  shadowLg: "0 20px 48px rgba(19, 61, 75, 0.14)",
115
119
  radius: "14px",
116
120
  radiusLg: "24px",
117
- 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
118
126
  };
119
127
  var lightTransparentTheme = {
120
128
  ...lightTheme
@@ -157,7 +165,13 @@ var lightThemeNew = {
157
165
  shadowLg: "0 20px 48px rgba(0, 0, 0, 0.14)",
158
166
  radius: "14px",
159
167
  radiusLg: "24px",
160
- 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
161
175
  };
162
176
  var darkThemeNew = { ...lightThemeNew };
163
177
  var lightTransparentThemeNew = { ...lightThemeNew };
@@ -327,7 +341,7 @@ function resolveDepositPriorityContext(accounts, chains, destination) {
327
341
  for (const account of accounts) {
328
342
  for (const wallet of account.wallets) {
329
343
  if (wallet.chain.name !== destinationChainName) continue;
330
- const source = wallet.sources.find(
344
+ const source = (wallet.sources ?? []).find(
331
345
  (candidate) => candidate.address.toLowerCase() === destinationTokenAddress
332
346
  );
333
347
  if (source) {
@@ -361,6 +375,11 @@ function getWalletAddress(wallet) {
361
375
  const address = wallet.name.trim();
362
376
  return address.length > 0 ? address : null;
363
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
+ }
364
383
  function getAddressableWallets(account) {
365
384
  return account.wallets.filter((wallet) => getWalletAddress(wallet) != null);
366
385
  }
@@ -368,7 +387,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
368
387
  const wallets = getAddressableWallets(account);
369
388
  if (wallets.length === 0) return null;
370
389
  const ranked = wallets.map((wallet, walletIndex) => {
371
- const bestSource = wallet.sources.map((source, sourceIndex) => ({
390
+ const bestSource = (wallet.sources ?? []).map((source, sourceIndex) => ({
372
391
  source,
373
392
  sourceIndex
374
393
  })).sort((a, b) => {
@@ -393,7 +412,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
393
412
  if (a.wallet.status !== b.wallet.status) {
394
413
  return a.wallet.status === "ACTIVE" ? -1 : 1;
395
414
  }
396
- 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);
397
416
  if (balanceDiff !== 0) return balanceDiff;
398
417
  return a.walletIndex - b.walletIndex;
399
418
  });
@@ -409,7 +428,7 @@ function resolveDepositSelectionAfterRefresh(accounts, transferAmount, prev, pri
409
428
  const wallet = acct?.wallets.find((w) => w.id === selectedWalletId);
410
429
  if (wallet) {
411
430
  if (selectedTokenSymbol) {
412
- const hasToken = wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
431
+ const hasToken = wallet.sources == null || wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
413
432
  if (hasToken) {
414
433
  return {
415
434
  defaults: { accountId: selectedAccountId, walletId: selectedWalletId },
@@ -447,7 +466,9 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
447
466
  const eligibleAccounts = getDepositEligibleAccounts(accounts);
448
467
  if (eligibleAccounts.length === 0) return null;
449
468
  const accountHasSufficientBalanceWallet = (account) => getAddressableWallets(account).some(
450
- (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
+ )
451
472
  );
452
473
  if (transferAmount <= 0 && selectedAccountId) {
453
474
  const selectedAccount = eligibleAccounts.find((account) => account.id === selectedAccountId);
@@ -469,7 +490,7 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
469
490
  transferAmount,
470
491
  priorityContext
471
492
  );
472
- const preferredSource = preferredWallet ? [...preferredWallet.sources].sort((a, b) => compareDepositSourcePriority(
493
+ const preferredSource = preferredWallet ? [...preferredWallet.sources ?? []].sort((a, b) => compareDepositSourcePriority(
473
494
  sourcePriorityInput(preferredWallet.chain.name, a, transferAmount, priorityContext),
474
495
  sourcePriorityInput(preferredWallet.chain.name, b, transferAmount, priorityContext)
475
496
  ))[0] : void 0;
@@ -535,7 +556,7 @@ function buildNativeUnsupportedEntries(options) {
535
556
  logoURI: option.logoURI ?? null
536
557
  })).filter((entry) => entry.amount > 0);
537
558
  }
538
- function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD) {
559
+ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD, includeUnfundedTokens = false) {
539
560
  const chainChoices = [];
540
561
  const chainIndexByName = /* @__PURE__ */ new Map();
541
562
  for (const option of options) {
@@ -576,7 +597,7 @@ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SE
576
597
  }
577
598
  }
578
599
  return chainChoices.map((chain) => {
579
- const visibleTokens = chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
600
+ const visibleTokens = includeUnfundedTokens ? chain.tokens : chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
580
601
  return {
581
602
  ...chain,
582
603
  balance: visibleTokens.reduce((sum, token) => sum + token.balance, 0),
@@ -1964,464 +1985,197 @@ function screenForPhase(phase) {
1964
1985
  }
1965
1986
  }
1966
1987
 
1967
- // src/passkey-delegation.ts
1968
- var PasskeyIframeBlockedError = class extends Error {
1969
- constructor(message = "Passkey creation is not supported in this browser context.") {
1970
- super(message);
1971
- 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
+ }
1972
2033
  }
1973
- };
1974
- function isInCrossOriginIframe() {
1975
- if (typeof window === "undefined") return false;
1976
- if (window.parent === window) return false;
1977
- try {
1978
- void window.parent.location.origin;
1979
- return false;
1980
- } catch {
1981
- 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}`);
1982
2052
  }
2053
+ notify();
1983
2054
  }
1984
- function isSafari() {
1985
- if (typeof navigator === "undefined") return false;
1986
- const ua = navigator.userAgent;
1987
- 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
+ };
1988
2060
  }
1989
- var VERIFY_POPUP_TIMEOUT_MS = 6e4;
1990
- var POPUP_CLOSED_POLL_MS = 500;
1991
- var POPUP_CLOSED_GRACE_MS = 1e3;
1992
- function findDevicePasskeyViaPopup(options) {
1993
- return new Promise((resolve, reject) => {
1994
- const verificationToken = crypto.randomUUID();
1995
- const payload = {
1996
- ...options,
1997
- verificationToken
1998
- };
1999
- const encoded = btoa(JSON.stringify(payload));
2000
- const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
2001
- const popup = window.open(popupUrl, "blink-passkey-verify");
2002
- if (!popup) {
2003
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2004
- return;
2005
- }
2006
- let settled = false;
2007
- const timer = setTimeout(() => {
2008
- cleanup();
2009
- resolve(null);
2010
- }, VERIFY_POPUP_TIMEOUT_MS);
2011
- const closedPoll = setInterval(() => {
2012
- if (popup.closed && !settled) {
2013
- clearInterval(closedPoll);
2014
- setTimeout(() => {
2015
- if (!settled) {
2016
- settled = true;
2017
- cleanup();
2018
- checkServerForPasskeyByToken(
2019
- options.authToken,
2020
- options.apiBaseUrl,
2021
- verificationToken
2022
- ).then((result) => {
2023
- resolve(result?.credentialId ?? null);
2024
- }).catch(() => {
2025
- resolve(null);
2026
- });
2027
- }
2028
- }, 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));
2029
2102
  }
2030
- }, POPUP_CLOSED_POLL_MS);
2031
- function cleanup() {
2032
- clearTimeout(timer);
2033
- clearInterval(closedPoll);
2034
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}` }
2035
2149
  });
2150
+ if (!res.ok) await throwApiError(res);
2151
+ const data = await res.json();
2152
+ return data.items;
2036
2153
  }
2037
- async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
2038
- if (!authToken || !apiBaseUrl) return null;
2039
- const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
2040
- 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}` }
2041
2158
  });
2042
- if (!res.ok) return null;
2043
- const body = await res.json();
2044
- const passkeys = body.config.passkeys ?? [];
2045
- const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
2046
- 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;
2047
2162
  }
2048
- function shouldUsePasskeySignupPopup() {
2049
- 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();
2050
2170
  }
2051
- var SIGNUP_POPUP_TIMEOUT_MS = 12e4;
2052
- function signupWithPasskeyViaPopup() {
2053
- return new Promise((resolve, reject) => {
2054
- const popupUrl = `${window.location.origin}/passkey-signup`;
2055
- const popup = window.open(popupUrl, "blink-passkey-signup");
2056
- if (!popup) {
2057
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2058
- return;
2059
- }
2060
- let settled = false;
2061
- const timer = setTimeout(() => {
2062
- cleanup();
2063
- resolve(null);
2064
- }, SIGNUP_POPUP_TIMEOUT_MS);
2065
- function onMessage(event) {
2066
- if (event.origin !== window.location.origin) return;
2067
- if (event.source !== popup) return;
2068
- const data = event.data;
2069
- if (!data || data.type !== "blink:passkey-signup-complete") return;
2070
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2071
- settled = true;
2072
- cleanup();
2073
- resolve({
2074
- accessToken: data.accessToken,
2075
- credentialId: data.credentialId,
2076
- publicKey: data.publicKey
2077
- });
2078
- }
2079
- window.addEventListener("message", onMessage);
2080
- const closedPoll = setInterval(() => {
2081
- if (popup.closed && !settled) {
2082
- settled = true;
2083
- cleanup();
2084
- resolve(null);
2085
- }
2086
- }, POPUP_CLOSED_POLL_MS);
2087
- function cleanup() {
2088
- clearTimeout(timer);
2089
- clearInterval(closedPoll);
2090
- window.removeEventListener("message", onMessage);
2091
- }
2092
- });
2093
- }
2094
- var LOGIN_POPUP_TIMEOUT_MS = 12e4;
2095
- function loginWithPasskeyViaPopup() {
2096
- return new Promise((resolve, reject) => {
2097
- const popupUrl = `${window.location.origin}/passkey-login`;
2098
- const popup = window.open(popupUrl, "blink-passkey-login");
2099
- if (!popup) {
2100
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2101
- return;
2102
- }
2103
- let settled = false;
2104
- const timer = setTimeout(() => {
2105
- cleanup();
2106
- resolve(null);
2107
- }, LOGIN_POPUP_TIMEOUT_MS);
2108
- function onMessage(event) {
2109
- if (event.origin !== window.location.origin) return;
2110
- if (event.source !== popup) return;
2111
- const data = event.data;
2112
- if (!data || data.type !== "blink:passkey-login-complete") return;
2113
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2114
- settled = true;
2115
- cleanup();
2116
- resolve({
2117
- accessToken: data.accessToken,
2118
- credentialId: data.credentialId,
2119
- publicKey: data.publicKey
2120
- });
2121
- }
2122
- window.addEventListener("message", onMessage);
2123
- const closedPoll = setInterval(() => {
2124
- if (popup.closed && !settled) {
2125
- settled = true;
2126
- cleanup();
2127
- resolve(null);
2128
- }
2129
- }, POPUP_CLOSED_POLL_MS);
2130
- function cleanup() {
2131
- clearTimeout(timer);
2132
- clearInterval(closedPoll);
2133
- window.removeEventListener("message", onMessage);
2134
- }
2135
- });
2136
- }
2137
-
2138
- // src/credentialIdEncoding.ts
2139
- function credentialIdBase64ToBytes(value) {
2140
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2141
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
2142
- const raw = atob(padded);
2143
- const bytes = new Uint8Array(raw.length);
2144
- for (let i = 0; i < raw.length; i++) {
2145
- bytes[i] = raw.charCodeAt(i);
2146
- }
2147
- return bytes;
2148
- }
2149
-
2150
- // src/passkeyRpId.ts
2151
- function normalizeConfiguredDomain(value) {
2152
- return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2153
- }
2154
- function resolveRootDomainFromHostname(hostname) {
2155
- const trimmedHostname = hostname.trim().toLowerCase();
2156
- if (!trimmedHostname) {
2157
- return "localhost";
2158
- }
2159
- if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
2160
- return trimmedHostname;
2161
- }
2162
- const parts = trimmedHostname.split(".").filter(Boolean);
2163
- if (parts.length < 2) {
2164
- return trimmedHostname;
2165
- }
2166
- return parts.slice(-2).join(".");
2167
- }
2168
-
2169
- // src/hooks/passkeyPublic.ts
2170
- function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
2171
- return new Promise((resolve) => {
2172
- if (typeof document === "undefined") {
2173
- resolve();
2174
- return;
2175
- }
2176
- if (document.hasFocus()) {
2177
- resolve();
2178
- return;
2179
- }
2180
- const deadline = Date.now() + timeoutMs;
2181
- const timer = setInterval(() => {
2182
- if (document.hasFocus()) {
2183
- clearInterval(timer);
2184
- resolve();
2185
- } else if (Date.now() >= deadline) {
2186
- clearInterval(timer);
2187
- resolve();
2188
- }
2189
- }, intervalMs);
2190
- });
2191
- }
2192
- function toBase64(buffer) {
2193
- return btoa(String.fromCharCode(...new Uint8Array(buffer)));
2194
- }
2195
- function readEnvValue(name) {
2196
- 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)) });
2197
- const metaValue = meta.env?.[name];
2198
- if (typeof metaValue === "string" && metaValue.trim().length > 0) {
2199
- return metaValue.trim();
2200
- }
2201
- const processValue = globalThis.process?.env?.[name];
2202
- if (typeof processValue === "string" && processValue.trim().length > 0) {
2203
- return processValue.trim();
2204
- }
2205
- return void 0;
2206
- }
2207
- function resolvePasskeyRpId() {
2208
- const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
2209
- if (configuredDomain) {
2210
- return normalizeConfiguredDomain(configuredDomain);
2211
- }
2212
- if (typeof window !== "undefined") {
2213
- return resolveRootDomainFromHostname(window.location.hostname);
2214
- }
2215
- return "localhost";
2216
- }
2217
- async function deviceHasPasskey(credentialId) {
2218
- const found = await findDevicePasskey([credentialId]);
2219
- return found != null;
2220
- }
2221
- async function findDevicePasskey(credentialIds) {
2222
- if (credentialIds.length === 0) return null;
2223
- try {
2224
- const challenge = new Uint8Array(32);
2225
- crypto.getRandomValues(challenge);
2226
- await waitForDocumentFocus();
2227
- const assertion = await navigator.credentials.get({
2228
- publicKey: {
2229
- challenge,
2230
- rpId: resolvePasskeyRpId(),
2231
- allowCredentials: credentialIds.map((id) => ({
2232
- type: "public-key",
2233
- id: credentialIdBase64ToBytes(id)
2234
- })),
2235
- userVerification: "discouraged",
2236
- timeout: 3e4
2237
- }
2238
- });
2239
- if (!assertion) return null;
2240
- return toBase64(assertion.rawId);
2241
- } catch {
2242
- return null;
2243
- }
2244
- }
2245
-
2246
- // src/api.ts
2247
- var api_exports = {};
2248
- __export(api_exports, {
2249
- createAccount: () => createAccount,
2250
- createAccountAuthorizationSession: () => createAccountAuthorizationSession,
2251
- createManualTransfer: () => createManualTransfer,
2252
- createTransfer: () => createTransfer,
2253
- fetchAccount: () => fetchAccount,
2254
- fetchAccounts: () => fetchAccounts,
2255
- fetchAuthorizationSession: () => fetchAuthorizationSession,
2256
- fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
2257
- fetchChains: () => fetchChains,
2258
- fetchManualTransferSession: () => fetchManualTransferSession,
2259
- fetchManualTransferSources: () => fetchManualTransferSources,
2260
- fetchMerchantPublicKey: () => fetchMerchantPublicKey,
2261
- fetchProviders: () => fetchProviders,
2262
- fetchTransfer: () => fetchTransfer,
2263
- fetchUserConfig: () => fetchUserConfig,
2264
- postTransferQuote: () => postTransferQuote,
2265
- probeActionCompletion: () => probeActionCompletion,
2266
- refreshManualTransferQuote: () => refreshManualTransferQuote,
2267
- regenerateTransferSignPayload: () => regenerateTransferSignPayload,
2268
- registerPasskey: () => registerPasskey,
2269
- reportActionCompletion: () => reportActionCompletion,
2270
- reportPasskeyActivity: () => reportPasskeyActivity,
2271
- setAuthorizationSessionPaymentIntentAmount: () => setAuthorizationSessionPaymentIntentAmount,
2272
- setAuthorizationSessionProvider: () => setAuthorizationSessionProvider,
2273
- signTransfer: () => signTransfer,
2274
- updateManualTransferDepositTargetChain: () => updateManualTransferDepositTargetChain,
2275
- updateUserConfig: () => updateUserConfig,
2276
- updateUserConfigBySession: () => updateUserConfigBySession,
2277
- waitForActionTransactionReceipt: () => waitForActionTransactionReceipt
2278
- });
2279
- var DEBUG_BUFFER_CAPACITY = 200;
2280
- var nextId = 1;
2281
- var entries = [];
2282
- var listeners = /* @__PURE__ */ new Set();
2283
- function notify() {
2284
- for (const listener of listeners) {
2285
- try {
2286
- listener();
2287
- } catch (err) {
2288
- console.error("[blink-sdk][debug-log] listener threw:", err);
2289
- }
2290
- }
2291
- }
2292
- function appendDebug(level, message, data) {
2293
- const entry = {
2294
- id: nextId++,
2295
- ts: Date.now(),
2296
- level,
2297
- message,
2298
- data
2299
- };
2300
- const next = entries.length >= DEBUG_BUFFER_CAPACITY ? entries.slice(entries.length - DEBUG_BUFFER_CAPACITY + 1) : entries.slice();
2301
- next.push(entry);
2302
- entries = next;
2303
- const prefix = "[blink-sdk][debug]";
2304
- const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.info;
2305
- if (data !== void 0) {
2306
- sink(`${prefix} ${message}`, data);
2307
- } else {
2308
- sink(`${prefix} ${message}`);
2309
- }
2310
- notify();
2311
- }
2312
- function subscribeDebug(listener) {
2313
- listeners.add(listener);
2314
- return () => {
2315
- listeners.delete(listener);
2316
- };
2317
- }
2318
- function getDebugEntries() {
2319
- return entries;
2320
- }
2321
- function clearDebugEntries() {
2322
- entries = [];
2323
- notify();
2324
- }
2325
- function useBlinkDebugLog() {
2326
- return react.useSyncExternalStore(subscribeDebug, getDebugEntries, getDebugEntries);
2327
- }
2328
-
2329
- // src/fetchWithRetry.ts
2330
- var DEFAULT_MAX_RETRIES = 3;
2331
- var DEFAULT_BASE_DELAY_MS = 500;
2332
- var DEFAULT_MAX_JITTER_MS = 200;
2333
- function isNetworkTypeError(err) {
2334
- return err instanceof TypeError && /fetch|network|load failed/i.test(err.message);
2335
- }
2336
- async function fetchWithRetry(input, init, options) {
2337
- const maxRetries = DEFAULT_MAX_RETRIES;
2338
- const baseDelayMs = DEFAULT_BASE_DELAY_MS;
2339
- const maxJitterMs = DEFAULT_MAX_JITTER_MS;
2340
- const label = String(input).replace(/https?:\/\/[^/]+/, "");
2341
- let lastError;
2342
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
2343
- try {
2344
- return await fetch(input, init);
2345
- } catch (err) {
2346
- lastError = err;
2347
- if (!isNetworkTypeError(err)) {
2348
- throw err;
2349
- }
2350
- if (attempt < maxRetries) {
2351
- const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * maxJitterMs;
2352
- appendDebug("warn", `fetchWithRetry: network error, retrying ${label}`, {
2353
- attempt: attempt + 1,
2354
- maxRetries,
2355
- delayMs: Math.round(delay),
2356
- error: err instanceof Error ? err.message : String(err)
2357
- });
2358
- await new Promise((resolve) => setTimeout(resolve, delay));
2359
- }
2360
- }
2361
- }
2362
- throw lastError;
2363
- }
2364
-
2365
- // src/apiError.ts
2366
- var ApiError = class extends Error {
2367
- status;
2368
- code;
2369
- constructor(status, code, message) {
2370
- super(message);
2371
- this.name = "ApiError";
2372
- this.status = status;
2373
- this.code = code;
2374
- }
2375
- };
2376
- function isApiError(err) {
2377
- if (err instanceof ApiError) return true;
2378
- return typeof err === "object" && err !== null && "name" in err && err.name === "ApiError";
2379
- }
2380
- var SVM_SIGN_PAYLOAD_EXPIRED_CODE = "SVM_SIGN_PAYLOAD_EXPIRED";
2381
- function isSvmSignExpiredError(err) {
2382
- return isApiError(err) && err.code === SVM_SIGN_PAYLOAD_EXPIRED_CODE;
2383
- }
2384
-
2385
- // src/api.ts
2386
- async function throwApiError(res) {
2387
- const body = await res.json().catch(() => null);
2388
- const detail = body?.error ?? body;
2389
- const msg = detail?.message ?? res.statusText;
2390
- const code = detail?.code ?? String(res.status);
2391
- throw new ApiError(res.status, code, `${res.status} \u2014 ${code}: ${msg}`);
2392
- }
2393
- async function fetchProviders(apiBaseUrl, token) {
2394
- const headers = {};
2395
- if (token) {
2396
- headers.Authorization = `Bearer ${token}`;
2397
- }
2398
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/providers`, { headers });
2399
- if (!res.ok) await throwApiError(res);
2400
- const data = await res.json();
2401
- return data.items;
2402
- }
2403
- async function fetchChains(apiBaseUrl, token) {
2404
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/chains`, {
2405
- headers: { Authorization: `Bearer ${token}` }
2406
- });
2407
- if (!res.ok) await throwApiError(res);
2408
- const data = await res.json();
2409
- return data.items;
2410
- }
2411
- async function fetchAccounts(apiBaseUrl, token, credentialId) {
2412
- const params = new URLSearchParams({ credentialId });
2413
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts?${params.toString()}`, {
2414
- headers: { Authorization: `Bearer ${token}` }
2415
- });
2416
- if (!res.ok) await throwApiError(res);
2417
- const data = await res.json();
2418
- return data.items;
2419
- }
2420
- async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
2171
+ async function fetchAccountBalances(apiBaseUrl, token, accountId, credentialId) {
2421
2172
  const params = new URLSearchParams({ credentialId });
2422
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts/${accountId}?${params.toString()}`, {
2423
- headers: { Authorization: `Bearer ${token}` }
2424
- });
2173
+ const res = await fetchWithRetry(
2174
+ `${apiBaseUrl}/v1/accounts/${accountId}/balances?${params.toString()}`,
2175
+ {
2176
+ headers: { Authorization: `Bearer ${token}` }
2177
+ }
2178
+ );
2425
2179
  if (!res.ok) await throwApiError(res);
2426
2180
  return await res.json();
2427
2181
  }
@@ -2697,89 +2451,368 @@ async function createManualTransfer(apiBaseUrl, params) {
2697
2451
  headers: { "Content-Type": "application/json" },
2698
2452
  body: JSON.stringify(params)
2699
2453
  });
2700
- if (!res.ok) await throwApiError(res);
2701
- return await res.json();
2454
+ if (!res.ok) await throwApiError(res);
2455
+ return await res.json();
2456
+ }
2457
+ async function fetchManualTransferSession(apiBaseUrl, sessionId) {
2458
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`);
2459
+ if (!res.ok) await throwApiError(res);
2460
+ return await res.json();
2461
+ }
2462
+ async function updateManualTransferDepositTargetChain(apiBaseUrl, sessionId, selectedChainId) {
2463
+ const res = await fetch(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`, {
2464
+ method: "PATCH",
2465
+ headers: { "Content-Type": "application/json" },
2466
+ body: JSON.stringify({ selectedChainId })
2467
+ });
2468
+ if (!res.ok) await throwApiError(res);
2469
+ return await res.json();
2470
+ }
2471
+ async function refreshManualTransferQuote(apiBaseUrl, sessionId) {
2472
+ const res = await fetch(
2473
+ `${apiBaseUrl}/v1/manual-transfers/${sessionId}/refresh-quote`,
2474
+ { method: "POST" }
2475
+ );
2476
+ if (!res.ok) await throwApiError(res);
2477
+ return await res.json();
2478
+ }
2479
+ async function reportActionCompletion(apiBaseUrl, actionId, result) {
2480
+ const res = await fetchWithRetry(
2481
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2482
+ {
2483
+ method: "PATCH",
2484
+ headers: { "Content-Type": "application/json" },
2485
+ body: JSON.stringify({ status: "COMPLETED", result })
2486
+ }
2487
+ );
2488
+ if (!res.ok) await throwApiError(res);
2489
+ return await res.json();
2490
+ }
2491
+ async function waitForActionTransactionReceipt(apiBaseUrl, actionId, txHash) {
2492
+ const res = await fetch(
2493
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}/transaction-receipt`,
2494
+ {
2495
+ method: "POST",
2496
+ headers: { "Content-Type": "application/json" },
2497
+ body: JSON.stringify({ txHash })
2498
+ }
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
+ });
2702
2708
  }
2703
- async function fetchManualTransferSession(apiBaseUrl, sessionId) {
2704
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`);
2705
- if (!res.ok) await throwApiError(res);
2706
- return await res.json();
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;
2707
2720
  }
2708
- async function updateManualTransferDepositTargetChain(apiBaseUrl, sessionId, selectedChainId) {
2709
- const res = await fetch(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`, {
2710
- method: "PATCH",
2711
- headers: { "Content-Type": "application/json" },
2712
- body: JSON.stringify({ selectedChainId })
2713
- });
2714
- if (!res.ok) await throwApiError(res);
2715
- return await res.json();
2721
+
2722
+ // src/passkeyRpId.ts
2723
+ function normalizeConfiguredDomain(value) {
2724
+ return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2716
2725
  }
2717
- async function refreshManualTransferQuote(apiBaseUrl, sessionId) {
2718
- const res = await fetch(
2719
- `${apiBaseUrl}/v1/manual-transfers/${sessionId}/refresh-quote`,
2720
- { method: "POST" }
2721
- );
2722
- if (!res.ok) await throwApiError(res);
2723
- return await res.json();
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(".");
2724
2739
  }
2725
- async function reportActionCompletion(apiBaseUrl, actionId, result) {
2726
- const res = await fetchWithRetry(
2727
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2728
- {
2729
- method: "PATCH",
2730
- headers: { "Content-Type": "application/json" },
2731
- body: JSON.stringify({ status: "COMPLETED", result })
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;
2732
2747
  }
2733
- );
2734
- if (!res.ok) await throwApiError(res);
2735
- return await res.json();
2736
- }
2737
- async function waitForActionTransactionReceipt(apiBaseUrl, actionId, txHash) {
2738
- const res = await fetch(
2739
- `${apiBaseUrl}/v1/authorization-actions/${actionId}/transaction-receipt`,
2740
- {
2741
- method: "POST",
2742
- headers: { "Content-Type": "application/json" },
2743
- body: JSON.stringify({ txHash })
2748
+ if (document.hasFocus()) {
2749
+ resolve();
2750
+ return;
2744
2751
  }
2745
- );
2746
- if (!res.ok) await throwApiError(res);
2747
- return await res.json();
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
+ });
2748
2763
  }
2749
- async function probeActionCompletion(apiBaseUrl, actionId) {
2750
- const res = await fetchWithRetry(
2751
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2752
- {
2753
- method: "PATCH",
2754
- headers: { "Content-Type": "application/json" },
2755
- body: JSON.stringify({ status: "COMPLETED", result: {} })
2756
- }
2757
- );
2758
- if (res.ok) {
2759
- const session = await res.json();
2760
- 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();
2761
2772
  }
2762
- const body = await res.json().catch(() => null);
2763
- const detail = body?.error ?? body;
2764
- const code = detail?.code;
2765
- const message = detail?.message ?? res.statusText ?? `HTTP ${res.status}`;
2766
- if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
2767
- 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();
2768
2776
  }
2769
- const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
2770
- "APPROVE_NOT_DETECTED",
2771
- "APPROVE_SPL_NOT_DETECTED",
2772
- "SPL_DELEGATE_MISSING",
2773
- "SPL_DELEGATE_INSUFFICIENT",
2774
- "SPL_DELEGATE_WRONG_OWNER"
2775
- ]);
2776
- if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
2777
- 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);
2778
2783
  }
2779
- if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
2780
- 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;
2781
2815
  }
2782
- return { detected: false, reason: "error", status: res.status, code, message };
2783
2816
  }
2784
2817
 
2785
2818
  // src/transferPolling.ts
@@ -5810,6 +5843,43 @@ function useAuthorizationExecutor(options) {
5810
5843
  }),
5811
5844
  []
5812
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
+ }, []);
5813
5883
  const cancelPendingExecution = react.useCallback(() => {
5814
5884
  if (selectSourceRejectRef.current) {
5815
5885
  selectSourceRejectRef.current(new AuthorizationSessionCancelledError());
@@ -5817,6 +5887,12 @@ function useAuthorizationExecutor(options) {
5817
5887
  selectSourceResolverRef.current = null;
5818
5888
  setPendingSelectSource(null);
5819
5889
  }
5890
+ if (approvalRejectRef.current) {
5891
+ approvalRejectRef.current(new AuthorizationSessionCancelledError());
5892
+ approvalRejectRef.current = null;
5893
+ approvalResolverRef.current = null;
5894
+ }
5895
+ setAwaitingApproval(false);
5820
5896
  setError(null);
5821
5897
  setCurrentAction(null);
5822
5898
  setApproveSplConfirming(null);
@@ -6219,6 +6295,11 @@ function useAuthorizationExecutor(options) {
6219
6295
  currentAction,
6220
6296
  approveSplConfirming,
6221
6297
  pendingSelectSource,
6298
+ awaitingApproval,
6299
+ approvalDestinationAddress,
6300
+ waitForApproval,
6301
+ approveAuthorization,
6302
+ resetApprovalGate,
6222
6303
  resolveSelectSource,
6223
6304
  cancelPendingExecution,
6224
6305
  resetWalletConnect,
@@ -6513,6 +6594,7 @@ function useAuthorizationOrchestrator(deps) {
6513
6594
  const [pendingSelectSourceAction, setPendingSelectSourceAction] = react.useState(null);
6514
6595
  const [orchestratorCompleted, setOrchestratorCompleted] = react.useState(false);
6515
6596
  const [sourceSelectionResolved, setSourceSelectionResolved] = react.useState(false);
6597
+ const [approvalSmartAccountAddress, setApprovalSmartAccountAddress] = react.useState(null);
6516
6598
  const selectSourceResolverRef = react.useRef(null);
6517
6599
  const selectSourceRejectRef = react.useRef(null);
6518
6600
  const submittedApprovePermit2ActionIdsRef = react.useRef(/* @__PURE__ */ new Set());
@@ -6538,6 +6620,7 @@ function useAuthorizationOrchestrator(deps) {
6538
6620
  const cancellation = new AuthorizationSessionCancelledError();
6539
6621
  setOrchestratorCompleted(false);
6540
6622
  setSourceSelectionResolved(false);
6623
+ setApprovalSmartAccountAddress(null);
6541
6624
  if (selectSourceRejectRef.current) {
6542
6625
  selectSourceRejectRef.current(cancellation);
6543
6626
  selectSourceRejectRef.current = null;
@@ -6560,6 +6643,10 @@ function useAuthorizationOrchestrator(deps) {
6560
6643
  lastRunRef.current = { sessionId, options };
6561
6644
  setOrchestratorCompleted(false);
6562
6645
  setSourceSelectionResolved(false);
6646
+ setApprovalSmartAccountAddress(null);
6647
+ if (!options?.keepApprovalGate) {
6648
+ authExecutor.resetApprovalGate();
6649
+ }
6563
6650
  if (!authExecutor.beginExecution()) {
6564
6651
  appendDebug("warn", "orchestrator:run aborted \u2014 already executing");
6565
6652
  return { status: "cancelled" };
@@ -6588,6 +6675,8 @@ function useAuthorizationOrchestrator(deps) {
6588
6675
  let action = mergedPending[0];
6589
6676
  if (completedIds.has(action.id)) break;
6590
6677
  const ownerSessionId = actionSessionMap.get(action.id) ?? sessionId;
6678
+ const ownerSessionSca = sessions.find((s) => s.id === ownerSessionId)?.session.smartAccountAddress;
6679
+ if (ownerSessionSca) setApprovalSmartAccountAddress(ownerSessionSca);
6591
6680
  console.info("[blink-sdk][orchestrator] Next pending action.", {
6592
6681
  actionId: action.id,
6593
6682
  actionType: action.type,
@@ -6612,6 +6701,7 @@ function useAuthorizationOrchestrator(deps) {
6612
6701
  const autoResult = createSelectSourceResult(action, options.autoResolveSource);
6613
6702
  completedIds.add(action.id);
6614
6703
  authExecutor.addResult(autoResult);
6704
+ setSourceSelectionResolved(true);
6615
6705
  const reportedSession3 = await reportActionCompletionWithLogging(
6616
6706
  apiBaseUrl,
6617
6707
  action,
@@ -6705,6 +6795,13 @@ function useAuthorizationOrchestrator(deps) {
6705
6795
  throw new Error(SIGN_PERMIT2_CONFIRMING_MESSAGE);
6706
6796
  }
6707
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
+ }
6708
6805
  appendDebug("info", `orchestrator:executeAction start ${action.type}`, {
6709
6806
  actionId: action.id,
6710
6807
  ownerSessionId
@@ -6901,7 +6998,8 @@ function useAuthorizationOrchestrator(deps) {
6901
6998
  authExecutor.setError(null);
6902
6999
  return run(lastRun.sessionId, {
6903
7000
  ...lastRun.options,
6904
- probeBeforePrompt: true
7001
+ probeBeforePrompt: true,
7002
+ keepApprovalGate: true
6905
7003
  });
6906
7004
  }, [authExecutor, run, cancelPendingFlow]);
6907
7005
  return {
@@ -6911,9 +7009,17 @@ function useAuthorizationOrchestrator(deps) {
6911
7009
  resolveSelectSource,
6912
7010
  sourceSelectionResolved,
6913
7011
  orchestratorCompleted,
7012
+ approvalSmartAccountAddress,
6914
7013
  cancelPendingFlow
6915
7014
  };
6916
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
+ }
6917
7023
  function createSelectSourceResult(action, selection) {
6918
7024
  return {
6919
7025
  actionId: action.id,
@@ -7884,6 +7990,26 @@ function deriveSourceTypeAndId(state) {
7884
7990
  }
7885
7991
  return { sourceType: "accountId", sourceId: "" };
7886
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
+ }
7887
8013
  function clearStaleSelection(state) {
7888
8014
  if (state.selectedAccountId == null) return state;
7889
8015
  if (state.desktopWait != null || state.standardDesktopInlineOpenWallet) return state;
@@ -7905,6 +8031,7 @@ function createInitialState(config) {
7905
8031
  accounts: [],
7906
8032
  chains: [],
7907
8033
  loadingData: false,
8034
+ balancesLoading: false,
7908
8035
  depositSelectionRefreshing: false,
7909
8036
  selectedProviderId: null,
7910
8037
  selectedAccountId: null,
@@ -7934,6 +8061,7 @@ function createInitialState(config) {
7934
8061
  privyAuthenticated: false,
7935
8062
  lastResumedAt: 0,
7936
8063
  setupDepositToken: null,
8064
+ setupSpendingLimit: null,
7937
8065
  guestWalletPrepared: null,
7938
8066
  guestWalletDeeplinksPreparing: false,
7939
8067
  amountTooLow: null,
@@ -7953,6 +8081,7 @@ function clearAuthenticatedSessionState(state) {
7953
8081
  accounts: [],
7954
8082
  chains: [],
7955
8083
  loadingData: false,
8084
+ balancesLoading: false,
7956
8085
  depositSelectionRefreshing: false,
7957
8086
  selectedProviderId: null,
7958
8087
  selectedAccountId: null,
@@ -7978,6 +8107,7 @@ function clearAuthenticatedSessionState(state) {
7978
8107
  mobileTokenAuthorizationPending: false,
7979
8108
  linkSettling: false,
7980
8109
  setupDepositToken: null,
8110
+ setupSpendingLimit: null,
7981
8111
  guestWalletPrepared: null,
7982
8112
  guestWalletDeeplinksPreparing: false,
7983
8113
  amountTooLow: null,
@@ -8046,6 +8176,8 @@ function applyAction(state, action) {
8046
8176
  providers: action.providers,
8047
8177
  accounts: action.accounts,
8048
8178
  chains: action.chains,
8179
+ // Accounts arrive balance-free; balances follow via BALANCES_LOADED.
8180
+ balancesLoading: true,
8049
8181
  depositSelectionRefreshing: false,
8050
8182
  initialDataLoaded: true
8051
8183
  };
@@ -8069,10 +8201,12 @@ function applyAction(state, action) {
8069
8201
  depositSelectionRefreshing: false
8070
8202
  };
8071
8203
  case "ACCOUNTS_RELOADED": {
8204
+ const hadBalances = hasAnyBalances(state.accounts);
8072
8205
  const next = {
8073
8206
  ...state,
8074
- accounts: action.accounts,
8207
+ accounts: carryOverBalances(state.accounts, action.accounts),
8075
8208
  providers: action.providers,
8209
+ balancesLoading: !hadBalances,
8076
8210
  initialDataLoaded: true
8077
8211
  };
8078
8212
  if (action.defaults) {
@@ -8084,6 +8218,24 @@ function applyAction(state, action) {
8084
8218
  }
8085
8219
  return clearStaleSelection(next);
8086
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
+ }
8087
8239
  case "SET_DEPOSIT_SELECTION_REFRESHING":
8088
8240
  return { ...state, depositSelectionRefreshing: action.value };
8089
8241
  case "SAVE_SELECTION":
@@ -8155,6 +8307,7 @@ function applyAction(state, action) {
8155
8307
  mobileFlow: false,
8156
8308
  mobileTokenAuthorizationPending: false,
8157
8309
  setupDepositToken: null,
8310
+ setupSpendingLimit: null,
8158
8311
  setupFlowScreen: null
8159
8312
  };
8160
8313
  case "PAY_ENDED":
@@ -8400,6 +8553,7 @@ function applyAction(state, action) {
8400
8553
  linkSettling: false,
8401
8554
  savedSelection: null,
8402
8555
  setupDepositToken: null,
8556
+ setupSpendingLimit: null,
8403
8557
  setupFlowScreen: null,
8404
8558
  guestWalletPrepared: null,
8405
8559
  guestWalletDeeplinksPreparing: false,
@@ -8465,8 +8619,10 @@ function applyAction(state, action) {
8465
8619
  ...action.chainId != null ? { chainId: action.chainId } : {}
8466
8620
  }
8467
8621
  };
8622
+ case "SET_SETUP_SPENDING_LIMIT":
8623
+ return { ...state, setupSpendingLimit: action.limit };
8468
8624
  case "CLEAR_SETUP_DEPOSIT_TOKEN":
8469
- return { ...state, setupDepositToken: null };
8625
+ return { ...state, setupDepositToken: null, setupSpendingLimit: null };
8470
8626
  default:
8471
8627
  return state;
8472
8628
  }
@@ -9252,9 +9408,12 @@ function PrimaryButton({
9252
9408
  }
9253
9409
  );
9254
9410
  }
9411
+ var BUTTON_MIN_HEIGHT = 60;
9255
9412
  var progressButtonStyle = (tokens, paused) => ({
9256
9413
  position: "relative",
9257
9414
  width: "100%",
9415
+ minHeight: BUTTON_MIN_HEIGHT,
9416
+ boxSizing: "border-box",
9258
9417
  padding: "18px 24px",
9259
9418
  background: `linear-gradient(180deg, ${tokens.accent}88, ${tokens.accentHover}88)`,
9260
9419
  color: tokens.accentText,
@@ -9291,6 +9450,8 @@ var buttonStyle2 = (tokens, state) => {
9291
9450
  const gradient = `linear-gradient(180deg, ${tokens.accent}, ${tokens.accentHover})`;
9292
9451
  return {
9293
9452
  width: "100%",
9453
+ minHeight: BUTTON_MIN_HEIGHT,
9454
+ boxSizing: "border-box",
9294
9455
  padding: "18px 24px",
9295
9456
  // Layer a faint white film over the gradient on hover rather than using
9296
9457
  // `filter: brightness()` — the accent gradient is near-black in the new
@@ -10217,6 +10378,8 @@ function SourcePill({
10217
10378
  logo,
10218
10379
  name,
10219
10380
  balance,
10381
+ nameSlot,
10382
+ balanceSlot,
10220
10383
  expanded,
10221
10384
  showChevron = true,
10222
10385
  icon
@@ -10224,8 +10387,8 @@ function SourcePill({
10224
10387
  const { tokens } = useBlinkConfig();
10225
10388
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: pillStyle2(tokens.bgCardTranslucent), children: [
10226
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),
10227
- name && /* @__PURE__ */ jsxRuntime.jsx("span", { style: labelStyle2(tokens.text), children: name }),
10228
- 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 })),
10229
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(
10230
10393
  "path",
10231
10394
  {
@@ -10288,6 +10451,37 @@ var balanceStyle = (color) => ({
10288
10451
  color,
10289
10452
  whiteSpace: "nowrap"
10290
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
+ }
10291
10485
  function formatUsdTwoDecimals(value) {
10292
10486
  const n = Number(value);
10293
10487
  return (Number.isFinite(n) ? n : 0).toLocaleString("en-US", {
@@ -10988,6 +11182,14 @@ function LockIcon({ size = 24 }) {
10988
11182
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V8a4 4 0 1 1 8 0v3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" })
10989
11183
  ] });
10990
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
+ }
10991
11193
  function FaceIdIcon({ size = 24 }) {
10992
11194
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
10993
11195
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9V6a2 2 0 0 1 2-2h3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }),
@@ -11001,6 +11203,21 @@ function FaceIdIcon({ size = 24 }) {
11001
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" })
11002
11204
  ] });
11003
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
+ }
11004
11221
  function WalletIcon() {
11005
11222
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
11006
11223
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -11372,7 +11589,7 @@ function CopyAddressButton({
11372
11589
  }) {
11373
11590
  const { tokens } = useBlinkConfig();
11374
11591
  const [hovered, setHovered] = react.useState(false);
11375
- const short = address.length > 18 ? `${address.slice(0, 8)}...${address.slice(-6)}` : address;
11592
+ const short = truncateMiddle(address);
11376
11593
  return /* @__PURE__ */ jsxRuntime.jsxs(
11377
11594
  "button",
11378
11595
  {
@@ -13530,7 +13747,6 @@ var errorBannerStyle6 = (tokens) => ({
13530
13747
  textAlign: "left",
13531
13748
  boxSizing: "border-box"
13532
13749
  });
13533
- var SHIMMER_ROWS = 3;
13534
13750
  function LinkTokensScreen({
13535
13751
  entries: entries2,
13536
13752
  selectedIndex,
@@ -13543,44 +13759,75 @@ function LinkTokensScreen({
13543
13759
  approving = false
13544
13760
  }) {
13545
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);
13546
13766
  const showShimmer = loading && entries2.length === 0;
13547
13767
  const showEmpty = !loading && entries2.length === 0;
13548
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 };
13549
13773
  const approveDisabled = loading || approving || entries2.length === 0 || selectedIndex < 0 || selectedIndex >= entries2.length || !!selected?.notSupported;
13550
- const ctaLabel = selected ? `Authorize ${selected.tokenSymbol} on ${shortChainName(selected.chainName)}` : "Approve";
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
+ }
13551
13824
  return /* @__PURE__ */ jsxRuntime.jsxs(
13552
13825
  ScreenLayout,
13553
13826
  {
13554
13827
  scrollableBody: false,
13555
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
13556
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: lockBannerStyle2, children: [
13557
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: lockIconWrapStyle2(t.text), children: /* @__PURE__ */ jsxRuntime.jsx(LockIcon3, {}) }),
13558
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: lockBannerTextStyle2(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
13559
- ] }),
13560
- /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onApprove, disabled: approveDisabled, loading: approving, children: ctaLabel })
13561
- ] }),
13828
+ footer: /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: () => onApprove(currentSelection()), disabled: approveDisabled, loading: approving, children: "Review" }),
13562
13829
  children: [
13563
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
13564
- @keyframes blink-link-tokens-shimmer {
13565
- 0% { background-position: 200% 0; }
13566
- 100% { background-position: -200% 0; }
13567
- }
13568
- .blink-link-tokens-list {
13569
- scrollbar-width: thin;
13570
- scrollbar-color: ${t.textTertiary} transparent;
13571
- }
13572
- .blink-link-tokens-list::-webkit-scrollbar {
13573
- width: 4px;
13574
- }
13575
- .blink-link-tokens-list::-webkit-scrollbar-track {
13576
- background: transparent;
13577
- margin: 8px 0;
13578
- }
13579
- .blink-link-tokens-list::-webkit-scrollbar-thumb {
13580
- background: ${t.textTertiary};
13581
- border-radius: 999px;
13582
- }
13583
- ` }),
13830
+ /* @__PURE__ */ jsxRuntime.jsx(ListScrollbarStyles, { textTertiary: t.textTertiary }),
13584
13831
  /* @__PURE__ */ jsxRuntime.jsx(
13585
13832
  ScreenHeader,
13586
13833
  {
@@ -13590,71 +13837,198 @@ function LinkTokensScreen({
13590
13837
  }
13591
13838
  ),
13592
13839
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle10, children: [
13593
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle7(t.text), children: "Pick your stablecoin for passkey deposits" }),
13594
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "blink-link-tokens-list", style: listCardStyle(t.bgRecessed), children: [
13595
- showShimmer ? Array.from({ length: SHIMMER_ROWS }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs(
13596
- "div",
13597
- {
13598
- "data-testid": "link-tokens-shimmer-row",
13599
- "aria-hidden": "true",
13600
- style: shimmerRowStyle,
13601
- children: [
13602
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerCircleStyle(t.bgInput, t.border) }),
13603
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: shimmerInfoStyle, children: [
13604
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(72, t.bgInput, t.border) }),
13605
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(48, t.bgInput, t.border) })
13606
- ] }),
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) }),
13607
13848
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: shimmerLineStyle(56, t.bgInput, t.border) })
13608
- ]
13609
- },
13610
- `shimmer-${i}`
13611
- )) : entries2.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsx(
13612
- TokenSourceRow,
13613
- {
13614
- symbol: entry.tokenSymbol,
13615
- chainName: entry.chainName,
13616
- tokenLogoUri: entry.tokenLogoUri,
13617
- balance: entry.balanceLabel == null ? entry.balanceUsd : void 0,
13618
- balanceLabel: entry.balanceLabel,
13619
- selected: i === selectedIndex,
13620
- notSupported: entry.notSupported,
13621
- onClick: () => onSelect(i)
13622
- },
13623
- `${entry.tokenSymbol}-${entry.chainName}-${i}`
13624
- )),
13625
- 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
+ ] })
13626
13926
  ] }),
13627
- 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
+ ] })
13628
13951
  ] })
13629
13952
  ]
13630
13953
  }
13631
13954
  );
13632
13955
  }
13633
- function shortChainName(chainName) {
13634
- 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() }) });
13635
13963
  }
13636
- function LockIcon3() {
13637
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
13638
- "path",
13639
- {
13640
- 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",
13641
- stroke: "currentColor",
13642
- strokeWidth: "2",
13643
- strokeLinecap: "round",
13644
- strokeLinejoin: "round"
13645
- }
13646
- ) });
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
+ ` });
13647
14002
  }
13648
14003
  var contentStyle10 = {
13649
14004
  display: "flex",
13650
14005
  flexDirection: "column",
13651
- alignItems: "center",
13652
- gap: 16,
13653
- paddingTop: 8,
13654
14006
  flex: 1,
13655
14007
  minHeight: 0,
13656
14008
  width: "100%"
13657
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
+ };
13658
14032
  var wordmarkImgStyle3 = {
13659
14033
  height: 22,
13660
14034
  width: "auto",
@@ -13662,9 +14036,9 @@ var wordmarkImgStyle3 = {
13662
14036
  pointerEvents: "none",
13663
14037
  userSelect: "none"
13664
14038
  };
13665
- var headingStyle7 = (color) => ({
14039
+ var headingStyle7 = (color, weight) => ({
13666
14040
  fontSize: 24,
13667
- fontWeight: 700,
14041
+ fontWeight: weight,
13668
14042
  lineHeight: "normal",
13669
14043
  letterSpacing: 0,
13670
14044
  color,
@@ -13684,68 +14058,155 @@ var listCardStyle = (bg) => ({
13684
14058
  minHeight: 0,
13685
14059
  overflowY: "auto"
13686
14060
  });
13687
- var shimmerRowStyle = {
14061
+ var pillStyle3 = (bg) => ({
13688
14062
  display: "flex",
13689
14063
  alignItems: "center",
13690
14064
  gap: 16,
13691
- padding: "12px 16px 12px 12px",
13692
- background: "transparent",
13693
- border: "none",
13694
- borderRadius: 16,
13695
14065
  width: "100%",
13696
- 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
13697
14084
  };
13698
- 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",
13699
14113
  width: 40,
13700
14114
  height: 40,
13701
- borderRadius: "50%",
13702
14115
  flexShrink: 0,
13703
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13704
- backgroundSize: "200% 100%",
13705
- 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
13706
14137
  });
13707
- 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",
13708
14148
  display: "flex",
13709
- flexDirection: "column",
13710
- gap: 6,
13711
- flex: 1,
14149
+ alignItems: "center",
14150
+ gap: 8,
13712
14151
  minWidth: 0
13713
14152
  };
13714
- var shimmerLineStyle = (width, baseColor, highlightColor) => ({
13715
- width,
13716
- height: 12,
13717
- borderRadius: 6,
13718
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13719
- backgroundSize: "200% 100%",
13720
- 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
13721
14160
  });
13722
- var emptyStyle = (color) => ({
13723
- padding: "32px 16px",
13724
- 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",
13725
14169
  color,
13726
- 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
13727
14180
  });
13728
- var bannerSlotStyle = {
13729
- display: "flex",
13730
- justifyContent: "center",
13731
- width: "100%"
13732
- };
13733
- 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) => ({
13734
14194
  display: "flex",
13735
14195
  flexDirection: "column",
13736
14196
  gap: 16,
13737
- width: "100%"
13738
- };
13739
- var lockBannerStyle2 = {
14197
+ width: "100%",
14198
+ boxSizing: "border-box",
14199
+ padding: 16,
14200
+ borderRadius: 20,
14201
+ background: bg
14202
+ });
14203
+ var infoRowStyle2 = {
13740
14204
  display: "flex",
13741
14205
  alignItems: "flex-start",
13742
- gap: 16,
13743
- padding: "12px 16px 12px 12px",
13744
- borderRadius: 16,
13745
- width: "100%",
13746
- boxSizing: "border-box"
14206
+ gap: 14,
14207
+ width: "100%"
13747
14208
  };
13748
- var lockIconWrapStyle2 = (color) => ({
14209
+ var infoIconStyle = (color) => ({
13749
14210
  flexShrink: 0,
13750
14211
  width: 24,
13751
14212
  height: 24,
@@ -13754,14 +14215,60 @@ var lockIconWrapStyle2 = (color) => ({
13754
14215
  justifyContent: "center",
13755
14216
  color
13756
14217
  });
13757
- var lockBannerTextStyle2 = (color) => ({
13758
- margin: 0,
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,
13759
14250
  flex: 1,
13760
- fontSize: 12,
13761
- fontWeight: 500,
13762
- lineHeight: "normal",
13763
- color
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"
13764
14266
  });
14267
+ var bannerSlotStyle = {
14268
+ display: "flex",
14269
+ justifyContent: "center",
14270
+ width: "100%"
14271
+ };
13765
14272
  function DepositCompleteScreen({ amount }) {
13766
14273
  const { tokens } = useBlinkConfig();
13767
14274
  return /* @__PURE__ */ jsxRuntime.jsxs(ScreenLayout, { children: [
@@ -13823,6 +14330,7 @@ function SelectDepositSourceScreen({
13823
14330
  tokenOptions,
13824
14331
  selectedTokenSymbol,
13825
14332
  selectedChainName,
14333
+ balancesLoading,
13826
14334
  selectedWalletId,
13827
14335
  onPickToken,
13828
14336
  useDeeplink,
@@ -13839,8 +14347,9 @@ function SelectDepositSourceScreen({
13839
14347
  const [pendingMobileSelection, setPendingMobileSelection] = react.useState(null);
13840
14348
  const [preparedAuthorization, setPreparedAuthorization] = react.useState(null);
13841
14349
  const [preparingAuthorization, setPreparingAuthorization] = react.useState(false);
14350
+ const isAuthorizedOption = (opt) => !opt.status || opt.status === "AUTHORIZED";
13842
14351
  const selectableOptions = tokenOptions.filter(
13843
- (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
14352
+ (opt) => isAuthorizedOption(opt) || opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
13844
14353
  );
13845
14354
  const authorized = selectableOptions.filter(
13846
14355
  (opt) => !opt.status || opt.status === "AUTHORIZED"
@@ -13994,6 +14503,30 @@ function SelectDepositSourceScreen({
13994
14503
  "data-testid": "select-deposit-source-scroll-content",
13995
14504
  style: contentStyle12,
13996
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
+ )) }) }),
13997
14530
  orderedAccounts.map((account) => {
13998
14531
  const authRows = authorized.filter((opt) => rowMatchesSection(opt, account));
13999
14532
  const reqRows = requiresAuth.filter((opt) => rowMatchesSection(opt, account)).sort((a, b) => Number(!!a.notSupported) - Number(!!b.notSupported));
@@ -14208,6 +14741,7 @@ function ShimmerBlock({
14208
14741
  );
14209
14742
  }
14210
14743
  function DepositScreen({
14744
+ balancesLoading,
14211
14745
  availableBalance,
14212
14746
  remainingLimit,
14213
14747
  initialAmount,
@@ -14261,6 +14795,10 @@ function DepositScreen({
14261
14795
  const showMobileKeypad = isEntryMode && !isDesktop;
14262
14796
  const [keypadOpen, setKeypadOpen] = react.useState(showMobileKeypad);
14263
14797
  const [tokenPickerOpen, setTokenPickerOpen] = react.useState(false);
14798
+ const hasLoadedQuoteRef = react.useRef(false);
14799
+ if (!quoteLoading) {
14800
+ hasLoadedQuoteRef.current = true;
14801
+ }
14264
14802
  const selectableTokenOptions = tokenOptions?.filter(
14265
14803
  (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minDepositFloor)
14266
14804
  ) ?? [];
@@ -14278,11 +14816,11 @@ function DepositScreen({
14278
14816
  }, []);
14279
14817
  const selectedAccount = accounts?.find((a) => a.id === selectedAccountId);
14280
14818
  const selectedProviderName = selectedAccount?.name ?? "Wallet";
14281
- const isLowBalance = availableBalance < minDepositFloor;
14282
- const insufficientFunds = availableBalance < validationAmount;
14819
+ const isLowBalance = !balancesLoading && availableBalance < minDepositFloor;
14820
+ const insufficientFunds = !balancesLoading && availableBalance < validationAmount;
14283
14821
  const needsAuthorization = selectedTokenStatus != null && selectedTokenStatus !== "AUTHORIZED" && !insufficientFunds && !isLowBalance;
14284
14822
  const exceedsLimit = remainingLimit != null && validationAmount > remainingLimit && !isLowBalance && !needsAuthorization;
14285
- const canDeposit = validationAmount >= minDepositFloor && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14823
+ const canDeposit = validationAmount >= minDepositFloor && !balancesLoading && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14286
14824
  const hasAccountPill = !!accounts && accounts.length > 0;
14287
14825
  const pillClickable = canOpenInlineSheet || !!onSelectToken;
14288
14826
  const formattedBalance = selectedTokenSymbol != null ? `$${formatUsdTwoDecimals2(availableBalance)}` : void 0;
@@ -14305,6 +14843,7 @@ function DepositScreen({
14305
14843
  accounts: depositSourceAccounts,
14306
14844
  selectedAccountId: selectedAccountId ?? null,
14307
14845
  tokenOptions: selectableTokenOptions,
14846
+ balancesLoading,
14308
14847
  selectedTokenSymbol,
14309
14848
  selectedChainName,
14310
14849
  selectedWalletId: selectedWalletId ?? null,
@@ -14326,7 +14865,7 @@ function DepositScreen({
14326
14865
  const mobileEntryWithKeypad = isEntryMode && !isDesktop && keypadOpen;
14327
14866
  const showLiveMobileHero = isEntryMode && !isDesktop && keypadOpen;
14328
14867
  const showDesktopInputHero = isEntryMode && isDesktop;
14329
- const showFormattedHero = !showLiveMobileHero && !showDesktopInputHero;
14868
+ const showFeeRow = validationAmount > 0;
14330
14869
  let primaryButton;
14331
14870
  if (mobileEntryWithKeypad) {
14332
14871
  const canConfirmEntry = canContinue(amountEntryValue ?? "");
@@ -14456,7 +14995,32 @@ function DepositScreen({
14456
14995
  "aria-label": tokenAriaLabel,
14457
14996
  "aria-expanded": canOpenInlineSheet ? tokenPickerOpen : void 0,
14458
14997
  "aria-haspopup": canOpenInlineSheet ? "listbox" : void 0,
14459
- 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(
14460
15024
  SourcePill,
14461
15025
  {
14462
15026
  icon: /* @__PURE__ */ jsxRuntime.jsx(
@@ -14476,7 +15040,7 @@ function DepositScreen({
14476
15040
  )
14477
15041
  }
14478
15042
  ),
14479
- 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(
14480
15044
  "div",
14481
15045
  {
14482
15046
  "data-testid": "deposit-fee-shimmer",
@@ -14494,7 +15058,7 @@ function DepositScreen({
14494
15058
  }
14495
15059
  )
14496
15060
  }
14497
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" }) })
15061
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" })) })
14498
15062
  ] }),
14499
15063
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: bannerSlotStyle2, children: mobileEntryWithKeypad ? null : showLowFunds ? /* @__PURE__ */ jsxRuntime.jsx(
14500
15064
  NotificationBanner,
@@ -15871,7 +16435,7 @@ function OpenWalletScreen({
15871
16435
  return /* @__PURE__ */ jsxRuntime.jsxs(
15872
16436
  ScreenLayout,
15873
16437
  {
15874
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
16438
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
15875
16439
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
15876
16440
  /* @__PURE__ */ jsxRuntime.jsxs(
15877
16441
  SecondaryButton,
@@ -15918,7 +16482,7 @@ function OpenWalletScreen({
15918
16482
  return /* @__PURE__ */ jsxRuntime.jsxs(
15919
16483
  ScreenLayout,
15920
16484
  {
15921
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
16485
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle4, children: [
15922
16486
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
15923
16487
  /* @__PURE__ */ jsxRuntime.jsxs(
15924
16488
  SecondaryButton,
@@ -16002,7 +16566,7 @@ var primaryClusterStyle = {
16002
16566
  textAlign: "center",
16003
16567
  gap: 12
16004
16568
  };
16005
- var footerStackStyle5 = {
16569
+ var footerStackStyle4 = {
16006
16570
  display: "flex",
16007
16571
  flexDirection: "column",
16008
16572
  gap: 8
@@ -16130,6 +16694,12 @@ function ApprovingInWalletScreen({
16130
16694
  chainName,
16131
16695
  step,
16132
16696
  complete,
16697
+ signing,
16698
+ spendingLimitLabel,
16699
+ destinationAddress,
16700
+ tokenLogoUri,
16701
+ awaitingApproval,
16702
+ onApprove,
16133
16703
  error,
16134
16704
  onRetry,
16135
16705
  onBack,
@@ -16139,10 +16709,11 @@ function ApprovingInWalletScreen({
16139
16709
  onOpenWallet
16140
16710
  }) {
16141
16711
  const { tokens: t } = useBlinkConfig();
16142
- const showDelayedRetry = useDelayedRetry(error == null && onRetry != null);
16712
+ const showDelayedRetry = useDelayedRetry(!awaitingApproval && error == null && onRetry != null);
16143
16713
  const retryVisible = onRetry != null && (error != null || showDelayedRetry);
16144
16714
  const token = tokenSymbol ?? "token";
16145
- const steps = buildSteps(step, tokenSymbol, chainName, complete);
16715
+ const chain = chainName ?? "chain";
16716
+ const steps = buildSteps(step, tokenSymbol, complete, signing);
16146
16717
  const foregroundNavigation = foregroundDeeplink ? classifyWalletDeeplinkNavigation(foregroundDeeplink) : null;
16147
16718
  const foregroundWithJs = foregroundNavigation ? shouldOpenWithJavaScript(foregroundNavigation) : false;
16148
16719
  const showOpenWalletButton = !!foregroundDeeplink && onOpenWallet != null;
@@ -16150,11 +16721,7 @@ function ApprovingInWalletScreen({
16150
16721
  ScreenLayout,
16151
16722
  {
16152
16723
  scrollableBody: false,
16153
- footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle6, children: [
16154
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: lockBannerStyle3, children: [
16155
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: lockIconWrapStyle3(t.text), children: /* @__PURE__ */ jsxRuntime.jsx(LockIcon4, {}) }),
16156
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: lockBannerTextStyle3(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
16157
- ] }),
16724
+ footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: footerStackStyle5, children: [
16158
16725
  showOpenWalletButton && /* @__PURE__ */ jsxRuntime.jsxs(
16159
16726
  SecondaryButton,
16160
16727
  {
@@ -16171,7 +16738,7 @@ function ApprovingInWalletScreen({
16171
16738
  retryVisible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: retryStackStyle, children: [
16172
16739
  error && /* @__PURE__ */ jsxRuntime.jsx(InfoBanner, { children: error }),
16173
16740
  /* @__PURE__ */ jsxRuntime.jsx(OutlineButton, { onClick: onRetry, children: "Retry" })
16174
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { spinnerOnly: true })
16741
+ ] }) : awaitingApproval ? /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { onClick: onApprove, children: "Approve" }) : /* @__PURE__ */ jsxRuntime.jsx(PrimaryButton, { spinnerOnly: true })
16175
16742
  ] }),
16176
16743
  children: [
16177
16744
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -16183,7 +16750,39 @@ function ApprovingInWalletScreen({
16183
16750
  }
16184
16751
  ),
16185
16752
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: contentStyle16, children: [
16186
- /* @__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
+ ] }),
16187
16786
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle2(t.bgRecessed, t.radiusLg), children: [
16188
16787
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: SPIN_KEYFRAMES_CSS2 }),
16189
16788
  steps.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(ChecklistRow, { step: s }, i))
@@ -16193,6 +16792,32 @@ function ApprovingInWalletScreen({
16193
16792
  }
16194
16793
  );
16195
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
+ }
16196
16821
  function ChecklistRow({ step }) {
16197
16822
  const { tokens: t } = useBlinkConfig();
16198
16823
  const isComplete = step.status === "complete";
@@ -16205,7 +16830,7 @@ function ChecklistRow({ step }) {
16205
16830
  fill: t.accentText
16206
16831
  }
16207
16832
  ) }) }) : isActive ? /* @__PURE__ */ jsxRuntime.jsx(RowSpinner, { color: t.accent }) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: pendingBadgeStyle(t.bgRecessed) }),
16208
- /* @__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 })
16209
16834
  ] });
16210
16835
  }
16211
16836
  function RowSpinner({ color }) {
@@ -16240,22 +16865,28 @@ function RowSpinner({ color }) {
16240
16865
  }
16241
16866
  );
16242
16867
  }
16243
- 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) {
16244
16875
  const token = tokenSymbol ?? "token";
16245
- const chain = chainName ?? "chain";
16246
- const passkeyLabel = `Approve ${token} for passkey`;
16876
+ const passkeyLabel = "Assign spending right to your passkey";
16877
+ const activeStatus = signing ? "active" : "pending";
16247
16878
  if (step === "spl") {
16248
- return [{ label: passkeyLabel, status: complete ? "complete" : "active" }];
16879
+ return [{ label: passkeyLabel, status: complete ? "complete" : activeStatus }];
16249
16880
  }
16250
- const approveLabel = `Approve ${token} on ${chain}`;
16881
+ const approveLabel = `Approve ${token} for spending via Permit2`;
16251
16882
  return [
16252
16883
  {
16253
16884
  label: approveLabel,
16254
- status: complete || step !== "approve" ? "complete" : "active"
16885
+ status: complete || step !== "approve" ? "complete" : activeStatus
16255
16886
  },
16256
16887
  {
16257
16888
  label: passkeyLabel,
16258
- status: complete ? "complete" : step === "approve" ? "pending" : "active"
16889
+ status: complete ? "complete" : step === "approve" ? "pending" : activeStatus
16259
16890
  }
16260
16891
  ];
16261
16892
  }
@@ -16271,18 +16902,6 @@ function useDelayedRetry(enabled) {
16271
16902
  }, [enabled]);
16272
16903
  return visible;
16273
16904
  }
16274
- function LockIcon4() {
16275
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(
16276
- "path",
16277
- {
16278
- 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",
16279
- stroke: "currentColor",
16280
- strokeWidth: "2",
16281
- strokeLinecap: "round",
16282
- strokeLinejoin: "round"
16283
- }
16284
- ) });
16285
- }
16286
16905
  var wordmarkImgStyle5 = {
16287
16906
  height: 22,
16288
16907
  width: "auto",
@@ -16300,15 +16919,84 @@ var contentStyle16 = {
16300
16919
  minHeight: 0,
16301
16920
  width: "100%"
16302
16921
  };
16303
- var headingStyle13 = (color) => ({
16922
+ var headingStyle13 = (color, fontWeight) => ({
16304
16923
  fontSize: 24,
16305
- fontWeight: 700,
16924
+ fontWeight,
16306
16925
  lineHeight: "normal",
16307
16926
  letterSpacing: 0,
16308
16927
  color,
16309
- margin: 0,
16928
+ margin: "8px 0 8px",
16310
16929
  textAlign: "center"
16311
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
+ };
16312
17000
  var cardStyle2 = (bg, radius) => ({
16313
17001
  background: bg,
16314
17002
  borderRadius: radius,
@@ -16316,7 +17004,9 @@ var cardStyle2 = (bg, radius) => ({
16316
17004
  width: "100%",
16317
17005
  boxSizing: "border-box",
16318
17006
  display: "flex",
17007
+ flex: 1,
16319
17008
  flexDirection: "column",
17009
+ justifyContent: "center",
16320
17010
  gap: 4
16321
17011
  });
16322
17012
  var rowStyle6 = {
@@ -16324,10 +17014,25 @@ var rowStyle6 = {
16324
17014
  alignItems: "center",
16325
17015
  gap: 16,
16326
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,
16327
17021
  borderRadius: 16,
16328
17022
  width: "100%",
16329
17023
  boxSizing: "border-box"
16330
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
+ });
16331
17036
  var completeBadgeStyle = (color) => ({
16332
17037
  width: 24,
16333
17038
  height: 24,
@@ -16345,13 +17050,13 @@ var pendingBadgeStyle = (color) => ({
16345
17050
  background: color,
16346
17051
  flexShrink: 0
16347
17052
  });
16348
- var labelStyle7 = (color) => ({
17053
+ var labelStyle7 = (color, fontWeight) => ({
16349
17054
  fontSize: "1rem",
16350
- fontWeight: 400,
17055
+ fontWeight,
16351
17056
  lineHeight: 1.25,
16352
17057
  color
16353
17058
  });
16354
- var footerStackStyle6 = {
17059
+ var footerStackStyle5 = {
16355
17060
  display: "flex",
16356
17061
  flexDirection: "column",
16357
17062
  gap: 16,
@@ -16363,32 +17068,6 @@ var retryStackStyle = {
16363
17068
  gap: 8,
16364
17069
  width: "100%"
16365
17070
  };
16366
- var lockBannerStyle3 = {
16367
- display: "flex",
16368
- alignItems: "flex-start",
16369
- gap: 16,
16370
- padding: "12px 16px 12px 12px",
16371
- borderRadius: 16,
16372
- width: "100%",
16373
- boxSizing: "border-box"
16374
- };
16375
- var lockIconWrapStyle3 = (color) => ({
16376
- flexShrink: 0,
16377
- width: 24,
16378
- height: 24,
16379
- display: "inline-flex",
16380
- alignItems: "center",
16381
- justifyContent: "center",
16382
- color
16383
- });
16384
- var lockBannerTextStyle3 = (color) => ({
16385
- margin: 0,
16386
- flex: 1,
16387
- fontSize: 12,
16388
- fontWeight: 500,
16389
- lineHeight: "normal",
16390
- color
16391
- });
16392
17071
  function ConfirmSignScreen({
16393
17072
  walletName,
16394
17073
  chainFamily,
@@ -16514,7 +17193,7 @@ function TokenPickerScreen({
16514
17193
  }
16515
17194
  const entries2 = [];
16516
17195
  for (const wallet of account.wallets) {
16517
- for (const source of wallet.sources) {
17196
+ for (const source of wallet.sources ?? []) {
16518
17197
  const visibleBalance = source.balance.available.amount;
16519
17198
  if (!isSelectableDepositSourceAmountUsd(visibleBalance, minDepositFloor)) continue;
16520
17199
  entries2.push({
@@ -17355,22 +18034,45 @@ function buildOpenWalletScreenProps({
17355
18034
  function isApprovingInWalletAction(actionType) {
17356
18035
  return actionType === "APPROVE_PERMIT2" || actionType === "SIGN_PERMIT2" || actionType === "APPROVE_SPL";
17357
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
+ }
17358
18047
  function buildApprovingInWalletScreenProps({
17359
18048
  flow,
17360
18049
  remote,
18050
+ derived,
17361
18051
  handlers
17362
18052
  }) {
17363
18053
  const { state } = flow;
17364
18054
  const setupToken = state.setupDepositToken;
17365
- 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");
17366
18060
  const isSolana = setupToken != null && state.chains.find((c) => c.name === setupToken.chainName)?.chainFamily === "svm";
17367
18061
  const step = actionType === "SIGN_PERMIT2" ? "sign" : actionType === "APPROVE_SPL" ? "spl" : actionType == null && isSolana ? "spl" : "approve";
17368
18062
  const complete = remote.authExecutorCompleted && actionType == null && !remote.authExecutorExecuting;
18063
+ const awaitingApproval = remote.authExecutorAwaitingApproval ?? false;
18064
+ const signing = isApprovingInWalletAction(actionType) && !awaitingApproval && !complete;
17369
18065
  return {
17370
18066
  tokenSymbol: setupToken?.symbol ?? null,
17371
18067
  chainName: setupToken?.chainName ?? null,
17372
18068
  step,
17373
18069
  complete,
18070
+ signing,
18071
+ spendingLimitLabel,
18072
+ destinationAddress,
18073
+ tokenLogoUri: derived.selectedSource?.token.logoURI ?? null,
18074
+ awaitingApproval,
18075
+ onApprove: handlers.onApprove,
17374
18076
  error: flow.state.error || remote.authExecutorError,
17375
18077
  onRetry: handlers.onRetryAuthorization,
17376
18078
  onLogout: flow.isDesktop ? handlers.onLogout : void 0,
@@ -17389,7 +18091,7 @@ function buildLinkTokensScreenProps({
17389
18091
  handlers
17390
18092
  }) {
17391
18093
  const sourceOptions = remote.pendingSelectSource?.metadata?.options ?? [];
17392
- const rowContexts = derived.selectSourceChoices.flatMap(
18094
+ const supportedRows = derived.selectSourceChoices.flatMap(
17393
18095
  (chain) => chain.tokens.map((t) => {
17394
18096
  const rawOption = sourceOptions.find(
17395
18097
  (option) => option.chainName === chain.chainName && option.tokenSymbol === t.tokenSymbol
@@ -17409,6 +18111,10 @@ function buildLinkTokensScreenProps({
17409
18111
  };
17410
18112
  })
17411
18113
  );
18114
+ const rowContexts = [
18115
+ ...supportedRows.filter((row) => isVisibleUsdAmountAtTwoDecimals(row.balanceUsd)),
18116
+ ...supportedRows.filter((row) => !isVisibleUsdAmountAtTwoDecimals(row.balanceUsd))
18117
+ ];
17412
18118
  rowContexts.push(
17413
18119
  ...derived.selectSourceUnsupportedChoices.flatMap(
17414
18120
  (chain) => chain.tokens.map((t) => ({
@@ -17430,6 +18136,17 @@ function buildLinkTokensScreenProps({
17430
18136
  balanceLabel: `${formatNativeAmount(native.amount)} ${native.tokenSymbol}`
17431
18137
  }))
17432
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
+ }
17433
18150
  const entries2 = rowContexts.map(
17434
18151
  ({ symbol, chainName, balanceUsd, notSupported, tokenLogoUri, balanceLabel }) => ({
17435
18152
  tokenSymbol: symbol,
@@ -17442,8 +18159,6 @@ function buildLinkTokensScreenProps({
17442
18159
  })
17443
18160
  );
17444
18161
  const firstSupportedIndex = rowContexts.findIndex((row) => !row.notSupported);
17445
- const selectedTokenSymbol = flow.state.setupDepositToken?.symbol ?? derived.selectSourceRecommended?.tokenSymbol;
17446
- const selectedChainName = flow.state.setupDepositToken?.chainName ?? derived.selectSourceRecommended?.chainName;
17447
18162
  const selectedIndex = (() => {
17448
18163
  if (!selectedTokenSymbol || !selectedChainName) {
17449
18164
  return firstSupportedIndex;
@@ -17535,9 +18250,10 @@ function buildDepositScreenProps({
17535
18250
  const registryChainIds = derived.walletConnectChainIdsByAccount[account.id] ?? null;
17536
18251
  const approvedChainIds = account.walletConnect?.approvedChainIds ?? null;
17537
18252
  for (const wallet of account.wallets) {
17538
- for (const source of wallet.sources) {
18253
+ for (const source of wallet.sources ?? []) {
17539
18254
  const balance = source.balance.available.amount;
17540
- if (!isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
18255
+ const isAuthorized = source.token.status === "AUTHORIZED";
18256
+ if (!isAuthorized && !isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
17541
18257
  const chain = flow.state.chains.find((c) => c.name === wallet.chain.name);
17542
18258
  const requiresAuth = source.token.status != null && source.token.status !== "AUTHORIZED";
17543
18259
  const notSupported = requiresAuth && chain?.commonId != null && registryChainIds != null && !registryChainIds.includes(chain.commonId) && !(approvedChainIds?.includes(chain.commonId) ?? false);
@@ -17559,7 +18275,10 @@ function buildDepositScreenProps({
17559
18275
  }
17560
18276
  return {
17561
18277
  merchantName,
17562
- 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,
17563
18282
  remainingLimit: selectedSource != null ? selectedSource.remainingAllowance ?? null : selectedAccount?.remainingAllowance ?? null,
17564
18283
  tokenCount,
17565
18284
  initialAmount: parsedAmt,
@@ -17750,6 +18469,28 @@ function StepRendererContent({
17750
18469
  screen
17751
18470
  }) {
17752
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]);
17753
18494
  switch (screen) {
17754
18495
  case "loading":
17755
18496
  return /* @__PURE__ */ jsxRuntime.jsx(BlinkLoadingScreen, {});
@@ -17778,11 +18519,9 @@ function StepRendererContent({
17778
18519
  case "wallet-picker":
17779
18520
  return /* @__PURE__ */ jsxRuntime.jsx(WalletPickerScreen, { ...buildWalletPickerScreenProps(input) });
17780
18521
  case "open-wallet": {
17781
- const currentActionType = input.remote.authExecutorCurrentAction?.type;
17782
- const inSigningAction = isApprovingInWalletAction(currentActionType);
17783
- 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;
17784
18523
  const settlingPostSign = input.flow.state.linkSettling;
17785
- if (inSigningAction || pastPairingTransient || settlingPostSign) {
18524
+ if (pastSourceSelection || settlingPostSign) {
17786
18525
  return /* @__PURE__ */ jsxRuntime.jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17787
18526
  }
17788
18527
  return /* @__PURE__ */ jsxRuntime.jsx(OpenWalletScreen, { ...buildOpenWalletScreenProps(input) });
@@ -17792,12 +18531,9 @@ function StepRendererContent({
17792
18531
  if (phase.step === "wallet-setup" && phase.pendingSourceWait) {
17793
18532
  return /* @__PURE__ */ jsxRuntime.jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
17794
18533
  }
17795
- const inSigningAction = isApprovingInWalletAction(
17796
- input.remote.authExecutorCurrentAction?.type
17797
- );
17798
- const justResolvedSelectSource = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null;
18534
+ const pastSourceSelection = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && input.remote.sourceSelectionResolved;
17799
18535
  const settlingPostSign = input.flow.state.linkSettling;
17800
- if (inSigningAction || justResolvedSelectSource || settlingPostSign) {
18536
+ if (pastSourceSelection || settlingPostSign) {
17801
18537
  return /* @__PURE__ */ jsxRuntime.jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17802
18538
  }
17803
18539
  return /* @__PURE__ */ jsxRuntime.jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
@@ -17881,11 +18617,12 @@ var PaymentErrorBoundary = class extends react.Component {
17881
18617
  };
17882
18618
  function selectedSourceForWallet(selectedWallet, selectedTokenSymbol, depositAmount, priorityContext) {
17883
18619
  if (!selectedWallet) return null;
18620
+ const walletSources = selectedWallet.sources ?? [];
17884
18621
  if (selectedTokenSymbol) {
17885
- return selectedWallet.sources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
18622
+ return walletSources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
17886
18623
  }
17887
18624
  const walletChainName = selectedWallet.chain.name;
17888
- const ranked = selectedWallet.sources.map((source, index) => {
18625
+ const ranked = walletSources.map((source, index) => {
17889
18626
  return { source, index };
17890
18627
  }).sort((a, b) => {
17891
18628
  const priority = compareDepositSourcePriority(
@@ -17933,7 +18670,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17933
18670
  let maxSourceBalance = 0;
17934
18671
  for (const acct of state.accounts) {
17935
18672
  for (const wallet of acct.wallets) {
17936
- for (const source of wallet.sources) {
18673
+ for (const source of wallet.sources ?? []) {
17937
18674
  if (source.balance.available.amount > maxSourceBalance) {
17938
18675
  maxSourceBalance = source.balance.available.amount;
17939
18676
  }
@@ -17943,7 +18680,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17943
18680
  let tokenCount = 0;
17944
18681
  for (const acct of state.accounts) {
17945
18682
  for (const wallet of acct.wallets) {
17946
- tokenCount += wallet.sources.filter((s) => s.balance.available.amount > 0).length;
18683
+ tokenCount += (wallet.sources ?? []).filter((s) => s.balance.available.amount > 0).length;
17947
18684
  }
17948
18685
  }
17949
18686
  return {
@@ -18163,6 +18900,31 @@ function usePasskeyHandlers(dispatch, apiBaseUrl) {
18163
18900
  };
18164
18901
  }
18165
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
+
18166
18928
  // src/transferSourceResolution.ts
18167
18929
  var STANDARD_TRANSFER_SOURCE_UNAVAILABLE_ERROR = "Selected payment source is no longer available. Choose or link an active wallet before depositing.";
18168
18930
  function resolveStandardTransferSource({
@@ -18214,14 +18976,14 @@ function useTransferHandlers(deps) {
18214
18976
  const processingStartedAtRef = react.useRef(null);
18215
18977
  const pollingTransferIdRef = react.useRef(null);
18216
18978
  const depositSelectionReloadCountRef = react.useRef(0);
18217
- const reloadAccounts = react.useCallback(async () => {
18979
+ const reloadAccounts = react.useCallback(async (opts) => {
18218
18980
  depositSelectionReloadCountRef.current += 1;
18219
18981
  if (depositSelectionReloadCountRef.current === 1) {
18220
18982
  dispatch({ type: "SET_DEPOSIT_SELECTION_REFRESHING", value: true });
18221
18983
  }
18222
18984
  try {
18223
18985
  const token = await getAccessToken();
18224
- if (!token || !activeCredentialId) return;
18986
+ if (!token || !activeCredentialId) return null;
18225
18987
  const [accts, prov] = await Promise.all([
18226
18988
  fetchAccounts(apiBaseUrl, token, activeCredentialId),
18227
18989
  fetchProviders(apiBaseUrl, token)
@@ -18245,6 +19007,20 @@ function useTransferHandlers(deps) {
18245
19007
  defaults,
18246
19008
  resetSelectedTokenSymbol
18247
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;
18248
19024
  } finally {
18249
19025
  depositSelectionReloadCountRef.current = Math.max(
18250
19026
  0,
@@ -18440,7 +19216,7 @@ function useSourceSelectionHandlers(_dispatch, orchestrator, options) {
18440
19216
  const selectSourceChoices = react.useMemo(() => {
18441
19217
  if (!pendingSelectSourceAction) return [];
18442
19218
  const sourceOptions = pendingSelectSourceAction.metadata?.options ?? [];
18443
- return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd);
19219
+ return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd, true);
18444
19220
  }, [pendingSelectSourceAction, minTransferAmountUsd]);
18445
19221
  const selectSourceUnsupportedChoices = react.useMemo(() => {
18446
19222
  if (!pendingSelectSourceAction) return [];
@@ -18626,13 +19402,6 @@ function buildReauthorizationSessionOptions(params) {
18626
19402
  return options;
18627
19403
  }
18628
19404
 
18629
- // src/tokenAuthorizationRunOptions.ts
18630
- function buildDesktopTokenAuthorizationOnlyRunOptions(chainName, tokenSymbol) {
18631
- return {
18632
- autoResolveSource: { chainName, tokenSymbol }
18633
- };
18634
- }
18635
-
18636
19405
  // src/oneTapDefaults.ts
18637
19406
  var DEFAULT_ONE_TAP_ALLOWANCE_USD = 1e4;
18638
19407
  async function ensureDefaultOneTapAllowance(apiBaseUrl, token) {
@@ -18653,7 +19422,7 @@ function resolveReauthorizationTarget(input) {
18653
19422
  };
18654
19423
  }
18655
19424
  const wallet = input.account?.wallets.find((candidate) => candidate.id === input.selectedWalletId);
18656
- const source = wallet?.sources.find(
19425
+ const source = wallet?.sources?.find(
18657
19426
  (candidate) => input.selectedTokenSymbol ? candidate.token.symbol === input.selectedTokenSymbol : candidate.token.status === "AUTHORIZED"
18658
19427
  );
18659
19428
  return {
@@ -18664,6 +19433,28 @@ function resolveReauthorizationTarget(input) {
18664
19433
  };
18665
19434
  }
18666
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
+
18667
19458
  // src/walletConnectLinks.ts
18668
19459
  function buildWalletConnectDeeplink(links, walletConnectUri) {
18669
19460
  const baseLink = links?.native ?? links?.universal;
@@ -18795,6 +19586,8 @@ function useProviderHandlers(deps) {
18795
19586
  setupDepositToken,
18796
19587
  tryStartExtensionConnectForReownWallet
18797
19588
  } = deps;
19589
+ const setupDepositTokenRef = react.useRef(setupDepositToken);
19590
+ setupDepositTokenRef.current = setupDepositToken;
18798
19591
  const checkWalletConnectChainSupport = react.useMemo(
18799
19592
  () => deps.checkWalletConnectChainSupport ?? ((reownWalletId, chainId) => checkReownWalletChainSupport(BLINK_WC_PROJECT_ID, reownWalletId, chainId)),
18800
19593
  [deps.checkWalletConnectChainSupport]
@@ -19010,7 +19803,7 @@ function useProviderHandlers(deps) {
19010
19803
  }
19011
19804
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19012
19805
  try {
19013
- await reloadAccounts();
19806
+ await reloadAccounts({ awaitBalances: true });
19014
19807
  } finally {
19015
19808
  dispatch({ type: "END_LINK_SETTLING" });
19016
19809
  }
@@ -19137,7 +19930,7 @@ function useProviderHandlers(deps) {
19137
19930
  }
19138
19931
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19139
19932
  try {
19140
- await reloadAccounts();
19933
+ await reloadAccounts({ awaitBalances: true });
19141
19934
  } finally {
19142
19935
  dispatch({ type: "END_LINK_SETTLING" });
19143
19936
  }
@@ -19361,7 +20154,7 @@ function useProviderHandlers(deps) {
19361
20154
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19362
20155
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19363
20156
  try {
19364
- await reloadAccounts();
20157
+ await reloadAccounts({ awaitBalances: true });
19365
20158
  } finally {
19366
20159
  dispatch({ type: "END_LINK_SETTLING" });
19367
20160
  }
@@ -19463,7 +20256,9 @@ function useProviderHandlers(deps) {
19463
20256
  const result = await orchestrator.run(
19464
20257
  sessionId,
19465
20258
  {
19466
- ...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.
19467
20262
  // Pin the existing account id so pairing and signing share one
19468
20263
  // runtime (see handleSelectWalletConnectWallet).
19469
20264
  walletConnectRuntimeKey: runtimeAccountId,
@@ -19471,10 +20266,11 @@ function useProviderHandlers(deps) {
19471
20266
  // Require the target token's chain at pairing. A reused session
19472
20267
  // that doesn't approve it re-pairs with the chain required; a
19473
20268
  // wallet that approves the connection without it fails
19474
- // OPEN_PROVIDER with a clear error instead of the auto-resolved
19475
- // SELECT_SOURCE 422 (INVALID_SELECT_SOURCE_SELECTION) — e.g.
19476
- // authorizing USDC on HyperEVM (999, a WC-optional chain) with a
19477
- // 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.
19478
20274
  walletConnectRequiredChainId: chainId,
19479
20275
  walletConnectRequiredChainName: inlineChain.name,
19480
20276
  onWalletConnectDisplayUri: (uri) => {
@@ -19510,11 +20306,17 @@ function useProviderHandlers(deps) {
19510
20306
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19511
20307
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19512
20308
  try {
19513
- await reloadAccounts();
20309
+ await reloadAccounts({ awaitBalances: true });
19514
20310
  } finally {
19515
20311
  dispatch({ type: "END_LINK_SETTLING" });
19516
20312
  }
19517
- 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
+ });
19518
20320
  dispatch({ type: "DISCARD_SAVED_SELECTION" });
19519
20321
  } catch (err) {
19520
20322
  captureException(err);
@@ -19829,10 +20631,7 @@ function useProviderHandlers(deps) {
19829
20631
  walletDeeplinks: walletDeeplinks ?? null,
19830
20632
  providerId: matchedProvider?.id ?? null
19831
20633
  });
19832
- const result = await orchestrator.run(
19833
- session.id,
19834
- buildDesktopTokenAuthorizationOnlyRunOptions(inlineChain.name, tokenSymbol)
19835
- );
20634
+ const result = await orchestrator.run(session.id);
19836
20635
  if (result.status === "cancelled") {
19837
20636
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19838
20637
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
@@ -19844,14 +20643,25 @@ function useProviderHandlers(deps) {
19844
20643
  }
19845
20644
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19846
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
+ };
19847
20652
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19848
20653
  try {
19849
- 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" });
19850
20662
  } finally {
19851
20663
  dispatch({ type: "END_LINK_SETTLING" });
19852
20664
  }
19853
- dispatch({ type: "SELECT_TOKEN", walletId: _walletId, tokenSymbol, accountId: _accountId });
19854
- dispatch({ type: "DISCARD_SAVED_SELECTION" });
19855
20665
  } catch (err) {
19856
20666
  captureException(err);
19857
20667
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
@@ -20311,6 +21121,15 @@ function useDataLoadEffect(deps) {
20311
21121
  hasActiveCredential: !!state.activeCredentialId,
20312
21122
  loading: loadingDataRef.current
20313
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
+ });
20314
21133
  if (loadAction === "reset") {
20315
21134
  loadingDataRef.current = false;
20316
21135
  dispatch({ type: "DATA_LOAD_END" });
@@ -20328,13 +21147,21 @@ function useDataLoadEffect(deps) {
20328
21147
  const load = async () => {
20329
21148
  dispatch({ type: "DATA_LOAD_START" });
20330
21149
  try {
21150
+ appendDebug("warn", "dataLoad: requesting access token");
20331
21151
  const token = await getAccessTokenRef.current();
21152
+ appendDebug("warn", "dataLoad: token result", { hasToken: !!token, cancelled });
20332
21153
  if (!token) throw new Error("Not authenticated");
20333
21154
  const [prov, accts, chn] = await Promise.all([
20334
21155
  fetchProviders(apiBaseUrl, token),
20335
21156
  fetchAccounts(apiBaseUrl, token, credentialId),
20336
21157
  fetchChains(apiBaseUrl, token)
20337
21158
  ]);
21159
+ appendDebug("warn", "dataLoad: fetch resolved", {
21160
+ cancelled,
21161
+ providers: prov.length,
21162
+ accounts: accts.length,
21163
+ chains: chn.length
21164
+ });
20338
21165
  if (cancelled) return;
20339
21166
  const parsedAmt = depositAmountRef.current != null ? depositAmountRef.current : 0;
20340
21167
  const priorityContext = resolveDepositPriorityContext(accts, chn, destination);
@@ -20360,7 +21187,16 @@ function useDataLoadEffect(deps) {
20360
21187
  resetSelectedTokenSymbol
20361
21188
  });
20362
21189
  if (clearMobile) clearMobileFlowState();
21190
+ void fetchBalancesByAccountId(apiBaseUrl, token, credentialId, accts).then(
21191
+ (balancesByAccountId) => {
21192
+ dispatch({ type: "BALANCES_LOADED", balancesByAccountId });
21193
+ }
21194
+ );
20363
21195
  } catch (err) {
21196
+ appendDebug("error", "dataLoad: threw", {
21197
+ cancelled,
21198
+ message: err instanceof Error ? err.message : String(err)
21199
+ });
20364
21200
  if (!cancelled) {
20365
21201
  captureException(err);
20366
21202
  dispatch({
@@ -20377,6 +21213,7 @@ function useDataLoadEffect(deps) {
20377
21213
  };
20378
21214
  load();
20379
21215
  return () => {
21216
+ appendDebug("warn", "dataLoadEffect cleanup (cancelling in-flight load)");
20380
21217
  cancelled = true;
20381
21218
  loadingDataRef.current = false;
20382
21219
  dispatch({ type: "DATA_LOAD_END" });
@@ -21512,7 +22349,7 @@ function BlinkPaymentInner({
21512
22349
  const handleSetDepositToken = react.useCallback((symbol, chainName, walletId, tokenAddress, chainId) => {
21513
22350
  dispatch({ type: "SET_SETUP_DEPOSIT_TOKEN", symbol, chainName, walletId, tokenAddress, chainId });
21514
22351
  }, []);
21515
- const handleConfirmSetupDeposit = react.useCallback(async () => {
22352
+ const handleConfirmSetupDeposit = react.useCallback(async (limit) => {
21516
22353
  const plan = planConfirmSetupDeposit({
21517
22354
  setupDepositToken: state.setupDepositToken,
21518
22355
  setupSelectedSourceOption,
@@ -21522,6 +22359,19 @@ function BlinkPaymentInner({
21522
22359
  dispatch({ type: "SET_ERROR", error: plan.error });
21523
22360
  return;
21524
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 });
21525
22375
  for (const action of plan.actions) {
21526
22376
  dispatch(action);
21527
22377
  }
@@ -21530,6 +22380,8 @@ function BlinkPaymentInner({
21530
22380
  state.selectedAccountId,
21531
22381
  state.setupDepositToken,
21532
22382
  setupSelectedSourceOption,
22383
+ effectiveGetAccessToken,
22384
+ apiBaseUrl,
21533
22385
  dispatch,
21534
22386
  orchestrator
21535
22387
  ]);
@@ -21590,6 +22442,9 @@ function BlinkPaymentInner({
21590
22442
  }
21591
22443
  })();
21592
22444
  }, [orchestrator]);
22445
+ const handleApprove = react.useCallback(() => {
22446
+ authExecutor.approveAuthorization();
22447
+ }, [authExecutor]);
21593
22448
  const handleSetPhase = react.useCallback((phase) => {
21594
22449
  clearScreenErrors();
21595
22450
  if (phase.step === "deposit") {
@@ -21611,6 +22466,7 @@ function BlinkPaymentInner({
21611
22466
  onConfirmSign: transfer.handleConfirmSign,
21612
22467
  onRetryMobileStatus: mobileFlow.handleRetryMobileStatus,
21613
22468
  onRetryAuthorization: handleAuthorizationRetry,
22469
+ onApprove: handleApprove,
21614
22470
  onRetryTransferSigning: transfer.handleRetryTransferSigning,
21615
22471
  onBackFromOpenWallet: handleBackFromOpenWallet,
21616
22472
  onLogout: handleLogout,
@@ -21681,6 +22537,7 @@ function BlinkPaymentInner({
21681
22537
  handleConfirmSetupDeposit,
21682
22538
  handleBackFromSetupDeposit,
21683
22539
  handleAuthorizationRetry,
22540
+ handleApprove,
21684
22541
  disconnectWallets,
21685
22542
  state.desktopWait?.walletForegroundLink
21686
22543
  ]);
@@ -21720,6 +22577,9 @@ function BlinkPaymentInner({
21720
22577
  authExecutorError: authExecutor.error,
21721
22578
  authExecutorExecuting: authExecutor.executing,
21722
22579
  authExecutorCurrentAction: authExecutor.currentAction,
22580
+ authExecutorAwaitingApproval: authExecutor.awaitingApproval,
22581
+ authExecutorApprovalDestinationAddress: authExecutor.approvalDestinationAddress,
22582
+ authApprovalSmartAccountAddress: orchestrator.approvalSmartAccountAddress,
21723
22583
  authExecutorCompleted: orchestrator.orchestratorCompleted,
21724
22584
  transferSigningSigning: transferSigning.signing,
21725
22585
  transferSigningError: transferSigning.error,