@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.js CHANGED
@@ -55,7 +55,11 @@ var darkTheme = {
55
55
  shadowLg: "0 18px 44px rgba(0,0,0,0.42)",
56
56
  radius: "14px",
57
57
  radiusLg: "24px",
58
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
58
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
59
+ fontWeightRegular: 400,
60
+ fontWeightMedium: 500,
61
+ fontWeightSemibold: 600,
62
+ fontWeightBold: 700
59
63
  };
60
64
  var lightTheme = {
61
65
  bg: "#ebf9fb",
@@ -87,7 +91,11 @@ var lightTheme = {
87
91
  shadowLg: "0 20px 48px rgba(19, 61, 75, 0.14)",
88
92
  radius: "14px",
89
93
  radiusLg: "24px",
90
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
94
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
95
+ fontWeightRegular: 400,
96
+ fontWeightMedium: 500,
97
+ fontWeightSemibold: 600,
98
+ fontWeightBold: 700
91
99
  };
92
100
  var lightTransparentTheme = {
93
101
  ...lightTheme
@@ -130,7 +138,13 @@ var lightThemeNew = {
130
138
  shadowLg: "0 20px 48px rgba(0, 0, 0, 0.14)",
131
139
  radius: "14px",
132
140
  radiusLg: "24px",
133
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
141
+ // Figma redesign uses Inter; fall back to the system stack when the host
142
+ // page hasn't loaded the Inter webfont.
143
+ fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
144
+ fontWeightRegular: 400,
145
+ fontWeightMedium: 500,
146
+ fontWeightSemibold: 600,
147
+ fontWeightBold: 700
134
148
  };
135
149
  var darkThemeNew = { ...lightThemeNew };
136
150
  var lightTransparentThemeNew = { ...lightThemeNew };
@@ -300,7 +314,7 @@ function resolveDepositPriorityContext(accounts, chains, destination) {
300
314
  for (const account of accounts) {
301
315
  for (const wallet of account.wallets) {
302
316
  if (wallet.chain.name !== destinationChainName) continue;
303
- const source = wallet.sources.find(
317
+ const source = (wallet.sources ?? []).find(
304
318
  (candidate) => candidate.address.toLowerCase() === destinationTokenAddress
305
319
  );
306
320
  if (source) {
@@ -334,6 +348,11 @@ function getWalletAddress(wallet) {
334
348
  const address = wallet.name.trim();
335
349
  return address.length > 0 ? address : null;
336
350
  }
351
+ function truncateMiddle(address, head = 8, tail = 6) {
352
+ const trimmed = address.trim();
353
+ if (trimmed.length <= head + tail + 4) return trimmed;
354
+ return `${trimmed.slice(0, head)}...${trimmed.slice(-tail)}`;
355
+ }
337
356
  function getAddressableWallets(account) {
338
357
  return account.wallets.filter((wallet) => getWalletAddress(wallet) != null);
339
358
  }
@@ -341,7 +360,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
341
360
  const wallets = getAddressableWallets(account);
342
361
  if (wallets.length === 0) return null;
343
362
  const ranked = wallets.map((wallet, walletIndex) => {
344
- const bestSource = wallet.sources.map((source, sourceIndex) => ({
363
+ const bestSource = (wallet.sources ?? []).map((source, sourceIndex) => ({
345
364
  source,
346
365
  sourceIndex
347
366
  })).sort((a, b) => {
@@ -366,7 +385,7 @@ function getPreferredDepositWallet(account, transferAmount, priorityContext) {
366
385
  if (a.wallet.status !== b.wallet.status) {
367
386
  return a.wallet.status === "ACTIVE" ? -1 : 1;
368
387
  }
369
- const balanceDiff = b.wallet.balance.available.amount - a.wallet.balance.available.amount;
388
+ const balanceDiff = (b.wallet.balance?.available.amount ?? 0) - (a.wallet.balance?.available.amount ?? 0);
370
389
  if (balanceDiff !== 0) return balanceDiff;
371
390
  return a.walletIndex - b.walletIndex;
372
391
  });
@@ -382,7 +401,7 @@ function resolveDepositSelectionAfterRefresh(accounts, transferAmount, prev, pri
382
401
  const wallet = acct?.wallets.find((w) => w.id === selectedWalletId);
383
402
  if (wallet) {
384
403
  if (selectedTokenSymbol) {
385
- const hasToken = wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
404
+ const hasToken = wallet.sources == null || wallet.sources.some((s) => s.token.symbol === selectedTokenSymbol);
386
405
  if (hasToken) {
387
406
  return {
388
407
  defaults: { accountId: selectedAccountId, walletId: selectedWalletId },
@@ -420,7 +439,9 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
420
439
  const eligibleAccounts = getDepositEligibleAccounts(accounts);
421
440
  if (eligibleAccounts.length === 0) return null;
422
441
  const accountHasSufficientBalanceWallet = (account) => getAddressableWallets(account).some(
423
- (wallet) => wallet.status === "ACTIVE" && wallet.sources.some((source) => source.balance.available.amount >= transferAmount)
442
+ (wallet) => wallet.status === "ACTIVE" && (wallet.sources ?? []).some(
443
+ (source) => source.balance.available.amount >= transferAmount
444
+ )
424
445
  );
425
446
  if (transferAmount <= 0 && selectedAccountId) {
426
447
  const selectedAccount = eligibleAccounts.find((account) => account.id === selectedAccountId);
@@ -442,7 +463,7 @@ function resolveDepositSelection(accounts, transferAmount, selectedAccountId, pr
442
463
  transferAmount,
443
464
  priorityContext
444
465
  );
445
- const preferredSource = preferredWallet ? [...preferredWallet.sources].sort((a, b) => compareDepositSourcePriority(
466
+ const preferredSource = preferredWallet ? [...preferredWallet.sources ?? []].sort((a, b) => compareDepositSourcePriority(
446
467
  sourcePriorityInput(preferredWallet.chain.name, a, transferAmount, priorityContext),
447
468
  sourcePriorityInput(preferredWallet.chain.name, b, transferAmount, priorityContext)
448
469
  ))[0] : void 0;
@@ -508,7 +529,7 @@ function buildNativeUnsupportedEntries(options) {
508
529
  logoURI: option.logoURI ?? null
509
530
  })).filter((entry) => entry.amount > 0);
510
531
  }
511
- function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD) {
532
+ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SEND_AMOUNT_USD, includeUnfundedTokens = false) {
512
533
  const chainChoices = [];
513
534
  const chainIndexByName = /* @__PURE__ */ new Map();
514
535
  for (const option of options) {
@@ -549,7 +570,7 @@ function buildSelectSourceChoices(options, minTransferAmountUsd = DEFAULT_MIN_SE
549
570
  }
550
571
  }
551
572
  return chainChoices.map((chain) => {
552
- const visibleTokens = chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
573
+ const visibleTokens = includeUnfundedTokens ? chain.tokens : chain.tokens.filter((t) => isSelectableDepositSourceAmountUsd(t.balance, minTransferAmountUsd));
553
574
  return {
554
575
  ...chain,
555
576
  balance: visibleTokens.reduce((sum, token) => sum + token.balance, 0),
@@ -1937,464 +1958,197 @@ function screenForPhase(phase) {
1937
1958
  }
1938
1959
  }
1939
1960
 
1940
- // src/passkey-delegation.ts
1941
- var PasskeyIframeBlockedError = class extends Error {
1942
- constructor(message = "Passkey creation is not supported in this browser context.") {
1943
- super(message);
1944
- this.name = "PasskeyIframeBlockedError";
1961
+ // src/api.ts
1962
+ var api_exports = {};
1963
+ __export(api_exports, {
1964
+ createAccount: () => createAccount,
1965
+ createAccountAuthorizationSession: () => createAccountAuthorizationSession,
1966
+ createManualTransfer: () => createManualTransfer,
1967
+ createTransfer: () => createTransfer,
1968
+ fetchAccount: () => fetchAccount,
1969
+ fetchAccountBalances: () => fetchAccountBalances,
1970
+ fetchAccounts: () => fetchAccounts,
1971
+ fetchAuthorizationSession: () => fetchAuthorizationSession,
1972
+ fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
1973
+ fetchChains: () => fetchChains,
1974
+ fetchManualTransferSession: () => fetchManualTransferSession,
1975
+ fetchManualTransferSources: () => fetchManualTransferSources,
1976
+ fetchMerchantPublicKey: () => fetchMerchantPublicKey,
1977
+ fetchProviders: () => fetchProviders,
1978
+ fetchTransfer: () => fetchTransfer,
1979
+ fetchUserConfig: () => fetchUserConfig,
1980
+ postTransferQuote: () => postTransferQuote,
1981
+ probeActionCompletion: () => probeActionCompletion,
1982
+ refreshManualTransferQuote: () => refreshManualTransferQuote,
1983
+ regenerateTransferSignPayload: () => regenerateTransferSignPayload,
1984
+ registerPasskey: () => registerPasskey,
1985
+ reportActionCompletion: () => reportActionCompletion,
1986
+ reportPasskeyActivity: () => reportPasskeyActivity,
1987
+ setAuthorizationSessionPaymentIntentAmount: () => setAuthorizationSessionPaymentIntentAmount,
1988
+ setAuthorizationSessionProvider: () => setAuthorizationSessionProvider,
1989
+ signTransfer: () => signTransfer,
1990
+ updateManualTransferDepositTargetChain: () => updateManualTransferDepositTargetChain,
1991
+ updateUserConfig: () => updateUserConfig,
1992
+ updateUserConfigBySession: () => updateUserConfigBySession,
1993
+ waitForActionTransactionReceipt: () => waitForActionTransactionReceipt
1994
+ });
1995
+ var DEBUG_BUFFER_CAPACITY = 200;
1996
+ var nextId = 1;
1997
+ var entries = [];
1998
+ var listeners = /* @__PURE__ */ new Set();
1999
+ function notify() {
2000
+ for (const listener of listeners) {
2001
+ try {
2002
+ listener();
2003
+ } catch (err) {
2004
+ console.error("[blink-sdk][debug-log] listener threw:", err);
2005
+ }
1945
2006
  }
1946
- };
1947
- function isInCrossOriginIframe() {
1948
- if (typeof window === "undefined") return false;
1949
- if (window.parent === window) return false;
1950
- try {
1951
- void window.parent.location.origin;
1952
- return false;
1953
- } catch {
1954
- return true;
2007
+ }
2008
+ function appendDebug(level, message, data) {
2009
+ const entry = {
2010
+ id: nextId++,
2011
+ ts: Date.now(),
2012
+ level,
2013
+ message,
2014
+ data
2015
+ };
2016
+ const next = entries.length >= DEBUG_BUFFER_CAPACITY ? entries.slice(entries.length - DEBUG_BUFFER_CAPACITY + 1) : entries.slice();
2017
+ next.push(entry);
2018
+ entries = next;
2019
+ const prefix = "[blink-sdk][debug]";
2020
+ const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.info;
2021
+ if (data !== void 0) {
2022
+ sink(`${prefix} ${message}`, data);
2023
+ } else {
2024
+ sink(`${prefix} ${message}`);
1955
2025
  }
2026
+ notify();
1956
2027
  }
1957
- function isSafari() {
1958
- if (typeof navigator === "undefined") return false;
1959
- const ua = navigator.userAgent;
1960
- return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
2028
+ function subscribeDebug(listener) {
2029
+ listeners.add(listener);
2030
+ return () => {
2031
+ listeners.delete(listener);
2032
+ };
1961
2033
  }
1962
- var VERIFY_POPUP_TIMEOUT_MS = 6e4;
1963
- var POPUP_CLOSED_POLL_MS = 500;
1964
- var POPUP_CLOSED_GRACE_MS = 1e3;
1965
- function findDevicePasskeyViaPopup(options) {
1966
- return new Promise((resolve, reject) => {
1967
- const verificationToken = crypto.randomUUID();
1968
- const payload = {
1969
- ...options,
1970
- verificationToken
1971
- };
1972
- const encoded = btoa(JSON.stringify(payload));
1973
- const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
1974
- const popup = window.open(popupUrl, "blink-passkey-verify");
1975
- if (!popup) {
1976
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
1977
- return;
1978
- }
1979
- let settled = false;
1980
- const timer = setTimeout(() => {
1981
- cleanup();
1982
- resolve(null);
1983
- }, VERIFY_POPUP_TIMEOUT_MS);
1984
- const closedPoll = setInterval(() => {
1985
- if (popup.closed && !settled) {
1986
- clearInterval(closedPoll);
1987
- setTimeout(() => {
1988
- if (!settled) {
1989
- settled = true;
1990
- cleanup();
1991
- checkServerForPasskeyByToken(
1992
- options.authToken,
1993
- options.apiBaseUrl,
1994
- verificationToken
1995
- ).then((result) => {
1996
- resolve(result?.credentialId ?? null);
1997
- }).catch(() => {
1998
- resolve(null);
1999
- });
2000
- }
2001
- }, POPUP_CLOSED_GRACE_MS);
2034
+ function getDebugEntries() {
2035
+ return entries;
2036
+ }
2037
+ function clearDebugEntries() {
2038
+ entries = [];
2039
+ notify();
2040
+ }
2041
+ function useBlinkDebugLog() {
2042
+ return useSyncExternalStore(subscribeDebug, getDebugEntries, getDebugEntries);
2043
+ }
2044
+
2045
+ // src/fetchWithRetry.ts
2046
+ var DEFAULT_MAX_RETRIES = 3;
2047
+ var DEFAULT_BASE_DELAY_MS = 500;
2048
+ var DEFAULT_MAX_JITTER_MS = 200;
2049
+ function isNetworkTypeError(err) {
2050
+ return err instanceof TypeError && /fetch|network|load failed/i.test(err.message);
2051
+ }
2052
+ async function fetchWithRetry(input, init, options) {
2053
+ const maxRetries = DEFAULT_MAX_RETRIES;
2054
+ const baseDelayMs = DEFAULT_BASE_DELAY_MS;
2055
+ const maxJitterMs = DEFAULT_MAX_JITTER_MS;
2056
+ const label = String(input).replace(/https?:\/\/[^/]+/, "");
2057
+ let lastError;
2058
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2059
+ try {
2060
+ return await fetch(input, init);
2061
+ } catch (err) {
2062
+ lastError = err;
2063
+ if (!isNetworkTypeError(err)) {
2064
+ throw err;
2065
+ }
2066
+ if (attempt < maxRetries) {
2067
+ const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * maxJitterMs;
2068
+ appendDebug("warn", `fetchWithRetry: network error, retrying ${label}`, {
2069
+ attempt: attempt + 1,
2070
+ maxRetries,
2071
+ delayMs: Math.round(delay),
2072
+ error: err instanceof Error ? err.message : String(err)
2073
+ });
2074
+ await new Promise((resolve) => setTimeout(resolve, delay));
2002
2075
  }
2003
- }, POPUP_CLOSED_POLL_MS);
2004
- function cleanup() {
2005
- clearTimeout(timer);
2006
- clearInterval(closedPoll);
2007
2076
  }
2077
+ }
2078
+ throw lastError;
2079
+ }
2080
+
2081
+ // src/apiError.ts
2082
+ var ApiError = class extends Error {
2083
+ status;
2084
+ code;
2085
+ constructor(status, code, message) {
2086
+ super(message);
2087
+ this.name = "ApiError";
2088
+ this.status = status;
2089
+ this.code = code;
2090
+ }
2091
+ };
2092
+ function isApiError(err) {
2093
+ if (err instanceof ApiError) return true;
2094
+ return typeof err === "object" && err !== null && "name" in err && err.name === "ApiError";
2095
+ }
2096
+ var SVM_SIGN_PAYLOAD_EXPIRED_CODE = "SVM_SIGN_PAYLOAD_EXPIRED";
2097
+ function isSvmSignExpiredError(err) {
2098
+ return isApiError(err) && err.code === SVM_SIGN_PAYLOAD_EXPIRED_CODE;
2099
+ }
2100
+
2101
+ // src/api.ts
2102
+ async function throwApiError(res) {
2103
+ const body = await res.json().catch(() => null);
2104
+ const detail = body?.error ?? body;
2105
+ const msg = detail?.message ?? res.statusText;
2106
+ const code = detail?.code ?? String(res.status);
2107
+ throw new ApiError(res.status, code, `${res.status} \u2014 ${code}: ${msg}`);
2108
+ }
2109
+ async function fetchProviders(apiBaseUrl, token) {
2110
+ const headers = {};
2111
+ if (token) {
2112
+ headers.Authorization = `Bearer ${token}`;
2113
+ }
2114
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/providers`, { headers });
2115
+ if (!res.ok) await throwApiError(res);
2116
+ const data = await res.json();
2117
+ return data.items;
2118
+ }
2119
+ async function fetchChains(apiBaseUrl, token) {
2120
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/chains`, {
2121
+ headers: { Authorization: `Bearer ${token}` }
2008
2122
  });
2123
+ if (!res.ok) await throwApiError(res);
2124
+ const data = await res.json();
2125
+ return data.items;
2009
2126
  }
2010
- async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
2011
- if (!authToken || !apiBaseUrl) return null;
2012
- const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
2013
- headers: { Authorization: `Bearer ${authToken}` }
2127
+ async function fetchAccounts(apiBaseUrl, token, credentialId) {
2128
+ const params = new URLSearchParams({ credentialId });
2129
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts?${params.toString()}`, {
2130
+ headers: { Authorization: `Bearer ${token}` }
2014
2131
  });
2015
- if (!res.ok) return null;
2016
- const body = await res.json();
2017
- const passkeys = body.config.passkeys ?? [];
2018
- const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
2019
- return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
2132
+ if (!res.ok) await throwApiError(res);
2133
+ const data = await res.json();
2134
+ return data.items;
2020
2135
  }
2021
- function shouldUsePasskeySignupPopup() {
2022
- return isSafari() && isInCrossOriginIframe();
2136
+ async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
2137
+ const params = new URLSearchParams({ credentialId });
2138
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts/${accountId}?${params.toString()}`, {
2139
+ headers: { Authorization: `Bearer ${token}` }
2140
+ });
2141
+ if (!res.ok) await throwApiError(res);
2142
+ return await res.json();
2023
2143
  }
2024
- var SIGNUP_POPUP_TIMEOUT_MS = 12e4;
2025
- function signupWithPasskeyViaPopup() {
2026
- return new Promise((resolve, reject) => {
2027
- const popupUrl = `${window.location.origin}/passkey-signup`;
2028
- const popup = window.open(popupUrl, "blink-passkey-signup");
2029
- if (!popup) {
2030
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2031
- return;
2032
- }
2033
- let settled = false;
2034
- const timer = setTimeout(() => {
2035
- cleanup();
2036
- resolve(null);
2037
- }, SIGNUP_POPUP_TIMEOUT_MS);
2038
- function onMessage(event) {
2039
- if (event.origin !== window.location.origin) return;
2040
- if (event.source !== popup) return;
2041
- const data = event.data;
2042
- if (!data || data.type !== "blink:passkey-signup-complete") return;
2043
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2044
- settled = true;
2045
- cleanup();
2046
- resolve({
2047
- accessToken: data.accessToken,
2048
- credentialId: data.credentialId,
2049
- publicKey: data.publicKey
2050
- });
2051
- }
2052
- window.addEventListener("message", onMessage);
2053
- const closedPoll = setInterval(() => {
2054
- if (popup.closed && !settled) {
2055
- settled = true;
2056
- cleanup();
2057
- resolve(null);
2058
- }
2059
- }, POPUP_CLOSED_POLL_MS);
2060
- function cleanup() {
2061
- clearTimeout(timer);
2062
- clearInterval(closedPoll);
2063
- window.removeEventListener("message", onMessage);
2064
- }
2065
- });
2066
- }
2067
- var LOGIN_POPUP_TIMEOUT_MS = 12e4;
2068
- function loginWithPasskeyViaPopup() {
2069
- return new Promise((resolve, reject) => {
2070
- const popupUrl = `${window.location.origin}/passkey-login`;
2071
- const popup = window.open(popupUrl, "blink-passkey-login");
2072
- if (!popup) {
2073
- reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2074
- return;
2075
- }
2076
- let settled = false;
2077
- const timer = setTimeout(() => {
2078
- cleanup();
2079
- resolve(null);
2080
- }, LOGIN_POPUP_TIMEOUT_MS);
2081
- function onMessage(event) {
2082
- if (event.origin !== window.location.origin) return;
2083
- if (event.source !== popup) return;
2084
- const data = event.data;
2085
- if (!data || data.type !== "blink:passkey-login-complete") return;
2086
- if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2087
- settled = true;
2088
- cleanup();
2089
- resolve({
2090
- accessToken: data.accessToken,
2091
- credentialId: data.credentialId,
2092
- publicKey: data.publicKey
2093
- });
2094
- }
2095
- window.addEventListener("message", onMessage);
2096
- const closedPoll = setInterval(() => {
2097
- if (popup.closed && !settled) {
2098
- settled = true;
2099
- cleanup();
2100
- resolve(null);
2101
- }
2102
- }, POPUP_CLOSED_POLL_MS);
2103
- function cleanup() {
2104
- clearTimeout(timer);
2105
- clearInterval(closedPoll);
2106
- window.removeEventListener("message", onMessage);
2107
- }
2108
- });
2109
- }
2110
-
2111
- // src/credentialIdEncoding.ts
2112
- function credentialIdBase64ToBytes(value) {
2113
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2114
- const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
2115
- const raw = atob(padded);
2116
- const bytes = new Uint8Array(raw.length);
2117
- for (let i = 0; i < raw.length; i++) {
2118
- bytes[i] = raw.charCodeAt(i);
2119
- }
2120
- return bytes;
2121
- }
2122
-
2123
- // src/passkeyRpId.ts
2124
- function normalizeConfiguredDomain(value) {
2125
- return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2126
- }
2127
- function resolveRootDomainFromHostname(hostname) {
2128
- const trimmedHostname = hostname.trim().toLowerCase();
2129
- if (!trimmedHostname) {
2130
- return "localhost";
2131
- }
2132
- if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
2133
- return trimmedHostname;
2134
- }
2135
- const parts = trimmedHostname.split(".").filter(Boolean);
2136
- if (parts.length < 2) {
2137
- return trimmedHostname;
2138
- }
2139
- return parts.slice(-2).join(".");
2140
- }
2141
-
2142
- // src/hooks/passkeyPublic.ts
2143
- function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
2144
- return new Promise((resolve) => {
2145
- if (typeof document === "undefined") {
2146
- resolve();
2147
- return;
2148
- }
2149
- if (document.hasFocus()) {
2150
- resolve();
2151
- return;
2152
- }
2153
- const deadline = Date.now() + timeoutMs;
2154
- const timer = setInterval(() => {
2155
- if (document.hasFocus()) {
2156
- clearInterval(timer);
2157
- resolve();
2158
- } else if (Date.now() >= deadline) {
2159
- clearInterval(timer);
2160
- resolve();
2161
- }
2162
- }, intervalMs);
2163
- });
2164
- }
2165
- function toBase64(buffer) {
2166
- return btoa(String.fromCharCode(...new Uint8Array(buffer)));
2167
- }
2168
- function readEnvValue(name) {
2169
- const meta = import.meta;
2170
- const metaValue = meta.env?.[name];
2171
- if (typeof metaValue === "string" && metaValue.trim().length > 0) {
2172
- return metaValue.trim();
2173
- }
2174
- const processValue = globalThis.process?.env?.[name];
2175
- if (typeof processValue === "string" && processValue.trim().length > 0) {
2176
- return processValue.trim();
2177
- }
2178
- return void 0;
2179
- }
2180
- function resolvePasskeyRpId() {
2181
- const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
2182
- if (configuredDomain) {
2183
- return normalizeConfiguredDomain(configuredDomain);
2184
- }
2185
- if (typeof window !== "undefined") {
2186
- return resolveRootDomainFromHostname(window.location.hostname);
2187
- }
2188
- return "localhost";
2189
- }
2190
- async function deviceHasPasskey(credentialId) {
2191
- const found = await findDevicePasskey([credentialId]);
2192
- return found != null;
2193
- }
2194
- async function findDevicePasskey(credentialIds) {
2195
- if (credentialIds.length === 0) return null;
2196
- try {
2197
- const challenge = new Uint8Array(32);
2198
- crypto.getRandomValues(challenge);
2199
- await waitForDocumentFocus();
2200
- const assertion = await navigator.credentials.get({
2201
- publicKey: {
2202
- challenge,
2203
- rpId: resolvePasskeyRpId(),
2204
- allowCredentials: credentialIds.map((id) => ({
2205
- type: "public-key",
2206
- id: credentialIdBase64ToBytes(id)
2207
- })),
2208
- userVerification: "discouraged",
2209
- timeout: 3e4
2210
- }
2211
- });
2212
- if (!assertion) return null;
2213
- return toBase64(assertion.rawId);
2214
- } catch {
2215
- return null;
2216
- }
2217
- }
2218
-
2219
- // src/api.ts
2220
- var api_exports = {};
2221
- __export(api_exports, {
2222
- createAccount: () => createAccount,
2223
- createAccountAuthorizationSession: () => createAccountAuthorizationSession,
2224
- createManualTransfer: () => createManualTransfer,
2225
- createTransfer: () => createTransfer,
2226
- fetchAccount: () => fetchAccount,
2227
- fetchAccounts: () => fetchAccounts,
2228
- fetchAuthorizationSession: () => fetchAuthorizationSession,
2229
- fetchAuthorizationSessionByToken: () => fetchAuthorizationSessionByToken,
2230
- fetchChains: () => fetchChains,
2231
- fetchManualTransferSession: () => fetchManualTransferSession,
2232
- fetchManualTransferSources: () => fetchManualTransferSources,
2233
- fetchMerchantPublicKey: () => fetchMerchantPublicKey,
2234
- fetchProviders: () => fetchProviders,
2235
- fetchTransfer: () => fetchTransfer,
2236
- fetchUserConfig: () => fetchUserConfig,
2237
- postTransferQuote: () => postTransferQuote,
2238
- probeActionCompletion: () => probeActionCompletion,
2239
- refreshManualTransferQuote: () => refreshManualTransferQuote,
2240
- regenerateTransferSignPayload: () => regenerateTransferSignPayload,
2241
- registerPasskey: () => registerPasskey,
2242
- reportActionCompletion: () => reportActionCompletion,
2243
- reportPasskeyActivity: () => reportPasskeyActivity,
2244
- setAuthorizationSessionPaymentIntentAmount: () => setAuthorizationSessionPaymentIntentAmount,
2245
- setAuthorizationSessionProvider: () => setAuthorizationSessionProvider,
2246
- signTransfer: () => signTransfer,
2247
- updateManualTransferDepositTargetChain: () => updateManualTransferDepositTargetChain,
2248
- updateUserConfig: () => updateUserConfig,
2249
- updateUserConfigBySession: () => updateUserConfigBySession,
2250
- waitForActionTransactionReceipt: () => waitForActionTransactionReceipt
2251
- });
2252
- var DEBUG_BUFFER_CAPACITY = 200;
2253
- var nextId = 1;
2254
- var entries = [];
2255
- var listeners = /* @__PURE__ */ new Set();
2256
- function notify() {
2257
- for (const listener of listeners) {
2258
- try {
2259
- listener();
2260
- } catch (err) {
2261
- console.error("[blink-sdk][debug-log] listener threw:", err);
2262
- }
2263
- }
2264
- }
2265
- function appendDebug(level, message, data) {
2266
- const entry = {
2267
- id: nextId++,
2268
- ts: Date.now(),
2269
- level,
2270
- message,
2271
- data
2272
- };
2273
- const next = entries.length >= DEBUG_BUFFER_CAPACITY ? entries.slice(entries.length - DEBUG_BUFFER_CAPACITY + 1) : entries.slice();
2274
- next.push(entry);
2275
- entries = next;
2276
- const prefix = "[blink-sdk][debug]";
2277
- const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.info;
2278
- if (data !== void 0) {
2279
- sink(`${prefix} ${message}`, data);
2280
- } else {
2281
- sink(`${prefix} ${message}`);
2282
- }
2283
- notify();
2284
- }
2285
- function subscribeDebug(listener) {
2286
- listeners.add(listener);
2287
- return () => {
2288
- listeners.delete(listener);
2289
- };
2290
- }
2291
- function getDebugEntries() {
2292
- return entries;
2293
- }
2294
- function clearDebugEntries() {
2295
- entries = [];
2296
- notify();
2297
- }
2298
- function useBlinkDebugLog() {
2299
- return useSyncExternalStore(subscribeDebug, getDebugEntries, getDebugEntries);
2300
- }
2301
-
2302
- // src/fetchWithRetry.ts
2303
- var DEFAULT_MAX_RETRIES = 3;
2304
- var DEFAULT_BASE_DELAY_MS = 500;
2305
- var DEFAULT_MAX_JITTER_MS = 200;
2306
- function isNetworkTypeError(err) {
2307
- return err instanceof TypeError && /fetch|network|load failed/i.test(err.message);
2308
- }
2309
- async function fetchWithRetry(input, init, options) {
2310
- const maxRetries = DEFAULT_MAX_RETRIES;
2311
- const baseDelayMs = DEFAULT_BASE_DELAY_MS;
2312
- const maxJitterMs = DEFAULT_MAX_JITTER_MS;
2313
- const label = String(input).replace(/https?:\/\/[^/]+/, "");
2314
- let lastError;
2315
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
2316
- try {
2317
- return await fetch(input, init);
2318
- } catch (err) {
2319
- lastError = err;
2320
- if (!isNetworkTypeError(err)) {
2321
- throw err;
2322
- }
2323
- if (attempt < maxRetries) {
2324
- const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * maxJitterMs;
2325
- appendDebug("warn", `fetchWithRetry: network error, retrying ${label}`, {
2326
- attempt: attempt + 1,
2327
- maxRetries,
2328
- delayMs: Math.round(delay),
2329
- error: err instanceof Error ? err.message : String(err)
2330
- });
2331
- await new Promise((resolve) => setTimeout(resolve, delay));
2332
- }
2333
- }
2334
- }
2335
- throw lastError;
2336
- }
2337
-
2338
- // src/apiError.ts
2339
- var ApiError = class extends Error {
2340
- status;
2341
- code;
2342
- constructor(status, code, message) {
2343
- super(message);
2344
- this.name = "ApiError";
2345
- this.status = status;
2346
- this.code = code;
2347
- }
2348
- };
2349
- function isApiError(err) {
2350
- if (err instanceof ApiError) return true;
2351
- return typeof err === "object" && err !== null && "name" in err && err.name === "ApiError";
2352
- }
2353
- var SVM_SIGN_PAYLOAD_EXPIRED_CODE = "SVM_SIGN_PAYLOAD_EXPIRED";
2354
- function isSvmSignExpiredError(err) {
2355
- return isApiError(err) && err.code === SVM_SIGN_PAYLOAD_EXPIRED_CODE;
2356
- }
2357
-
2358
- // src/api.ts
2359
- async function throwApiError(res) {
2360
- const body = await res.json().catch(() => null);
2361
- const detail = body?.error ?? body;
2362
- const msg = detail?.message ?? res.statusText;
2363
- const code = detail?.code ?? String(res.status);
2364
- throw new ApiError(res.status, code, `${res.status} \u2014 ${code}: ${msg}`);
2365
- }
2366
- async function fetchProviders(apiBaseUrl, token) {
2367
- const headers = {};
2368
- if (token) {
2369
- headers.Authorization = `Bearer ${token}`;
2370
- }
2371
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/providers`, { headers });
2372
- if (!res.ok) await throwApiError(res);
2373
- const data = await res.json();
2374
- return data.items;
2375
- }
2376
- async function fetchChains(apiBaseUrl, token) {
2377
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/chains`, {
2378
- headers: { Authorization: `Bearer ${token}` }
2379
- });
2380
- if (!res.ok) await throwApiError(res);
2381
- const data = await res.json();
2382
- return data.items;
2383
- }
2384
- async function fetchAccounts(apiBaseUrl, token, credentialId) {
2385
- const params = new URLSearchParams({ credentialId });
2386
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts?${params.toString()}`, {
2387
- headers: { Authorization: `Bearer ${token}` }
2388
- });
2389
- if (!res.ok) await throwApiError(res);
2390
- const data = await res.json();
2391
- return data.items;
2392
- }
2393
- async function fetchAccount(apiBaseUrl, token, accountId, credentialId) {
2144
+ async function fetchAccountBalances(apiBaseUrl, token, accountId, credentialId) {
2394
2145
  const params = new URLSearchParams({ credentialId });
2395
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/accounts/${accountId}?${params.toString()}`, {
2396
- headers: { Authorization: `Bearer ${token}` }
2397
- });
2146
+ const res = await fetchWithRetry(
2147
+ `${apiBaseUrl}/v1/accounts/${accountId}/balances?${params.toString()}`,
2148
+ {
2149
+ headers: { Authorization: `Bearer ${token}` }
2150
+ }
2151
+ );
2398
2152
  if (!res.ok) await throwApiError(res);
2399
2153
  return await res.json();
2400
2154
  }
@@ -2670,89 +2424,368 @@ async function createManualTransfer(apiBaseUrl, params) {
2670
2424
  headers: { "Content-Type": "application/json" },
2671
2425
  body: JSON.stringify(params)
2672
2426
  });
2673
- if (!res.ok) await throwApiError(res);
2674
- return await res.json();
2427
+ if (!res.ok) await throwApiError(res);
2428
+ return await res.json();
2429
+ }
2430
+ async function fetchManualTransferSession(apiBaseUrl, sessionId) {
2431
+ const res = await fetchWithRetry(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`);
2432
+ if (!res.ok) await throwApiError(res);
2433
+ return await res.json();
2434
+ }
2435
+ async function updateManualTransferDepositTargetChain(apiBaseUrl, sessionId, selectedChainId) {
2436
+ const res = await fetch(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`, {
2437
+ method: "PATCH",
2438
+ headers: { "Content-Type": "application/json" },
2439
+ body: JSON.stringify({ selectedChainId })
2440
+ });
2441
+ if (!res.ok) await throwApiError(res);
2442
+ return await res.json();
2443
+ }
2444
+ async function refreshManualTransferQuote(apiBaseUrl, sessionId) {
2445
+ const res = await fetch(
2446
+ `${apiBaseUrl}/v1/manual-transfers/${sessionId}/refresh-quote`,
2447
+ { method: "POST" }
2448
+ );
2449
+ if (!res.ok) await throwApiError(res);
2450
+ return await res.json();
2451
+ }
2452
+ async function reportActionCompletion(apiBaseUrl, actionId, result) {
2453
+ const res = await fetchWithRetry(
2454
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2455
+ {
2456
+ method: "PATCH",
2457
+ headers: { "Content-Type": "application/json" },
2458
+ body: JSON.stringify({ status: "COMPLETED", result })
2459
+ }
2460
+ );
2461
+ if (!res.ok) await throwApiError(res);
2462
+ return await res.json();
2463
+ }
2464
+ async function waitForActionTransactionReceipt(apiBaseUrl, actionId, txHash) {
2465
+ const res = await fetch(
2466
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}/transaction-receipt`,
2467
+ {
2468
+ method: "POST",
2469
+ headers: { "Content-Type": "application/json" },
2470
+ body: JSON.stringify({ txHash })
2471
+ }
2472
+ );
2473
+ if (!res.ok) await throwApiError(res);
2474
+ return await res.json();
2475
+ }
2476
+ async function probeActionCompletion(apiBaseUrl, actionId) {
2477
+ const res = await fetchWithRetry(
2478
+ `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2479
+ {
2480
+ method: "PATCH",
2481
+ headers: { "Content-Type": "application/json" },
2482
+ body: JSON.stringify({ status: "COMPLETED", result: {} })
2483
+ }
2484
+ );
2485
+ if (res.ok) {
2486
+ const session = await res.json();
2487
+ return { detected: true, session };
2488
+ }
2489
+ const body = await res.json().catch(() => null);
2490
+ const detail = body?.error ?? body;
2491
+ const code = detail?.code;
2492
+ const message = detail?.message ?? res.statusText ?? `HTTP ${res.status}`;
2493
+ if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
2494
+ return { detected: false, reason: "not-found", status: res.status, code, message };
2495
+ }
2496
+ const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
2497
+ "APPROVE_NOT_DETECTED",
2498
+ "APPROVE_SPL_NOT_DETECTED",
2499
+ "SPL_DELEGATE_MISSING",
2500
+ "SPL_DELEGATE_INSUFFICIENT",
2501
+ "SPL_DELEGATE_WRONG_OWNER"
2502
+ ]);
2503
+ if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
2504
+ return { detected: false, reason: "not-found", status: res.status, code, message };
2505
+ }
2506
+ if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
2507
+ return { detected: false, reason: "invalid-state", status: res.status, code, message };
2508
+ }
2509
+ return { detected: false, reason: "error", status: res.status, code, message };
2510
+ }
2511
+
2512
+ // src/passkey-delegation.ts
2513
+ var PasskeyIframeBlockedError = class extends Error {
2514
+ constructor(message = "Passkey creation is not supported in this browser context.") {
2515
+ super(message);
2516
+ this.name = "PasskeyIframeBlockedError";
2517
+ }
2518
+ };
2519
+ function isInCrossOriginIframe() {
2520
+ if (typeof window === "undefined") return false;
2521
+ if (window.parent === window) return false;
2522
+ try {
2523
+ void window.parent.location.origin;
2524
+ return false;
2525
+ } catch {
2526
+ return true;
2527
+ }
2528
+ }
2529
+ function isSafari() {
2530
+ if (typeof navigator === "undefined") return false;
2531
+ const ua = navigator.userAgent;
2532
+ return /Safari/i.test(ua) && !/Chrome|CriOS|Chromium|Edg|OPR|Firefox/i.test(ua);
2533
+ }
2534
+ var VERIFY_POPUP_TIMEOUT_MS = 6e4;
2535
+ var POPUP_CLOSED_POLL_MS = 500;
2536
+ var POPUP_CLOSED_GRACE_MS = 1e3;
2537
+ function findDevicePasskeyViaPopup(options) {
2538
+ return new Promise((resolve, reject) => {
2539
+ const verificationToken = crypto.randomUUID();
2540
+ const payload = {
2541
+ ...options,
2542
+ verificationToken
2543
+ };
2544
+ const encoded = btoa(JSON.stringify(payload));
2545
+ const popupUrl = `${window.location.origin}/passkey-verify#${encoded}`;
2546
+ const popup = window.open(popupUrl, "blink-passkey-verify");
2547
+ if (!popup) {
2548
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2549
+ return;
2550
+ }
2551
+ let settled = false;
2552
+ const timer = setTimeout(() => {
2553
+ cleanup();
2554
+ resolve(null);
2555
+ }, VERIFY_POPUP_TIMEOUT_MS);
2556
+ const closedPoll = setInterval(() => {
2557
+ if (popup.closed && !settled) {
2558
+ clearInterval(closedPoll);
2559
+ setTimeout(() => {
2560
+ if (!settled) {
2561
+ settled = true;
2562
+ cleanup();
2563
+ checkServerForPasskeyByToken(
2564
+ options.authToken,
2565
+ options.apiBaseUrl,
2566
+ verificationToken
2567
+ ).then((result) => {
2568
+ resolve(result?.credentialId ?? null);
2569
+ }).catch(() => {
2570
+ resolve(null);
2571
+ });
2572
+ }
2573
+ }, POPUP_CLOSED_GRACE_MS);
2574
+ }
2575
+ }, POPUP_CLOSED_POLL_MS);
2576
+ function cleanup() {
2577
+ clearTimeout(timer);
2578
+ clearInterval(closedPoll);
2579
+ }
2580
+ });
2581
+ }
2582
+ async function checkServerForPasskeyByToken(authToken, apiBaseUrl, verificationToken) {
2583
+ if (!authToken || !apiBaseUrl) return null;
2584
+ const res = await fetch(`${apiBaseUrl}/v1/users/config`, {
2585
+ headers: { Authorization: `Bearer ${authToken}` }
2586
+ });
2587
+ if (!res.ok) return null;
2588
+ const body = await res.json();
2589
+ const passkeys = body.config.passkeys ?? [];
2590
+ const matched = passkeys.find((p) => p.lastVerificationToken === verificationToken);
2591
+ return matched ? { credentialId: matched.credentialId, publicKey: matched.publicKey } : null;
2592
+ }
2593
+ function shouldUsePasskeySignupPopup() {
2594
+ return isSafari() && isInCrossOriginIframe();
2595
+ }
2596
+ var SIGNUP_POPUP_TIMEOUT_MS = 12e4;
2597
+ function signupWithPasskeyViaPopup() {
2598
+ return new Promise((resolve, reject) => {
2599
+ const popupUrl = `${window.location.origin}/passkey-signup`;
2600
+ const popup = window.open(popupUrl, "blink-passkey-signup");
2601
+ if (!popup) {
2602
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2603
+ return;
2604
+ }
2605
+ let settled = false;
2606
+ const timer = setTimeout(() => {
2607
+ cleanup();
2608
+ resolve(null);
2609
+ }, SIGNUP_POPUP_TIMEOUT_MS);
2610
+ function onMessage(event) {
2611
+ if (event.origin !== window.location.origin) return;
2612
+ if (event.source !== popup) return;
2613
+ const data = event.data;
2614
+ if (!data || data.type !== "blink:passkey-signup-complete") return;
2615
+ if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2616
+ settled = true;
2617
+ cleanup();
2618
+ resolve({
2619
+ accessToken: data.accessToken,
2620
+ credentialId: data.credentialId,
2621
+ publicKey: data.publicKey
2622
+ });
2623
+ }
2624
+ window.addEventListener("message", onMessage);
2625
+ const closedPoll = setInterval(() => {
2626
+ if (popup.closed && !settled) {
2627
+ settled = true;
2628
+ cleanup();
2629
+ resolve(null);
2630
+ }
2631
+ }, POPUP_CLOSED_POLL_MS);
2632
+ function cleanup() {
2633
+ clearTimeout(timer);
2634
+ clearInterval(closedPoll);
2635
+ window.removeEventListener("message", onMessage);
2636
+ }
2637
+ });
2638
+ }
2639
+ var LOGIN_POPUP_TIMEOUT_MS = 12e4;
2640
+ function loginWithPasskeyViaPopup() {
2641
+ return new Promise((resolve, reject) => {
2642
+ const popupUrl = `${window.location.origin}/passkey-login`;
2643
+ const popup = window.open(popupUrl, "blink-passkey-login");
2644
+ if (!popup) {
2645
+ reject(new Error("Pop-up blocked. Please allow pop-ups for this site and try again."));
2646
+ return;
2647
+ }
2648
+ let settled = false;
2649
+ const timer = setTimeout(() => {
2650
+ cleanup();
2651
+ resolve(null);
2652
+ }, LOGIN_POPUP_TIMEOUT_MS);
2653
+ function onMessage(event) {
2654
+ if (event.origin !== window.location.origin) return;
2655
+ if (event.source !== popup) return;
2656
+ const data = event.data;
2657
+ if (!data || data.type !== "blink:passkey-login-complete") return;
2658
+ if (typeof data.accessToken !== "string" || typeof data.credentialId !== "string" || typeof data.publicKey !== "string") return;
2659
+ settled = true;
2660
+ cleanup();
2661
+ resolve({
2662
+ accessToken: data.accessToken,
2663
+ credentialId: data.credentialId,
2664
+ publicKey: data.publicKey
2665
+ });
2666
+ }
2667
+ window.addEventListener("message", onMessage);
2668
+ const closedPoll = setInterval(() => {
2669
+ if (popup.closed && !settled) {
2670
+ settled = true;
2671
+ cleanup();
2672
+ resolve(null);
2673
+ }
2674
+ }, POPUP_CLOSED_POLL_MS);
2675
+ function cleanup() {
2676
+ clearTimeout(timer);
2677
+ clearInterval(closedPoll);
2678
+ window.removeEventListener("message", onMessage);
2679
+ }
2680
+ });
2675
2681
  }
2676
- async function fetchManualTransferSession(apiBaseUrl, sessionId) {
2677
- const res = await fetchWithRetry(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`);
2678
- if (!res.ok) await throwApiError(res);
2679
- return await res.json();
2682
+
2683
+ // src/credentialIdEncoding.ts
2684
+ function credentialIdBase64ToBytes(value) {
2685
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
2686
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
2687
+ const raw = atob(padded);
2688
+ const bytes = new Uint8Array(raw.length);
2689
+ for (let i = 0; i < raw.length; i++) {
2690
+ bytes[i] = raw.charCodeAt(i);
2691
+ }
2692
+ return bytes;
2680
2693
  }
2681
- async function updateManualTransferDepositTargetChain(apiBaseUrl, sessionId, selectedChainId) {
2682
- const res = await fetch(`${apiBaseUrl}/v1/manual-transfers/${sessionId}`, {
2683
- method: "PATCH",
2684
- headers: { "Content-Type": "application/json" },
2685
- body: JSON.stringify({ selectedChainId })
2686
- });
2687
- if (!res.ok) await throwApiError(res);
2688
- return await res.json();
2694
+
2695
+ // src/passkeyRpId.ts
2696
+ function normalizeConfiguredDomain(value) {
2697
+ return value.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^\./, "").trim();
2689
2698
  }
2690
- async function refreshManualTransferQuote(apiBaseUrl, sessionId) {
2691
- const res = await fetch(
2692
- `${apiBaseUrl}/v1/manual-transfers/${sessionId}/refresh-quote`,
2693
- { method: "POST" }
2694
- );
2695
- if (!res.ok) await throwApiError(res);
2696
- return await res.json();
2699
+ function resolveRootDomainFromHostname(hostname) {
2700
+ const trimmedHostname = hostname.trim().toLowerCase();
2701
+ if (!trimmedHostname) {
2702
+ return "localhost";
2703
+ }
2704
+ if (trimmedHostname === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(trimmedHostname)) {
2705
+ return trimmedHostname;
2706
+ }
2707
+ const parts = trimmedHostname.split(".").filter(Boolean);
2708
+ if (parts.length < 2) {
2709
+ return trimmedHostname;
2710
+ }
2711
+ return parts.slice(-2).join(".");
2697
2712
  }
2698
- async function reportActionCompletion(apiBaseUrl, actionId, result) {
2699
- const res = await fetchWithRetry(
2700
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2701
- {
2702
- method: "PATCH",
2703
- headers: { "Content-Type": "application/json" },
2704
- body: JSON.stringify({ status: "COMPLETED", result })
2713
+
2714
+ // src/hooks/passkeyPublic.ts
2715
+ function waitForDocumentFocus(timeoutMs = 5e3, intervalMs = 100) {
2716
+ return new Promise((resolve) => {
2717
+ if (typeof document === "undefined") {
2718
+ resolve();
2719
+ return;
2705
2720
  }
2706
- );
2707
- if (!res.ok) await throwApiError(res);
2708
- return await res.json();
2709
- }
2710
- async function waitForActionTransactionReceipt(apiBaseUrl, actionId, txHash) {
2711
- const res = await fetch(
2712
- `${apiBaseUrl}/v1/authorization-actions/${actionId}/transaction-receipt`,
2713
- {
2714
- method: "POST",
2715
- headers: { "Content-Type": "application/json" },
2716
- body: JSON.stringify({ txHash })
2721
+ if (document.hasFocus()) {
2722
+ resolve();
2723
+ return;
2717
2724
  }
2718
- );
2719
- if (!res.ok) await throwApiError(res);
2720
- return await res.json();
2725
+ const deadline = Date.now() + timeoutMs;
2726
+ const timer = setInterval(() => {
2727
+ if (document.hasFocus()) {
2728
+ clearInterval(timer);
2729
+ resolve();
2730
+ } else if (Date.now() >= deadline) {
2731
+ clearInterval(timer);
2732
+ resolve();
2733
+ }
2734
+ }, intervalMs);
2735
+ });
2721
2736
  }
2722
- async function probeActionCompletion(apiBaseUrl, actionId) {
2723
- const res = await fetchWithRetry(
2724
- `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
2725
- {
2726
- method: "PATCH",
2727
- headers: { "Content-Type": "application/json" },
2728
- body: JSON.stringify({ status: "COMPLETED", result: {} })
2729
- }
2730
- );
2731
- if (res.ok) {
2732
- const session = await res.json();
2733
- return { detected: true, session };
2737
+ function toBase64(buffer) {
2738
+ return btoa(String.fromCharCode(...new Uint8Array(buffer)));
2739
+ }
2740
+ function readEnvValue(name) {
2741
+ const meta = import.meta;
2742
+ const metaValue = meta.env?.[name];
2743
+ if (typeof metaValue === "string" && metaValue.trim().length > 0) {
2744
+ return metaValue.trim();
2734
2745
  }
2735
- const body = await res.json().catch(() => null);
2736
- const detail = body?.error ?? body;
2737
- const code = detail?.code;
2738
- const message = detail?.message ?? res.statusText ?? `HTTP ${res.status}`;
2739
- if (res.status === 422 && code === "DEPOSIT_TX_NOT_FOUND") {
2740
- return { detected: false, reason: "not-found", status: res.status, code, message };
2746
+ const processValue = globalThis.process?.env?.[name];
2747
+ if (typeof processValue === "string" && processValue.trim().length > 0) {
2748
+ return processValue.trim();
2741
2749
  }
2742
- const approvalNotDetectedCodes = /* @__PURE__ */ new Set([
2743
- "APPROVE_NOT_DETECTED",
2744
- "APPROVE_SPL_NOT_DETECTED",
2745
- "SPL_DELEGATE_MISSING",
2746
- "SPL_DELEGATE_INSUFFICIENT",
2747
- "SPL_DELEGATE_WRONG_OWNER"
2748
- ]);
2749
- if (res.status === 422 && code && approvalNotDetectedCodes.has(code)) {
2750
- return { detected: false, reason: "not-found", status: res.status, code, message };
2750
+ return void 0;
2751
+ }
2752
+ function resolvePasskeyRpId() {
2753
+ const configuredDomain = readEnvValue("VITE_DOMAIN") ?? readEnvValue("BLINK_DOMAIN");
2754
+ if (configuredDomain) {
2755
+ return normalizeConfiguredDomain(configuredDomain);
2751
2756
  }
2752
- if (res.status === 422 && code === "INVALID_TRANSFER_STATE") {
2753
- return { detected: false, reason: "invalid-state", status: res.status, code, message };
2757
+ if (typeof window !== "undefined") {
2758
+ return resolveRootDomainFromHostname(window.location.hostname);
2759
+ }
2760
+ return "localhost";
2761
+ }
2762
+ async function deviceHasPasskey(credentialId) {
2763
+ const found = await findDevicePasskey([credentialId]);
2764
+ return found != null;
2765
+ }
2766
+ async function findDevicePasskey(credentialIds) {
2767
+ if (credentialIds.length === 0) return null;
2768
+ try {
2769
+ const challenge = new Uint8Array(32);
2770
+ crypto.getRandomValues(challenge);
2771
+ await waitForDocumentFocus();
2772
+ const assertion = await navigator.credentials.get({
2773
+ publicKey: {
2774
+ challenge,
2775
+ rpId: resolvePasskeyRpId(),
2776
+ allowCredentials: credentialIds.map((id) => ({
2777
+ type: "public-key",
2778
+ id: credentialIdBase64ToBytes(id)
2779
+ })),
2780
+ userVerification: "discouraged",
2781
+ timeout: 3e4
2782
+ }
2783
+ });
2784
+ if (!assertion) return null;
2785
+ return toBase64(assertion.rawId);
2786
+ } catch {
2787
+ return null;
2754
2788
  }
2755
- return { detected: false, reason: "error", status: res.status, code, message };
2756
2789
  }
2757
2790
 
2758
2791
  // src/transferPolling.ts
@@ -5783,6 +5816,43 @@ function useAuthorizationExecutor(options) {
5783
5816
  }),
5784
5817
  []
5785
5818
  );
5819
+ const [awaitingApproval, setAwaitingApproval] = useState(false);
5820
+ const [approvalDestinationAddress, setApprovalDestinationAddress] = useState(null);
5821
+ const approvalResolverRef = useRef(null);
5822
+ const approvalRejectRef = useRef(null);
5823
+ const approvalInitiatedRef = useRef(false);
5824
+ const waitForApproval = useCallback(
5825
+ (action, destinationAddress) => {
5826
+ if (approvalInitiatedRef.current) return Promise.resolve();
5827
+ return new Promise((resolve, reject) => {
5828
+ approvalResolverRef.current = resolve;
5829
+ approvalRejectRef.current = reject;
5830
+ setCurrentAction(action);
5831
+ setApprovalDestinationAddress(destinationAddress);
5832
+ setAwaitingApproval(true);
5833
+ });
5834
+ },
5835
+ []
5836
+ );
5837
+ const approveAuthorization = useCallback(() => {
5838
+ approvalInitiatedRef.current = true;
5839
+ setAwaitingApproval(false);
5840
+ if (approvalResolverRef.current) {
5841
+ approvalResolverRef.current();
5842
+ approvalResolverRef.current = null;
5843
+ approvalRejectRef.current = null;
5844
+ }
5845
+ }, []);
5846
+ const resetApprovalGate = useCallback(() => {
5847
+ approvalInitiatedRef.current = false;
5848
+ setAwaitingApproval(false);
5849
+ setApprovalDestinationAddress(null);
5850
+ if (approvalRejectRef.current) {
5851
+ approvalRejectRef.current(new AuthorizationSessionCancelledError());
5852
+ approvalRejectRef.current = null;
5853
+ approvalResolverRef.current = null;
5854
+ }
5855
+ }, []);
5786
5856
  const cancelPendingExecution = useCallback(() => {
5787
5857
  if (selectSourceRejectRef.current) {
5788
5858
  selectSourceRejectRef.current(new AuthorizationSessionCancelledError());
@@ -5790,6 +5860,12 @@ function useAuthorizationExecutor(options) {
5790
5860
  selectSourceResolverRef.current = null;
5791
5861
  setPendingSelectSource(null);
5792
5862
  }
5863
+ if (approvalRejectRef.current) {
5864
+ approvalRejectRef.current(new AuthorizationSessionCancelledError());
5865
+ approvalRejectRef.current = null;
5866
+ approvalResolverRef.current = null;
5867
+ }
5868
+ setAwaitingApproval(false);
5793
5869
  setError(null);
5794
5870
  setCurrentAction(null);
5795
5871
  setApproveSplConfirming(null);
@@ -6192,6 +6268,11 @@ function useAuthorizationExecutor(options) {
6192
6268
  currentAction,
6193
6269
  approveSplConfirming,
6194
6270
  pendingSelectSource,
6271
+ awaitingApproval,
6272
+ approvalDestinationAddress,
6273
+ waitForApproval,
6274
+ approveAuthorization,
6275
+ resetApprovalGate,
6195
6276
  resolveSelectSource,
6196
6277
  cancelPendingExecution,
6197
6278
  resetWalletConnect,
@@ -6486,6 +6567,7 @@ function useAuthorizationOrchestrator(deps) {
6486
6567
  const [pendingSelectSourceAction, setPendingSelectSourceAction] = useState(null);
6487
6568
  const [orchestratorCompleted, setOrchestratorCompleted] = useState(false);
6488
6569
  const [sourceSelectionResolved, setSourceSelectionResolved] = useState(false);
6570
+ const [approvalSmartAccountAddress, setApprovalSmartAccountAddress] = useState(null);
6489
6571
  const selectSourceResolverRef = useRef(null);
6490
6572
  const selectSourceRejectRef = useRef(null);
6491
6573
  const submittedApprovePermit2ActionIdsRef = useRef(/* @__PURE__ */ new Set());
@@ -6511,6 +6593,7 @@ function useAuthorizationOrchestrator(deps) {
6511
6593
  const cancellation = new AuthorizationSessionCancelledError();
6512
6594
  setOrchestratorCompleted(false);
6513
6595
  setSourceSelectionResolved(false);
6596
+ setApprovalSmartAccountAddress(null);
6514
6597
  if (selectSourceRejectRef.current) {
6515
6598
  selectSourceRejectRef.current(cancellation);
6516
6599
  selectSourceRejectRef.current = null;
@@ -6533,6 +6616,10 @@ function useAuthorizationOrchestrator(deps) {
6533
6616
  lastRunRef.current = { sessionId, options };
6534
6617
  setOrchestratorCompleted(false);
6535
6618
  setSourceSelectionResolved(false);
6619
+ setApprovalSmartAccountAddress(null);
6620
+ if (!options?.keepApprovalGate) {
6621
+ authExecutor.resetApprovalGate();
6622
+ }
6536
6623
  if (!authExecutor.beginExecution()) {
6537
6624
  appendDebug("warn", "orchestrator:run aborted \u2014 already executing");
6538
6625
  return { status: "cancelled" };
@@ -6561,6 +6648,8 @@ function useAuthorizationOrchestrator(deps) {
6561
6648
  let action = mergedPending[0];
6562
6649
  if (completedIds.has(action.id)) break;
6563
6650
  const ownerSessionId = actionSessionMap.get(action.id) ?? sessionId;
6651
+ const ownerSessionSca = sessions.find((s) => s.id === ownerSessionId)?.session.smartAccountAddress;
6652
+ if (ownerSessionSca) setApprovalSmartAccountAddress(ownerSessionSca);
6564
6653
  console.info("[blink-sdk][orchestrator] Next pending action.", {
6565
6654
  actionId: action.id,
6566
6655
  actionType: action.type,
@@ -6585,6 +6674,7 @@ function useAuthorizationOrchestrator(deps) {
6585
6674
  const autoResult = createSelectSourceResult(action, options.autoResolveSource);
6586
6675
  completedIds.add(action.id);
6587
6676
  authExecutor.addResult(autoResult);
6677
+ setSourceSelectionResolved(true);
6588
6678
  const reportedSession3 = await reportActionCompletionWithLogging(
6589
6679
  apiBaseUrl,
6590
6680
  action,
@@ -6678,6 +6768,13 @@ function useAuthorizationOrchestrator(deps) {
6678
6768
  throw new Error(SIGN_PERMIT2_CONFIRMING_MESSAGE);
6679
6769
  }
6680
6770
  }
6771
+ if (isApprovalGatedAction(action)) {
6772
+ const sessionSca = sessions.find((s) => s.id === ownerSessionId)?.session.smartAccountAddress;
6773
+ await authExecutor.waitForApproval(
6774
+ action,
6775
+ sessionSca ?? readActionMetadataString(action.metadata, "smartAccountAddress") ?? readActionMetadataString(action.metadata, "ownerPubkey")
6776
+ );
6777
+ }
6681
6778
  appendDebug("info", `orchestrator:executeAction start ${action.type}`, {
6682
6779
  actionId: action.id,
6683
6780
  ownerSessionId
@@ -6874,7 +6971,8 @@ function useAuthorizationOrchestrator(deps) {
6874
6971
  authExecutor.setError(null);
6875
6972
  return run(lastRun.sessionId, {
6876
6973
  ...lastRun.options,
6877
- probeBeforePrompt: true
6974
+ probeBeforePrompt: true,
6975
+ keepApprovalGate: true
6878
6976
  });
6879
6977
  }, [authExecutor, run, cancelPendingFlow]);
6880
6978
  return {
@@ -6884,9 +6982,17 @@ function useAuthorizationOrchestrator(deps) {
6884
6982
  resolveSelectSource,
6885
6983
  sourceSelectionResolved,
6886
6984
  orchestratorCompleted,
6985
+ approvalSmartAccountAddress,
6887
6986
  cancelPendingFlow
6888
6987
  };
6889
6988
  }
6989
+ function readActionMetadataString(metadata, key) {
6990
+ const value = metadata?.[key];
6991
+ return typeof value === "string" && value.trim() !== "" ? value : null;
6992
+ }
6993
+ function isApprovalGatedAction(action) {
6994
+ return action.type === "APPROVE_PERMIT2" || action.type === "SIGN_PERMIT2" || action.type === "APPROVE_SPL";
6995
+ }
6890
6996
  function createSelectSourceResult(action, selection) {
6891
6997
  return {
6892
6998
  actionId: action.id,
@@ -7857,6 +7963,26 @@ function deriveSourceTypeAndId(state) {
7857
7963
  }
7858
7964
  return { sourceType: "accountId", sourceId: "" };
7859
7965
  }
7966
+ function hasAnyBalances(accounts) {
7967
+ return accounts.some(
7968
+ (account) => account.wallets.some(
7969
+ (wallet) => wallet.balance !== void 0 || wallet.sources !== void 0
7970
+ )
7971
+ );
7972
+ }
7973
+ function carryOverBalances(prev, next) {
7974
+ const prevWallets = new Map(
7975
+ prev.flatMap((account) => account.wallets.map((wallet) => [wallet.id, wallet]))
7976
+ );
7977
+ return next.map((account) => ({
7978
+ ...account,
7979
+ wallets: account.wallets.map((wallet) => {
7980
+ if (wallet.balance !== void 0 || wallet.sources !== void 0) return wallet;
7981
+ const prior = prevWallets.get(wallet.id);
7982
+ return prior && (prior.balance !== void 0 || prior.sources !== void 0) ? { ...wallet, balance: prior.balance, sources: prior.sources } : wallet;
7983
+ })
7984
+ }));
7985
+ }
7860
7986
  function clearStaleSelection(state) {
7861
7987
  if (state.selectedAccountId == null) return state;
7862
7988
  if (state.desktopWait != null || state.standardDesktopInlineOpenWallet) return state;
@@ -7878,6 +8004,7 @@ function createInitialState(config) {
7878
8004
  accounts: [],
7879
8005
  chains: [],
7880
8006
  loadingData: false,
8007
+ balancesLoading: false,
7881
8008
  depositSelectionRefreshing: false,
7882
8009
  selectedProviderId: null,
7883
8010
  selectedAccountId: null,
@@ -7907,6 +8034,7 @@ function createInitialState(config) {
7907
8034
  privyAuthenticated: false,
7908
8035
  lastResumedAt: 0,
7909
8036
  setupDepositToken: null,
8037
+ setupSpendingLimit: null,
7910
8038
  guestWalletPrepared: null,
7911
8039
  guestWalletDeeplinksPreparing: false,
7912
8040
  amountTooLow: null,
@@ -7926,6 +8054,7 @@ function clearAuthenticatedSessionState(state) {
7926
8054
  accounts: [],
7927
8055
  chains: [],
7928
8056
  loadingData: false,
8057
+ balancesLoading: false,
7929
8058
  depositSelectionRefreshing: false,
7930
8059
  selectedProviderId: null,
7931
8060
  selectedAccountId: null,
@@ -7951,6 +8080,7 @@ function clearAuthenticatedSessionState(state) {
7951
8080
  mobileTokenAuthorizationPending: false,
7952
8081
  linkSettling: false,
7953
8082
  setupDepositToken: null,
8083
+ setupSpendingLimit: null,
7954
8084
  guestWalletPrepared: null,
7955
8085
  guestWalletDeeplinksPreparing: false,
7956
8086
  amountTooLow: null,
@@ -8019,6 +8149,8 @@ function applyAction(state, action) {
8019
8149
  providers: action.providers,
8020
8150
  accounts: action.accounts,
8021
8151
  chains: action.chains,
8152
+ // Accounts arrive balance-free; balances follow via BALANCES_LOADED.
8153
+ balancesLoading: true,
8022
8154
  depositSelectionRefreshing: false,
8023
8155
  initialDataLoaded: true
8024
8156
  };
@@ -8042,10 +8174,12 @@ function applyAction(state, action) {
8042
8174
  depositSelectionRefreshing: false
8043
8175
  };
8044
8176
  case "ACCOUNTS_RELOADED": {
8177
+ const hadBalances = hasAnyBalances(state.accounts);
8045
8178
  const next = {
8046
8179
  ...state,
8047
- accounts: action.accounts,
8180
+ accounts: carryOverBalances(state.accounts, action.accounts),
8048
8181
  providers: action.providers,
8182
+ balancesLoading: !hadBalances,
8049
8183
  initialDataLoaded: true
8050
8184
  };
8051
8185
  if (action.defaults) {
@@ -8057,6 +8191,24 @@ function applyAction(state, action) {
8057
8191
  }
8058
8192
  return clearStaleSelection(next);
8059
8193
  }
8194
+ case "BALANCES_LOADED": {
8195
+ const { balancesByAccountId } = action;
8196
+ const accounts = state.accounts.map((account) => {
8197
+ const accountBalances = balancesByAccountId[account.id];
8198
+ if (!accountBalances) return account;
8199
+ const balancesByWalletId = new Map(
8200
+ accountBalances.wallets.map((w) => [w.id, w])
8201
+ );
8202
+ return {
8203
+ ...account,
8204
+ wallets: account.wallets.map((wallet) => {
8205
+ const merged = balancesByWalletId.get(wallet.id);
8206
+ return merged ? { ...wallet, balance: merged.balance, sources: merged.sources } : wallet;
8207
+ })
8208
+ };
8209
+ });
8210
+ return { ...state, accounts, balancesLoading: false };
8211
+ }
8060
8212
  case "SET_DEPOSIT_SELECTION_REFRESHING":
8061
8213
  return { ...state, depositSelectionRefreshing: action.value };
8062
8214
  case "SAVE_SELECTION":
@@ -8128,6 +8280,7 @@ function applyAction(state, action) {
8128
8280
  mobileFlow: false,
8129
8281
  mobileTokenAuthorizationPending: false,
8130
8282
  setupDepositToken: null,
8283
+ setupSpendingLimit: null,
8131
8284
  setupFlowScreen: null
8132
8285
  };
8133
8286
  case "PAY_ENDED":
@@ -8373,6 +8526,7 @@ function applyAction(state, action) {
8373
8526
  linkSettling: false,
8374
8527
  savedSelection: null,
8375
8528
  setupDepositToken: null,
8529
+ setupSpendingLimit: null,
8376
8530
  setupFlowScreen: null,
8377
8531
  guestWalletPrepared: null,
8378
8532
  guestWalletDeeplinksPreparing: false,
@@ -8438,8 +8592,10 @@ function applyAction(state, action) {
8438
8592
  ...action.chainId != null ? { chainId: action.chainId } : {}
8439
8593
  }
8440
8594
  };
8595
+ case "SET_SETUP_SPENDING_LIMIT":
8596
+ return { ...state, setupSpendingLimit: action.limit };
8441
8597
  case "CLEAR_SETUP_DEPOSIT_TOKEN":
8442
- return { ...state, setupDepositToken: null };
8598
+ return { ...state, setupDepositToken: null, setupSpendingLimit: null };
8443
8599
  default:
8444
8600
  return state;
8445
8601
  }
@@ -9225,9 +9381,12 @@ function PrimaryButton({
9225
9381
  }
9226
9382
  );
9227
9383
  }
9384
+ var BUTTON_MIN_HEIGHT = 60;
9228
9385
  var progressButtonStyle = (tokens, paused) => ({
9229
9386
  position: "relative",
9230
9387
  width: "100%",
9388
+ minHeight: BUTTON_MIN_HEIGHT,
9389
+ boxSizing: "border-box",
9231
9390
  padding: "18px 24px",
9232
9391
  background: `linear-gradient(180deg, ${tokens.accent}88, ${tokens.accentHover}88)`,
9233
9392
  color: tokens.accentText,
@@ -9264,6 +9423,8 @@ var buttonStyle2 = (tokens, state) => {
9264
9423
  const gradient = `linear-gradient(180deg, ${tokens.accent}, ${tokens.accentHover})`;
9265
9424
  return {
9266
9425
  width: "100%",
9426
+ minHeight: BUTTON_MIN_HEIGHT,
9427
+ boxSizing: "border-box",
9267
9428
  padding: "18px 24px",
9268
9429
  // Layer a faint white film over the gradient on hover rather than using
9269
9430
  // `filter: brightness()` — the accent gradient is near-black in the new
@@ -10190,6 +10351,8 @@ function SourcePill({
10190
10351
  logo,
10191
10352
  name,
10192
10353
  balance,
10354
+ nameSlot,
10355
+ balanceSlot,
10193
10356
  expanded,
10194
10357
  showChevron = true,
10195
10358
  icon
@@ -10197,8 +10360,8 @@ function SourcePill({
10197
10360
  const { tokens } = useBlinkConfig();
10198
10361
  return /* @__PURE__ */ jsxs("span", { style: pillStyle2(tokens.bgCardTranslucent), children: [
10199
10362
  icon ?? (logo ? /* @__PURE__ */ jsx("img", { src: logo, alt: "", "aria-hidden": "true", style: logoStyle }) : name ? /* @__PURE__ */ jsx("span", { style: logoFallbackStyle(tokens.bgInput, tokens.textMuted), children: name.charAt(0).toUpperCase() }) : null),
10200
- name && /* @__PURE__ */ jsx("span", { style: labelStyle2(tokens.text), children: name }),
10201
- balance && /* @__PURE__ */ jsx("span", { style: balanceStyle(tokens.text), children: balance }),
10363
+ nameSlot ?? (name && /* @__PURE__ */ jsx("span", { style: labelStyle2(tokens.text), children: name })),
10364
+ balanceSlot ?? (balance && /* @__PURE__ */ jsx("span", { style: balanceStyle(tokens.text), children: balance })),
10202
10365
  showChevron && /* @__PURE__ */ jsx("svg", { width: "17", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: expanded ? /* @__PURE__ */ jsx(
10203
10366
  "path",
10204
10367
  {
@@ -10261,6 +10424,37 @@ var balanceStyle = (color) => ({
10261
10424
  color,
10262
10425
  whiteSpace: "nowrap"
10263
10426
  });
10427
+ var SHIMMER_KEYFRAMES = `
10428
+ @keyframes blink-shimmer-sweep {
10429
+ 0% { background-position: 200% 0; }
10430
+ 100% { background-position: -200% 0; }
10431
+ }`;
10432
+ function Shimmer({
10433
+ width,
10434
+ height,
10435
+ borderRadius = 999,
10436
+ baseColor,
10437
+ highlightColor,
10438
+ style
10439
+ }) {
10440
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", style: { display: "inline-block", ...style }, children: [
10441
+ /* @__PURE__ */ jsx("style", { children: SHIMMER_KEYFRAMES }),
10442
+ /* @__PURE__ */ jsx(
10443
+ "span",
10444
+ {
10445
+ style: {
10446
+ display: "block",
10447
+ width,
10448
+ height,
10449
+ borderRadius,
10450
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
10451
+ backgroundSize: "200% 100%",
10452
+ animation: "blink-shimmer-sweep 1.4s ease-in-out infinite"
10453
+ }
10454
+ }
10455
+ )
10456
+ ] });
10457
+ }
10264
10458
  function formatUsdTwoDecimals(value) {
10265
10459
  const n = Number(value);
10266
10460
  return (Number.isFinite(n) ? n : 0).toLocaleString("en-US", {
@@ -10961,6 +11155,14 @@ function LockIcon({ size = 24 }) {
10961
11155
  /* @__PURE__ */ jsx("path", { d: "M8 11V8a4 4 0 1 1 8 0v3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" })
10962
11156
  ] });
10963
11157
  }
11158
+ function KeyIcon({ size = 24 }) {
11159
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
11160
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "12", r: "3.6", stroke: "currentColor", strokeWidth: "2" }),
11161
+ /* @__PURE__ */ jsx("path", { d: "M10.6 12H20", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
11162
+ /* @__PURE__ */ jsx("path", { d: "M17 12v3.2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
11163
+ /* @__PURE__ */ jsx("path", { d: "M20 12v3.2", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
11164
+ ] });
11165
+ }
10964
11166
  function FaceIdIcon({ size = 24 }) {
10965
11167
  return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
10966
11168
  /* @__PURE__ */ jsx("path", { d: "M4 9V6a2 2 0 0 1 2-2h3", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }),
@@ -10974,6 +11176,21 @@ function FaceIdIcon({ size = 24 }) {
10974
11176
  /* @__PURE__ */ 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" })
10975
11177
  ] });
10976
11178
  }
11179
+ function PencilIcon({ size = 18 } = {}) {
11180
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
11181
+ /* @__PURE__ */ jsx(
11182
+ "path",
11183
+ {
11184
+ 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",
11185
+ stroke: "currentColor",
11186
+ strokeWidth: "1.7",
11187
+ strokeLinecap: "round",
11188
+ strokeLinejoin: "round"
11189
+ }
11190
+ ),
11191
+ /* @__PURE__ */ jsx("path", { d: "M13.5 6.5l4 4", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round" })
11192
+ ] });
11193
+ }
10977
11194
  function WalletIcon() {
10978
11195
  return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
10979
11196
  /* @__PURE__ */ jsx(
@@ -11345,7 +11562,7 @@ function CopyAddressButton({
11345
11562
  }) {
11346
11563
  const { tokens } = useBlinkConfig();
11347
11564
  const [hovered, setHovered] = useState(false);
11348
- const short = address.length > 18 ? `${address.slice(0, 8)}...${address.slice(-6)}` : address;
11565
+ const short = truncateMiddle(address);
11349
11566
  return /* @__PURE__ */ jsxs(
11350
11567
  "button",
11351
11568
  {
@@ -13503,7 +13720,6 @@ var errorBannerStyle6 = (tokens) => ({
13503
13720
  textAlign: "left",
13504
13721
  boxSizing: "border-box"
13505
13722
  });
13506
- var SHIMMER_ROWS = 3;
13507
13723
  function LinkTokensScreen({
13508
13724
  entries: entries2,
13509
13725
  selectedIndex,
@@ -13516,44 +13732,75 @@ function LinkTokensScreen({
13516
13732
  approving = false
13517
13733
  }) {
13518
13734
  const { tokens: t } = useBlinkConfig();
13735
+ const [view, setView] = useState("summary");
13736
+ const [pendingIndex, setPendingIndex] = useState(selectedIndex);
13737
+ const [limit, setLimit] = useState("unlimited");
13738
+ const [editing, setEditing] = useState(false);
13519
13739
  const showShimmer = loading && entries2.length === 0;
13520
13740
  const showEmpty = !loading && entries2.length === 0;
13521
13741
  const selected = entries2[selectedIndex];
13742
+ const limitUsd = limit === "unlimited" ? null : parseAmount(limit);
13743
+ const isUnlimited = limitUsd == null;
13744
+ const limitLabel = isUnlimited ? "Unlimited" : `$${limit}`;
13745
+ const currentSelection = () => limitUsd == null ? { unlimited: true } : { usd: limitUsd };
13522
13746
  const approveDisabled = loading || approving || entries2.length === 0 || selectedIndex < 0 || selectedIndex >= entries2.length || !!selected?.notSupported;
13523
- const ctaLabel = selected ? `Authorize ${selected.tokenSymbol} on ${shortChainName(selected.chainName)}` : "Approve";
13747
+ function openSelectToken() {
13748
+ setPendingIndex(selectedIndex);
13749
+ setView("selectToken");
13750
+ }
13751
+ function commitSelectToken() {
13752
+ const row = entries2[pendingIndex];
13753
+ if (row && !row.notSupported) onSelect(pendingIndex);
13754
+ setView("summary");
13755
+ }
13756
+ if (view === "selectToken") {
13757
+ const pending = entries2[pendingIndex];
13758
+ const continueDisabled = !pending || !!pending.notSupported || entries2.length === 0;
13759
+ return /* @__PURE__ */ jsxs(
13760
+ ScreenLayout,
13761
+ {
13762
+ scrollableBody: false,
13763
+ footer: /* @__PURE__ */ jsx(PrimaryButton, { onClick: commitSelectToken, disabled: continueDisabled, children: "Continue" }),
13764
+ children: [
13765
+ /* @__PURE__ */ jsx(ListScrollbarStyles, { textTertiary: t.textTertiary }),
13766
+ /* @__PURE__ */ jsx(
13767
+ ScreenHeader,
13768
+ {
13769
+ onBack: () => setView("summary"),
13770
+ center: /* @__PURE__ */ jsx("img", { src: BLINK_WORDMARK, alt: "Blink", style: wordmarkImgStyle3 })
13771
+ }
13772
+ ),
13773
+ /* @__PURE__ */ jsxs("div", { style: contentStyle10, children: [
13774
+ /* @__PURE__ */ jsx("h2", { style: headingStyle7(t.text, t.fontWeightBold), children: "Select token" }),
13775
+ /* @__PURE__ */ jsxs("div", { className: "blink-link-tokens-list", style: listCardStyle(t.bgRecessed), children: [
13776
+ entries2.map((entry, i) => /* @__PURE__ */ jsx(
13777
+ TokenSourceRow,
13778
+ {
13779
+ symbol: entry.tokenSymbol,
13780
+ chainName: entry.chainName,
13781
+ tokenLogoUri: entry.tokenLogoUri,
13782
+ balance: entry.balanceLabel == null ? entry.balanceUsd : void 0,
13783
+ balanceLabel: entry.balanceLabel,
13784
+ selected: i === pendingIndex,
13785
+ notSupported: entry.notSupported,
13786
+ onClick: () => setPendingIndex(i)
13787
+ },
13788
+ `${entry.tokenSymbol}-${entry.chainName}-${i}`
13789
+ )),
13790
+ showEmpty && /* @__PURE__ */ jsx("div", { style: emptyStyle(t.textMuted), children: "No tokens available to link. Switch wallets or fund this account to continue." })
13791
+ ] })
13792
+ ] })
13793
+ ]
13794
+ }
13795
+ );
13796
+ }
13524
13797
  return /* @__PURE__ */ jsxs(
13525
13798
  ScreenLayout,
13526
13799
  {
13527
13800
  scrollableBody: false,
13528
- footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle4, children: [
13529
- /* @__PURE__ */ jsxs("div", { style: lockBannerStyle2, children: [
13530
- /* @__PURE__ */ jsx("span", { style: lockIconWrapStyle2(t.text), children: /* @__PURE__ */ jsx(LockIcon3, {}) }),
13531
- /* @__PURE__ */ jsx("p", { style: lockBannerTextStyle2(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
13532
- ] }),
13533
- /* @__PURE__ */ jsx(PrimaryButton, { onClick: onApprove, disabled: approveDisabled, loading: approving, children: ctaLabel })
13534
- ] }),
13801
+ footer: /* @__PURE__ */ jsx(PrimaryButton, { onClick: () => onApprove(currentSelection()), disabled: approveDisabled, loading: approving, children: "Review" }),
13535
13802
  children: [
13536
- /* @__PURE__ */ jsx("style", { children: `
13537
- @keyframes blink-link-tokens-shimmer {
13538
- 0% { background-position: 200% 0; }
13539
- 100% { background-position: -200% 0; }
13540
- }
13541
- .blink-link-tokens-list {
13542
- scrollbar-width: thin;
13543
- scrollbar-color: ${t.textTertiary} transparent;
13544
- }
13545
- .blink-link-tokens-list::-webkit-scrollbar {
13546
- width: 4px;
13547
- }
13548
- .blink-link-tokens-list::-webkit-scrollbar-track {
13549
- background: transparent;
13550
- margin: 8px 0;
13551
- }
13552
- .blink-link-tokens-list::-webkit-scrollbar-thumb {
13553
- background: ${t.textTertiary};
13554
- border-radius: 999px;
13555
- }
13556
- ` }),
13803
+ /* @__PURE__ */ jsx(ListScrollbarStyles, { textTertiary: t.textTertiary }),
13557
13804
  /* @__PURE__ */ jsx(
13558
13805
  ScreenHeader,
13559
13806
  {
@@ -13563,71 +13810,198 @@ function LinkTokensScreen({
13563
13810
  }
13564
13811
  ),
13565
13812
  /* @__PURE__ */ jsxs("div", { style: contentStyle10, children: [
13566
- /* @__PURE__ */ jsx("h2", { style: headingStyle7(t.text), children: "Pick your stablecoin for passkey deposits" }),
13567
- /* @__PURE__ */ jsxs("div", { className: "blink-link-tokens-list", style: listCardStyle(t.bgRecessed), children: [
13568
- showShimmer ? Array.from({ length: SHIMMER_ROWS }).map((_, i) => /* @__PURE__ */ jsxs(
13569
- "div",
13570
- {
13571
- "data-testid": "link-tokens-shimmer-row",
13572
- "aria-hidden": "true",
13573
- style: shimmerRowStyle,
13574
- children: [
13575
- /* @__PURE__ */ jsx("div", { style: shimmerCircleStyle(t.bgInput, t.border) }),
13576
- /* @__PURE__ */ jsxs("div", { style: shimmerInfoStyle, children: [
13577
- /* @__PURE__ */ jsx("div", { style: shimmerLineStyle(72, t.bgInput, t.border) }),
13578
- /* @__PURE__ */ jsx("div", { style: shimmerLineStyle(48, t.bgInput, t.border) })
13579
- ] }),
13813
+ /* @__PURE__ */ jsx("div", { style: flexSpacerStyle }),
13814
+ /* @__PURE__ */ jsxs("div", { style: topGroupStyle, children: [
13815
+ /* @__PURE__ */ jsx("h2", { style: headingStyle7(t.text, t.fontWeightBold), children: "Let your passkey spend" }),
13816
+ /* @__PURE__ */ jsxs("div", { style: pillsGroupStyle, children: [
13817
+ showShimmer ? /* @__PURE__ */ jsxs("div", { "data-testid": "link-tokens-summary-shimmer", "aria-hidden": "true", style: pillStyle3(t.bgRecessed), children: [
13818
+ /* @__PURE__ */ jsx("div", { style: shimmerCircleStyle(t.bgInput, t.border) }),
13819
+ /* @__PURE__ */ jsxs("div", { style: shimmerInfoStyle, children: [
13820
+ /* @__PURE__ */ jsx("div", { style: shimmerLineStyle(96, t.bgInput, t.border) }),
13580
13821
  /* @__PURE__ */ jsx("div", { style: shimmerLineStyle(56, t.bgInput, t.border) })
13581
- ]
13582
- },
13583
- `shimmer-${i}`
13584
- )) : entries2.map((entry, i) => /* @__PURE__ */ jsx(
13585
- TokenSourceRow,
13586
- {
13587
- symbol: entry.tokenSymbol,
13588
- chainName: entry.chainName,
13589
- tokenLogoUri: entry.tokenLogoUri,
13590
- balance: entry.balanceLabel == null ? entry.balanceUsd : void 0,
13591
- balanceLabel: entry.balanceLabel,
13592
- selected: i === selectedIndex,
13593
- notSupported: entry.notSupported,
13594
- onClick: () => onSelect(i)
13595
- },
13596
- `${entry.tokenSymbol}-${entry.chainName}-${i}`
13597
- )),
13598
- showEmpty && /* @__PURE__ */ jsx("div", { style: emptyStyle(t.textMuted), children: "No tokens available to link. Switch wallets or fund this account to continue." })
13822
+ ] })
13823
+ ] }) : selected ? /* @__PURE__ */ jsxs(
13824
+ "button",
13825
+ {
13826
+ type: "button",
13827
+ onClick: openSelectToken,
13828
+ style: pillButtonStyle(t.bgRecessed),
13829
+ "aria-label": `Selected token ${selected.tokenSymbol} on ${selected.chainName}. Change token.`,
13830
+ "data-testid": "link-tokens-selected-pill",
13831
+ children: [
13832
+ /* @__PURE__ */ jsx(TokenPillLogo, { entry: selected, fallbackBg: t.bgRecessed, fallbackColor: t.textMuted }),
13833
+ /* @__PURE__ */ jsxs("span", { style: pillTextColStyle, children: [
13834
+ /* @__PURE__ */ jsxs("span", { style: pillTitleStyle(t.text, t.fontWeightRegular), children: [
13835
+ selected.tokenSymbol,
13836
+ " on ",
13837
+ selected.chainName
13838
+ ] }),
13839
+ /* @__PURE__ */ jsx("span", { style: pillSubStyle(t.textMuted, t.fontWeightRegular), children: selected.balanceLabel ?? `$${formatUsdTwoDecimals2(selected.balanceUsd)}` })
13840
+ ] }),
13841
+ /* @__PURE__ */ jsx("span", { style: pillChevronStyle(t.textMuted), children: /* @__PURE__ */ jsx(ChevronDownIcon, { color: t.textMuted }) })
13842
+ ]
13843
+ }
13844
+ ) : null,
13845
+ editing ? /* @__PURE__ */ jsxs("div", { style: pillStyle3(t.bgRecessed), children: [
13846
+ /* @__PURE__ */ jsx("span", { style: limitEditLabelStyle(t.text, t.fontWeightRegular), children: "Spending limit" }),
13847
+ /* @__PURE__ */ jsxs("div", { style: limitEditRightStyle, children: [
13848
+ /* @__PURE__ */ jsxs("span", { style: limitAmountStyle(t.text, t.fontWeightRegular), children: [
13849
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: limitDollarStyle(isUnlimited, t.text), children: "$" }),
13850
+ /* @__PURE__ */ jsx(
13851
+ "input",
13852
+ {
13853
+ type: "text",
13854
+ inputMode: "decimal",
13855
+ autoFocus: true,
13856
+ placeholder: "0",
13857
+ "aria-label": "Lifetime spending limit",
13858
+ value: limit === "unlimited" ? "" : limit,
13859
+ onChange: (e) => setLimit(sanitizeRawInput(e.target.value)),
13860
+ onBlur: () => setEditing(false),
13861
+ "data-testid": "link-tokens-limit-input",
13862
+ style: limitInputStyle(t.text, t.textMuted)
13863
+ }
13864
+ )
13865
+ ] }),
13866
+ /* @__PURE__ */ jsx(
13867
+ "button",
13868
+ {
13869
+ type: "button",
13870
+ onMouseDown: (e) => e.preventDefault(),
13871
+ onClick: () => {
13872
+ setLimit("unlimited");
13873
+ setEditing(false);
13874
+ },
13875
+ style: limitResetStyle(t.border, t.textMuted, t.fontWeightMedium),
13876
+ "data-testid": "link-tokens-limit-reset",
13877
+ children: "Unlimited"
13878
+ }
13879
+ )
13880
+ ] })
13881
+ ] }) : /* @__PURE__ */ jsxs(
13882
+ "button",
13883
+ {
13884
+ type: "button",
13885
+ onClick: () => setEditing(true),
13886
+ style: pillButtonStyle(t.bgRecessed),
13887
+ "aria-label": `Lifetime spending limit: ${limitLabel}. Edit.`,
13888
+ "data-testid": "link-tokens-limit-pill",
13889
+ children: [
13890
+ /* @__PURE__ */ jsx("span", { style: pillTitleStyle(t.text, t.fontWeightRegular), children: "Lifetime spending limit" }),
13891
+ /* @__PURE__ */ jsxs("span", { style: limitValueStyle, children: [
13892
+ /* @__PURE__ */ jsx("span", { style: pillSubStyle(isUnlimited ? t.textMuted : t.text, t.fontWeightRegular), children: limitLabel }),
13893
+ /* @__PURE__ */ jsx("span", { style: pillChevronStyle(t.textMuted), children: /* @__PURE__ */ jsx(PencilIcon, { size: 16 }) })
13894
+ ] })
13895
+ ]
13896
+ }
13897
+ )
13898
+ ] })
13599
13899
  ] }),
13600
- error && /* @__PURE__ */ jsx("div", { style: bannerSlotStyle, children: /* @__PURE__ */ jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
13900
+ /* @__PURE__ */ jsx("div", { style: flexSpacerStyle }),
13901
+ /* @__PURE__ */ jsxs("div", { style: bottomGroupStyle, children: [
13902
+ /* @__PURE__ */ jsxs("div", { style: infoCardStyle3(t.bgRecessed), children: [
13903
+ /* @__PURE__ */ jsx(
13904
+ InfoRow,
13905
+ {
13906
+ icon: /* @__PURE__ */ jsx(LockIcon, { size: 22 }),
13907
+ title: "Blink is non-custodial",
13908
+ body: "Your passkey moves money, not Blink.",
13909
+ t
13910
+ }
13911
+ ),
13912
+ /* @__PURE__ */ jsx(
13913
+ InfoRow,
13914
+ {
13915
+ icon: /* @__PURE__ */ jsx(FaceIdIcon, { size: 22 }),
13916
+ title: "Every transaction is signed",
13917
+ body: "Nothing can move without your direct approval.",
13918
+ t
13919
+ }
13920
+ )
13921
+ ] }),
13922
+ error && /* @__PURE__ */ jsx("div", { style: bannerSlotStyle, children: /* @__PURE__ */ jsx(NotificationBanner, { title: "Something went wrong", description: error }) })
13923
+ ] })
13601
13924
  ] })
13602
13925
  ]
13603
13926
  }
13604
13927
  );
13605
13928
  }
13606
- function shortChainName(chainName) {
13607
- return chainName.replace(/\s+One$/i, "");
13929
+ function TokenPillLogo({
13930
+ entry,
13931
+ fallbackBg,
13932
+ fallbackColor
13933
+ }) {
13934
+ const logo = entry.tokenLogoUri ?? TOKEN_LOGOS[entry.tokenSymbol];
13935
+ return /* @__PURE__ */ jsx("span", { style: pillLogoSlotStyle, children: logo ? /* @__PURE__ */ jsx("img", { src: logo, alt: "", "aria-hidden": "true", style: pillLogoImgStyle }) : /* @__PURE__ */ jsx("span", { style: pillLogoFallbackStyle(fallbackBg, fallbackColor), children: entry.tokenSymbol.charAt(0).toUpperCase() }) });
13608
13936
  }
13609
- function LockIcon3() {
13610
- return /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
13611
- "path",
13612
- {
13613
- 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",
13614
- stroke: "currentColor",
13615
- strokeWidth: "2",
13616
- strokeLinecap: "round",
13617
- strokeLinejoin: "round"
13618
- }
13619
- ) });
13937
+ function InfoRow({
13938
+ icon,
13939
+ title,
13940
+ body,
13941
+ t
13942
+ }) {
13943
+ return /* @__PURE__ */ jsxs("div", { style: infoRowStyle2, children: [
13944
+ /* @__PURE__ */ jsx("span", { style: infoIconStyle(t.text), children: icon }),
13945
+ /* @__PURE__ */ jsxs("span", { style: infoTextColStyle, children: [
13946
+ /* @__PURE__ */ jsx("span", { style: infoTitleStyle2(t.text, t.fontWeightSemibold), children: title }),
13947
+ /* @__PURE__ */ jsx("span", { style: infoBodyStyle(t.textMuted, t.fontWeightMedium), children: body })
13948
+ ] })
13949
+ ] });
13950
+ }
13951
+ function ListScrollbarStyles({ textTertiary }) {
13952
+ return /* @__PURE__ */ jsx("style", { children: `
13953
+ @keyframes blink-link-tokens-shimmer {
13954
+ 0% { background-position: 200% 0; }
13955
+ 100% { background-position: -200% 0; }
13956
+ }
13957
+ .blink-link-tokens-list {
13958
+ scrollbar-width: thin;
13959
+ scrollbar-color: ${textTertiary} transparent;
13960
+ }
13961
+ .blink-link-tokens-list::-webkit-scrollbar { width: 4px; }
13962
+ .blink-link-tokens-list::-webkit-scrollbar-track {
13963
+ background: transparent;
13964
+ margin: 8px 0;
13965
+ }
13966
+ .blink-link-tokens-list::-webkit-scrollbar-thumb {
13967
+ background: ${textTertiary};
13968
+ border-radius: 999px;
13969
+ }
13970
+ input[data-testid="link-tokens-limit-input"]::placeholder {
13971
+ color: var(--link-limit-placeholder);
13972
+ opacity: 1;
13973
+ }
13974
+ ` });
13620
13975
  }
13621
13976
  var contentStyle10 = {
13622
13977
  display: "flex",
13623
13978
  flexDirection: "column",
13624
- alignItems: "center",
13625
- gap: 16,
13626
- paddingTop: 8,
13627
13979
  flex: 1,
13628
13980
  minHeight: 0,
13629
13981
  width: "100%"
13630
13982
  };
13983
+ var flexSpacerStyle = {
13984
+ flex: 1,
13985
+ minHeight: 16
13986
+ };
13987
+ var topGroupStyle = {
13988
+ display: "flex",
13989
+ flexDirection: "column",
13990
+ gap: 24,
13991
+ width: "100%"
13992
+ };
13993
+ var bottomGroupStyle = {
13994
+ display: "flex",
13995
+ flexDirection: "column",
13996
+ gap: 12,
13997
+ width: "100%"
13998
+ };
13999
+ var pillsGroupStyle = {
14000
+ display: "flex",
14001
+ flexDirection: "column",
14002
+ gap: 8,
14003
+ width: "100%"
14004
+ };
13631
14005
  var wordmarkImgStyle3 = {
13632
14006
  height: 22,
13633
14007
  width: "auto",
@@ -13635,9 +14009,9 @@ var wordmarkImgStyle3 = {
13635
14009
  pointerEvents: "none",
13636
14010
  userSelect: "none"
13637
14011
  };
13638
- var headingStyle7 = (color) => ({
14012
+ var headingStyle7 = (color, weight) => ({
13639
14013
  fontSize: 24,
13640
- fontWeight: 700,
14014
+ fontWeight: weight,
13641
14015
  lineHeight: "normal",
13642
14016
  letterSpacing: 0,
13643
14017
  color,
@@ -13657,68 +14031,155 @@ var listCardStyle = (bg) => ({
13657
14031
  minHeight: 0,
13658
14032
  overflowY: "auto"
13659
14033
  });
13660
- var shimmerRowStyle = {
14034
+ var pillStyle3 = (bg) => ({
13661
14035
  display: "flex",
13662
14036
  alignItems: "center",
13663
14037
  gap: 16,
13664
- padding: "12px 16px 12px 12px",
13665
- background: "transparent",
13666
- border: "none",
13667
- borderRadius: 16,
13668
14038
  width: "100%",
13669
- boxSizing: "border-box"
14039
+ boxSizing: "border-box",
14040
+ padding: "14px 16px",
14041
+ borderRadius: 20,
14042
+ background: bg
14043
+ });
14044
+ var pillButtonStyle = (bg) => ({
14045
+ ...pillStyle3(bg),
14046
+ border: "none",
14047
+ cursor: "pointer",
14048
+ fontFamily: "inherit",
14049
+ textAlign: "left"
14050
+ });
14051
+ var pillTextColStyle = {
14052
+ display: "flex",
14053
+ flexDirection: "column",
14054
+ flex: 1,
14055
+ minWidth: 0,
14056
+ gap: 2
13670
14057
  };
13671
- var shimmerCircleStyle = (baseColor, highlightColor) => ({
14058
+ var pillTitleStyle = (color, weight) => ({
14059
+ fontSize: "1rem",
14060
+ fontWeight: weight,
14061
+ lineHeight: "normal",
14062
+ color,
14063
+ whiteSpace: "nowrap",
14064
+ overflow: "hidden",
14065
+ textOverflow: "ellipsis"
14066
+ });
14067
+ var pillSubStyle = (color, weight) => ({
14068
+ fontSize: "0.95rem",
14069
+ fontWeight: weight,
14070
+ color
14071
+ });
14072
+ var pillChevronStyle = (color) => ({
14073
+ flexShrink: 0,
14074
+ display: "inline-flex",
14075
+ alignItems: "center",
14076
+ color
14077
+ });
14078
+ var limitValueStyle = {
14079
+ marginLeft: "auto",
14080
+ display: "inline-flex",
14081
+ alignItems: "center",
14082
+ gap: 8
14083
+ };
14084
+ var pillLogoSlotStyle = {
14085
+ position: "relative",
13672
14086
  width: 40,
13673
14087
  height: 40,
13674
- borderRadius: "50%",
13675
14088
  flexShrink: 0,
13676
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13677
- backgroundSize: "200% 100%",
13678
- animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14089
+ display: "inline-flex",
14090
+ alignItems: "center",
14091
+ justifyContent: "center"
14092
+ };
14093
+ var pillLogoImgStyle = {
14094
+ width: "100%",
14095
+ height: "100%",
14096
+ borderRadius: "50%",
14097
+ objectFit: "cover"
14098
+ };
14099
+ var pillLogoFallbackStyle = (bg, color) => ({
14100
+ width: "100%",
14101
+ height: "100%",
14102
+ borderRadius: "50%",
14103
+ background: bg,
14104
+ color,
14105
+ display: "inline-flex",
14106
+ alignItems: "center",
14107
+ justifyContent: "center",
14108
+ fontSize: "0.95rem",
14109
+ fontWeight: 700
13679
14110
  });
13680
- var shimmerInfoStyle = {
14111
+ var limitEditLabelStyle = (color, weight) => ({
14112
+ fontSize: "1rem",
14113
+ fontWeight: weight,
14114
+ lineHeight: "normal",
14115
+ color,
14116
+ whiteSpace: "nowrap",
14117
+ flexShrink: 0
14118
+ });
14119
+ var limitEditRightStyle = {
14120
+ marginLeft: "auto",
13681
14121
  display: "flex",
13682
- flexDirection: "column",
13683
- gap: 6,
13684
- flex: 1,
14122
+ alignItems: "center",
14123
+ gap: 8,
13685
14124
  minWidth: 0
13686
14125
  };
13687
- var shimmerLineStyle = (width, baseColor, highlightColor) => ({
13688
- width,
13689
- height: 12,
13690
- borderRadius: 6,
13691
- background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
13692
- backgroundSize: "200% 100%",
13693
- animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14126
+ var limitAmountStyle = (color, weight) => ({
14127
+ display: "inline-flex",
14128
+ alignItems: "baseline",
14129
+ gap: 1,
14130
+ color,
14131
+ fontSize: "1rem",
14132
+ fontWeight: weight
13694
14133
  });
13695
- var emptyStyle = (color) => ({
13696
- padding: "32px 16px",
13697
- textAlign: "center",
14134
+ var limitDollarStyle = (isZero, color) => ({
14135
+ opacity: isZero ? 0.5 : 1,
14136
+ color
14137
+ });
14138
+ var limitInputStyle = (color, mutedColor) => ({
14139
+ background: "transparent",
14140
+ border: "none",
14141
+ outline: "none",
13698
14142
  color,
13699
- fontSize: "0.92rem"
14143
+ font: "inherit",
14144
+ textAlign: "right",
14145
+ padding: 0,
14146
+ margin: 0,
14147
+ width: "auto",
14148
+ minWidth: "1ch",
14149
+ ...{ fieldSizing: "content" },
14150
+ fontVariantNumeric: "tabular-nums",
14151
+ caretColor: color,
14152
+ ["--link-limit-placeholder"]: mutedColor
13700
14153
  });
13701
- var bannerSlotStyle = {
13702
- display: "flex",
13703
- justifyContent: "center",
13704
- width: "100%"
13705
- };
13706
- var footerStackStyle4 = {
14154
+ var limitResetStyle = (borderColor, color, weight) => ({
14155
+ flexShrink: 0,
14156
+ padding: "5px 12px",
14157
+ borderRadius: 999,
14158
+ fontSize: "0.8rem",
14159
+ fontWeight: weight,
14160
+ fontFamily: "inherit",
14161
+ cursor: "pointer",
14162
+ border: `1.5px solid ${borderColor}`,
14163
+ background: "transparent",
14164
+ color
14165
+ });
14166
+ var infoCardStyle3 = (bg) => ({
13707
14167
  display: "flex",
13708
14168
  flexDirection: "column",
13709
14169
  gap: 16,
13710
- width: "100%"
13711
- };
13712
- var lockBannerStyle2 = {
14170
+ width: "100%",
14171
+ boxSizing: "border-box",
14172
+ padding: 16,
14173
+ borderRadius: 20,
14174
+ background: bg
14175
+ });
14176
+ var infoRowStyle2 = {
13713
14177
  display: "flex",
13714
14178
  alignItems: "flex-start",
13715
- gap: 16,
13716
- padding: "12px 16px 12px 12px",
13717
- borderRadius: 16,
13718
- width: "100%",
13719
- boxSizing: "border-box"
14179
+ gap: 14,
14180
+ width: "100%"
13720
14181
  };
13721
- var lockIconWrapStyle2 = (color) => ({
14182
+ var infoIconStyle = (color) => ({
13722
14183
  flexShrink: 0,
13723
14184
  width: 24,
13724
14185
  height: 24,
@@ -13727,14 +14188,60 @@ var lockIconWrapStyle2 = (color) => ({
13727
14188
  justifyContent: "center",
13728
14189
  color
13729
14190
  });
13730
- var lockBannerTextStyle2 = (color) => ({
13731
- margin: 0,
14191
+ var infoTextColStyle = {
14192
+ display: "flex",
14193
+ flexDirection: "column",
14194
+ gap: 2,
14195
+ flex: 1,
14196
+ minWidth: 0
14197
+ };
14198
+ var infoTitleStyle2 = (color, weight) => ({
14199
+ fontSize: "1rem",
14200
+ fontWeight: weight,
14201
+ lineHeight: "normal",
14202
+ color
14203
+ });
14204
+ var infoBodyStyle = (color, weight) => ({
14205
+ fontSize: "0.85rem",
14206
+ fontWeight: weight,
14207
+ lineHeight: 1.4,
14208
+ color
14209
+ });
14210
+ var shimmerCircleStyle = (baseColor, highlightColor) => ({
14211
+ width: 40,
14212
+ height: 40,
14213
+ borderRadius: "50%",
14214
+ flexShrink: 0,
14215
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
14216
+ backgroundSize: "200% 100%",
14217
+ animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14218
+ });
14219
+ var shimmerInfoStyle = {
14220
+ display: "flex",
14221
+ flexDirection: "column",
14222
+ gap: 6,
13732
14223
  flex: 1,
13733
- fontSize: 12,
13734
- fontWeight: 500,
13735
- lineHeight: "normal",
13736
- color
14224
+ minWidth: 0
14225
+ };
14226
+ var shimmerLineStyle = (width, baseColor, highlightColor) => ({
14227
+ width,
14228
+ height: 12,
14229
+ borderRadius: 6,
14230
+ background: `linear-gradient(90deg, ${baseColor} 0%, ${highlightColor} 50%, ${baseColor} 100%)`,
14231
+ backgroundSize: "200% 100%",
14232
+ animation: "blink-link-tokens-shimmer 1.4s ease-in-out infinite"
14233
+ });
14234
+ var emptyStyle = (color) => ({
14235
+ padding: "32px 16px",
14236
+ textAlign: "center",
14237
+ color,
14238
+ fontSize: "0.92rem"
13737
14239
  });
14240
+ var bannerSlotStyle = {
14241
+ display: "flex",
14242
+ justifyContent: "center",
14243
+ width: "100%"
14244
+ };
13738
14245
  function DepositCompleteScreen({ amount }) {
13739
14246
  const { tokens } = useBlinkConfig();
13740
14247
  return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
@@ -13796,6 +14303,7 @@ function SelectDepositSourceScreen({
13796
14303
  tokenOptions,
13797
14304
  selectedTokenSymbol,
13798
14305
  selectedChainName,
14306
+ balancesLoading,
13799
14307
  selectedWalletId,
13800
14308
  onPickToken,
13801
14309
  useDeeplink,
@@ -13812,8 +14320,9 @@ function SelectDepositSourceScreen({
13812
14320
  const [pendingMobileSelection, setPendingMobileSelection] = useState(null);
13813
14321
  const [preparedAuthorization, setPreparedAuthorization] = useState(null);
13814
14322
  const [preparingAuthorization, setPreparingAuthorization] = useState(false);
14323
+ const isAuthorizedOption = (opt) => !opt.status || opt.status === "AUTHORIZED";
13815
14324
  const selectableOptions = tokenOptions.filter(
13816
- (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
14325
+ (opt) => isAuthorizedOption(opt) || opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minTransferAmountUsd)
13817
14326
  );
13818
14327
  const authorized = selectableOptions.filter(
13819
14328
  (opt) => !opt.status || opt.status === "AUTHORIZED"
@@ -13967,6 +14476,30 @@ function SelectDepositSourceScreen({
13967
14476
  "data-testid": "select-deposit-source-scroll-content",
13968
14477
  style: contentStyle12,
13969
14478
  children: [
14479
+ balancesLoading && tokenOptions.length === 0 && /* @__PURE__ */ jsx("div", { style: accountSectionStyle, "data-testid": "select-source-shimmer", children: /* @__PURE__ */ jsx("div", { style: groupCardStyle(tokens.bgRecessed), children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs(
14480
+ "div",
14481
+ {
14482
+ style: { display: "flex", alignItems: "center", gap: 12, padding: 12 },
14483
+ children: [
14484
+ /* @__PURE__ */ jsx(
14485
+ Shimmer,
14486
+ {
14487
+ width: 36,
14488
+ height: 36,
14489
+ borderRadius: "50%",
14490
+ baseColor: tokens.bgInput,
14491
+ highlightColor: tokens.border
14492
+ }
14493
+ ),
14494
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: 1 }, children: [
14495
+ /* @__PURE__ */ jsx(Shimmer, { width: 96, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
14496
+ /* @__PURE__ */ jsx(Shimmer, { width: 56, height: 10, baseColor: tokens.bgInput, highlightColor: tokens.border })
14497
+ ] }),
14498
+ /* @__PURE__ */ jsx(Shimmer, { width: 48, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border })
14499
+ ]
14500
+ },
14501
+ `shimmer-row-${i}`
14502
+ )) }) }),
13970
14503
  orderedAccounts.map((account) => {
13971
14504
  const authRows = authorized.filter((opt) => rowMatchesSection(opt, account));
13972
14505
  const reqRows = requiresAuth.filter((opt) => rowMatchesSection(opt, account)).sort((a, b) => Number(!!a.notSupported) - Number(!!b.notSupported));
@@ -14181,6 +14714,7 @@ function ShimmerBlock({
14181
14714
  );
14182
14715
  }
14183
14716
  function DepositScreen({
14717
+ balancesLoading,
14184
14718
  availableBalance,
14185
14719
  remainingLimit,
14186
14720
  initialAmount,
@@ -14234,6 +14768,10 @@ function DepositScreen({
14234
14768
  const showMobileKeypad = isEntryMode && !isDesktop;
14235
14769
  const [keypadOpen, setKeypadOpen] = useState(showMobileKeypad);
14236
14770
  const [tokenPickerOpen, setTokenPickerOpen] = useState(false);
14771
+ const hasLoadedQuoteRef = useRef(false);
14772
+ if (!quoteLoading) {
14773
+ hasLoadedQuoteRef.current = true;
14774
+ }
14237
14775
  const selectableTokenOptions = tokenOptions?.filter(
14238
14776
  (opt) => opt.balance == null || isSelectableDepositSourceAmountUsd(opt.balance, minDepositFloor)
14239
14777
  ) ?? [];
@@ -14251,11 +14789,11 @@ function DepositScreen({
14251
14789
  }, []);
14252
14790
  const selectedAccount = accounts?.find((a) => a.id === selectedAccountId);
14253
14791
  const selectedProviderName = selectedAccount?.name ?? "Wallet";
14254
- const isLowBalance = availableBalance < minDepositFloor;
14255
- const insufficientFunds = availableBalance < validationAmount;
14792
+ const isLowBalance = !balancesLoading && availableBalance < minDepositFloor;
14793
+ const insufficientFunds = !balancesLoading && availableBalance < validationAmount;
14256
14794
  const needsAuthorization = selectedTokenStatus != null && selectedTokenStatus !== "AUTHORIZED" && !insufficientFunds && !isLowBalance;
14257
14795
  const exceedsLimit = remainingLimit != null && validationAmount > remainingLimit && !isLowBalance && !needsAuthorization;
14258
- const canDeposit = validationAmount >= minDepositFloor && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14796
+ const canDeposit = validationAmount >= minDepositFloor && !balancesLoading && !exceedsLimit && !isLowBalance && !insufficientFunds && !needsAuthorization && !processing;
14259
14797
  const hasAccountPill = !!accounts && accounts.length > 0;
14260
14798
  const pillClickable = canOpenInlineSheet || !!onSelectToken;
14261
14799
  const formattedBalance = selectedTokenSymbol != null ? `$${formatUsdTwoDecimals2(availableBalance)}` : void 0;
@@ -14278,6 +14816,7 @@ function DepositScreen({
14278
14816
  accounts: depositSourceAccounts,
14279
14817
  selectedAccountId: selectedAccountId ?? null,
14280
14818
  tokenOptions: selectableTokenOptions,
14819
+ balancesLoading,
14281
14820
  selectedTokenSymbol,
14282
14821
  selectedChainName,
14283
14822
  selectedWalletId: selectedWalletId ?? null,
@@ -14299,7 +14838,7 @@ function DepositScreen({
14299
14838
  const mobileEntryWithKeypad = isEntryMode && !isDesktop && keypadOpen;
14300
14839
  const showLiveMobileHero = isEntryMode && !isDesktop && keypadOpen;
14301
14840
  const showDesktopInputHero = isEntryMode && isDesktop;
14302
- const showFormattedHero = !showLiveMobileHero && !showDesktopInputHero;
14841
+ const showFeeRow = validationAmount > 0;
14303
14842
  let primaryButton;
14304
14843
  if (mobileEntryWithKeypad) {
14305
14844
  const canConfirmEntry = canContinue(amountEntryValue ?? "");
@@ -14429,7 +14968,32 @@ function DepositScreen({
14429
14968
  "aria-label": tokenAriaLabel,
14430
14969
  "aria-expanded": canOpenInlineSheet ? tokenPickerOpen : void 0,
14431
14970
  "aria-haspopup": canOpenInlineSheet ? "listbox" : void 0,
14432
- children: /* @__PURE__ */ jsx(
14971
+ children: balancesLoading ? (
14972
+ // Until balances merge in we don't know the selected token or its
14973
+ // balance, so the whole pill (icon + name + balance) is a shimmer
14974
+ // placeholder rather than showing the account name as a default.
14975
+ // Render through SourcePill so the placeholder inherits the exact
14976
+ // same container (fill, radius, padding, chevron) as the loaded
14977
+ // pill and does not change size when balances arrive.
14978
+ /* @__PURE__ */ jsx(
14979
+ "span",
14980
+ {
14981
+ "data-testid": "deposit-pill-shimmer",
14982
+ "aria-label": "Loading balance",
14983
+ "aria-busy": "true",
14984
+ style: { display: "inline-flex" },
14985
+ children: /* @__PURE__ */ jsx(
14986
+ SourcePill,
14987
+ {
14988
+ icon: /* @__PURE__ */ jsx(Shimmer, { width: 28, height: 28, borderRadius: "50%", baseColor: tokens.bgInput, highlightColor: tokens.border }),
14989
+ nameSlot: /* @__PURE__ */ jsx(Shimmer, { width: 72, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
14990
+ balanceSlot: /* @__PURE__ */ jsx(Shimmer, { width: 48, height: 12, baseColor: tokens.bgInput, highlightColor: tokens.border }),
14991
+ showChevron: pillClickable
14992
+ }
14993
+ )
14994
+ }
14995
+ )
14996
+ ) : /* @__PURE__ */ jsx(
14433
14997
  SourcePill,
14434
14998
  {
14435
14999
  icon: /* @__PURE__ */ jsx(
@@ -14449,7 +15013,7 @@ function DepositScreen({
14449
15013
  )
14450
15014
  }
14451
15015
  ),
14452
- showFormattedHero && /* @__PURE__ */ jsx("div", { style: redesignFeeRowStyle, children: quoteLoading ? /* @__PURE__ */ jsx(
15016
+ /* @__PURE__ */ jsx("div", { "data-testid": "deposit-fee-row", style: redesignFeeRowStyle, children: showFeeRow && (quoteLoading && !hasLoadedQuoteRef.current ? /* @__PURE__ */ jsx(
14453
15017
  "div",
14454
15018
  {
14455
15019
  "data-testid": "deposit-fee-shimmer",
@@ -14467,7 +15031,7 @@ function DepositScreen({
14467
15031
  }
14468
15032
  )
14469
15033
  }
14470
- ) : /* @__PURE__ */ jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" }) })
15034
+ ) : /* @__PURE__ */ jsx("span", { style: redesignNoFeesStyle(tokens.text), children: "No fees" })) })
14471
15035
  ] }),
14472
15036
  /* @__PURE__ */ jsx("div", { style: bannerSlotStyle2, children: mobileEntryWithKeypad ? null : showLowFunds ? /* @__PURE__ */ jsx(
14473
15037
  NotificationBanner,
@@ -15844,7 +16408,7 @@ function OpenWalletScreen({
15844
16408
  return /* @__PURE__ */ jsxs(
15845
16409
  ScreenLayout,
15846
16410
  {
15847
- footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle5, children: [
16411
+ footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle4, children: [
15848
16412
  error && /* @__PURE__ */ jsx(InfoBanner, { children: error }),
15849
16413
  /* @__PURE__ */ jsxs(
15850
16414
  SecondaryButton,
@@ -15891,7 +16455,7 @@ function OpenWalletScreen({
15891
16455
  return /* @__PURE__ */ jsxs(
15892
16456
  ScreenLayout,
15893
16457
  {
15894
- footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle5, children: [
16458
+ footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle4, children: [
15895
16459
  error && /* @__PURE__ */ jsx(InfoBanner, { children: error }),
15896
16460
  /* @__PURE__ */ jsxs(
15897
16461
  SecondaryButton,
@@ -15975,7 +16539,7 @@ var primaryClusterStyle = {
15975
16539
  textAlign: "center",
15976
16540
  gap: 12
15977
16541
  };
15978
- var footerStackStyle5 = {
16542
+ var footerStackStyle4 = {
15979
16543
  display: "flex",
15980
16544
  flexDirection: "column",
15981
16545
  gap: 8
@@ -16103,6 +16667,12 @@ function ApprovingInWalletScreen({
16103
16667
  chainName,
16104
16668
  step,
16105
16669
  complete,
16670
+ signing,
16671
+ spendingLimitLabel,
16672
+ destinationAddress,
16673
+ tokenLogoUri,
16674
+ awaitingApproval,
16675
+ onApprove,
16106
16676
  error,
16107
16677
  onRetry,
16108
16678
  onBack,
@@ -16112,10 +16682,11 @@ function ApprovingInWalletScreen({
16112
16682
  onOpenWallet
16113
16683
  }) {
16114
16684
  const { tokens: t } = useBlinkConfig();
16115
- const showDelayedRetry = useDelayedRetry(error == null && onRetry != null);
16685
+ const showDelayedRetry = useDelayedRetry(!awaitingApproval && error == null && onRetry != null);
16116
16686
  const retryVisible = onRetry != null && (error != null || showDelayedRetry);
16117
16687
  const token = tokenSymbol ?? "token";
16118
- const steps = buildSteps(step, tokenSymbol, chainName, complete);
16688
+ const chain = chainName ?? "chain";
16689
+ const steps = buildSteps(step, tokenSymbol, complete, signing);
16119
16690
  const foregroundNavigation = foregroundDeeplink ? classifyWalletDeeplinkNavigation(foregroundDeeplink) : null;
16120
16691
  const foregroundWithJs = foregroundNavigation ? shouldOpenWithJavaScript(foregroundNavigation) : false;
16121
16692
  const showOpenWalletButton = !!foregroundDeeplink && onOpenWallet != null;
@@ -16123,11 +16694,7 @@ function ApprovingInWalletScreen({
16123
16694
  ScreenLayout,
16124
16695
  {
16125
16696
  scrollableBody: false,
16126
- footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle6, children: [
16127
- /* @__PURE__ */ jsxs("div", { style: lockBannerStyle3, children: [
16128
- /* @__PURE__ */ jsx("span", { style: lockIconWrapStyle3(t.text), children: /* @__PURE__ */ jsx(LockIcon4, {}) }),
16129
- /* @__PURE__ */ jsx("p", { style: lockBannerTextStyle3(t.text), children: "Your passkey is required each time you deposit. Funds cannot move without your approval." })
16130
- ] }),
16697
+ footer: /* @__PURE__ */ jsxs("div", { style: footerStackStyle5, children: [
16131
16698
  showOpenWalletButton && /* @__PURE__ */ jsxs(
16132
16699
  SecondaryButton,
16133
16700
  {
@@ -16144,7 +16711,7 @@ function ApprovingInWalletScreen({
16144
16711
  retryVisible ? /* @__PURE__ */ jsxs("div", { style: retryStackStyle, children: [
16145
16712
  error && /* @__PURE__ */ jsx(InfoBanner, { children: error }),
16146
16713
  /* @__PURE__ */ jsx(OutlineButton, { onClick: onRetry, children: "Retry" })
16147
- ] }) : /* @__PURE__ */ jsx(PrimaryButton, { spinnerOnly: true })
16714
+ ] }) : awaitingApproval ? /* @__PURE__ */ jsx(PrimaryButton, { onClick: onApprove, children: "Approve" }) : /* @__PURE__ */ jsx(PrimaryButton, { spinnerOnly: true })
16148
16715
  ] }),
16149
16716
  children: [
16150
16717
  /* @__PURE__ */ jsx(
@@ -16156,7 +16723,39 @@ function ApprovingInWalletScreen({
16156
16723
  }
16157
16724
  ),
16158
16725
  /* @__PURE__ */ jsxs("div", { style: contentStyle16, children: [
16159
- /* @__PURE__ */ jsx("h2", { style: headingStyle13(t.text), children: `Authorizing ${token} for passkey deposits` }),
16726
+ /* @__PURE__ */ jsx("h2", { style: headingStyle13(t.text, t.fontWeightBold), children: "Approve in wallet" }),
16727
+ /* @__PURE__ */ jsxs("div", { style: transferCardStyle(t.bgCardTranslucent, t.radiusLg), children: [
16728
+ /* @__PURE__ */ jsx(
16729
+ AddressRow,
16730
+ {
16731
+ circle: /* @__PURE__ */ jsx(
16732
+ LogoCircle,
16733
+ {
16734
+ src: tokenLogoUri ?? TOKEN_LOGOS[token] ?? void 0,
16735
+ fallback: token.slice(0, 1).toUpperCase(),
16736
+ size: 40
16737
+ }
16738
+ ),
16739
+ title: `${token} on ${chain}`,
16740
+ subtitle: spendingLimitLabel
16741
+ }
16742
+ ),
16743
+ /* @__PURE__ */ jsx("div", { style: arrowRowStyle, children: /* @__PURE__ */ jsx(ArrowDownIcon, { color: t.textMuted }) }),
16744
+ /* @__PURE__ */ jsx(
16745
+ AddressRow,
16746
+ {
16747
+ circle: /* @__PURE__ */ jsx("span", { style: passkeyCircleStyle(t.accent, t.highlight), children: /* @__PURE__ */ jsx(KeyIcon, { size: 22 }) }),
16748
+ title: "Your Blink Passkey",
16749
+ address: destinationAddress,
16750
+ addressLoading: destinationAddress == null
16751
+ }
16752
+ )
16753
+ ] }),
16754
+ /* @__PURE__ */ jsxs("div", { style: dividerRowStyle3, children: [
16755
+ /* @__PURE__ */ jsx("span", { style: dividerLineStyle(t.textMuted) }),
16756
+ /* @__PURE__ */ jsx("span", { style: dividerLabelStyle3(t.textMuted, t.fontWeightSemibold), children: "Continue in wallet" }),
16757
+ /* @__PURE__ */ jsx("span", { style: dividerLineStyle(t.textMuted) })
16758
+ ] }),
16160
16759
  /* @__PURE__ */ jsxs("div", { style: cardStyle2(t.bgRecessed, t.radiusLg), children: [
16161
16760
  /* @__PURE__ */ jsx("style", { children: SPIN_KEYFRAMES_CSS2 }),
16162
16761
  steps.map((s, i) => /* @__PURE__ */ jsx(ChecklistRow, { step: s }, i))
@@ -16166,6 +16765,32 @@ function ApprovingInWalletScreen({
16166
16765
  }
16167
16766
  );
16168
16767
  }
16768
+ function AddressRow({
16769
+ circle,
16770
+ title,
16771
+ address,
16772
+ subtitle,
16773
+ addressLoading
16774
+ }) {
16775
+ const { tokens: t } = useBlinkConfig();
16776
+ const sub = subtitle ?? (address ? truncateMiddle(address) : null);
16777
+ return /* @__PURE__ */ jsxs("div", { style: addressRowStyle, children: [
16778
+ circle,
16779
+ /* @__PURE__ */ jsxs("div", { style: addressTextColStyle, children: [
16780
+ /* @__PURE__ */ jsx("span", { style: addressTitleStyle(t.text, t.fontWeightSemibold), children: title }),
16781
+ (sub || addressLoading) && /* @__PURE__ */ jsx("span", { style: addressSubSlotStyle, children: sub ? /* @__PURE__ */ jsx("span", { style: addressSubStyle(t.textMuted, t.fontWeightRegular), children: sub }) : /* @__PURE__ */ jsx(
16782
+ Shimmer,
16783
+ {
16784
+ width: 132,
16785
+ height: 12,
16786
+ borderRadius: 4,
16787
+ baseColor: t.bgInput,
16788
+ highlightColor: t.border
16789
+ }
16790
+ ) })
16791
+ ] })
16792
+ ] });
16793
+ }
16169
16794
  function ChecklistRow({ step }) {
16170
16795
  const { tokens: t } = useBlinkConfig();
16171
16796
  const isComplete = step.status === "complete";
@@ -16178,7 +16803,7 @@ function ChecklistRow({ step }) {
16178
16803
  fill: t.accentText
16179
16804
  }
16180
16805
  ) }) }) : isActive ? /* @__PURE__ */ jsx(RowSpinner, { color: t.accent }) : /* @__PURE__ */ jsx("span", { style: pendingBadgeStyle(t.bgRecessed) }),
16181
- /* @__PURE__ */ jsx("span", { style: labelStyle7(isComplete || isActive ? t.text : t.textMuted), children: step.label })
16806
+ /* @__PURE__ */ jsx("span", { style: labelStyle7(isComplete || isActive ? t.text : t.textMuted, t.fontWeightRegular), children: step.label })
16182
16807
  ] });
16183
16808
  }
16184
16809
  function RowSpinner({ color }) {
@@ -16213,22 +16838,28 @@ function RowSpinner({ color }) {
16213
16838
  }
16214
16839
  );
16215
16840
  }
16216
- function buildSteps(step, tokenSymbol, chainName, complete) {
16841
+ function ArrowDownIcon({ color }) {
16842
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", style: { color }, children: [
16843
+ /* @__PURE__ */ jsx("path", { d: "M12 5v14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }),
16844
+ /* @__PURE__ */ jsx("path", { d: "M6 13l6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
16845
+ ] });
16846
+ }
16847
+ function buildSteps(step, tokenSymbol, complete, signing) {
16217
16848
  const token = tokenSymbol ?? "token";
16218
- const chain = chainName ?? "chain";
16219
- const passkeyLabel = `Approve ${token} for passkey`;
16849
+ const passkeyLabel = "Assign spending right to your passkey";
16850
+ const activeStatus = signing ? "active" : "pending";
16220
16851
  if (step === "spl") {
16221
- return [{ label: passkeyLabel, status: complete ? "complete" : "active" }];
16852
+ return [{ label: passkeyLabel, status: complete ? "complete" : activeStatus }];
16222
16853
  }
16223
- const approveLabel = `Approve ${token} on ${chain}`;
16854
+ const approveLabel = `Approve ${token} for spending via Permit2`;
16224
16855
  return [
16225
16856
  {
16226
16857
  label: approveLabel,
16227
- status: complete || step !== "approve" ? "complete" : "active"
16858
+ status: complete || step !== "approve" ? "complete" : activeStatus
16228
16859
  },
16229
16860
  {
16230
16861
  label: passkeyLabel,
16231
- status: complete ? "complete" : step === "approve" ? "pending" : "active"
16862
+ status: complete ? "complete" : step === "approve" ? "pending" : activeStatus
16232
16863
  }
16233
16864
  ];
16234
16865
  }
@@ -16244,18 +16875,6 @@ function useDelayedRetry(enabled) {
16244
16875
  }, [enabled]);
16245
16876
  return visible;
16246
16877
  }
16247
- function LockIcon4() {
16248
- return /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
16249
- "path",
16250
- {
16251
- 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",
16252
- stroke: "currentColor",
16253
- strokeWidth: "2",
16254
- strokeLinecap: "round",
16255
- strokeLinejoin: "round"
16256
- }
16257
- ) });
16258
- }
16259
16878
  var wordmarkImgStyle5 = {
16260
16879
  height: 22,
16261
16880
  width: "auto",
@@ -16273,15 +16892,84 @@ var contentStyle16 = {
16273
16892
  minHeight: 0,
16274
16893
  width: "100%"
16275
16894
  };
16276
- var headingStyle13 = (color) => ({
16895
+ var headingStyle13 = (color, fontWeight) => ({
16277
16896
  fontSize: 24,
16278
- fontWeight: 700,
16897
+ fontWeight,
16279
16898
  lineHeight: "normal",
16280
16899
  letterSpacing: 0,
16281
16900
  color,
16282
- margin: 0,
16901
+ margin: "8px 0 8px",
16283
16902
  textAlign: "center"
16284
16903
  });
16904
+ var transferCardStyle = (bg, radius) => ({
16905
+ background: bg,
16906
+ borderRadius: radius,
16907
+ padding: "20px 16px",
16908
+ width: "100%",
16909
+ boxSizing: "border-box",
16910
+ display: "flex",
16911
+ flex: 1,
16912
+ flexDirection: "column",
16913
+ justifyContent: "space-between",
16914
+ gap: 12
16915
+ });
16916
+ var dividerRowStyle3 = {
16917
+ display: "flex",
16918
+ alignItems: "center",
16919
+ gap: 12,
16920
+ width: "100%"
16921
+ };
16922
+ var dividerLineStyle = (color) => ({
16923
+ flex: 1,
16924
+ height: 1,
16925
+ background: color
16926
+ });
16927
+ var dividerLabelStyle3 = (color, fontWeight) => ({
16928
+ fontSize: 11,
16929
+ fontWeight,
16930
+ letterSpacing: "0.08em",
16931
+ textTransform: "uppercase",
16932
+ color,
16933
+ whiteSpace: "nowrap"
16934
+ });
16935
+ var addressRowStyle = {
16936
+ display: "flex",
16937
+ alignItems: "center",
16938
+ gap: 12,
16939
+ width: "100%"
16940
+ };
16941
+ var addressTextColStyle = {
16942
+ display: "flex",
16943
+ flexDirection: "column",
16944
+ gap: 2,
16945
+ minWidth: 0
16946
+ };
16947
+ var addressTitleStyle = (color, fontWeight) => ({
16948
+ fontSize: 16,
16949
+ fontWeight,
16950
+ lineHeight: 1.25,
16951
+ color
16952
+ });
16953
+ var addressSubSlotStyle = {
16954
+ display: "flex",
16955
+ alignItems: "center",
16956
+ minHeight: 18
16957
+ };
16958
+ var addressSubStyle = (color, fontWeight) => ({
16959
+ fontSize: 14,
16960
+ fontWeight,
16961
+ lineHeight: 1.25,
16962
+ color,
16963
+ overflow: "hidden",
16964
+ textOverflow: "ellipsis",
16965
+ whiteSpace: "nowrap"
16966
+ });
16967
+ var arrowRowStyle = {
16968
+ display: "flex",
16969
+ alignItems: "center",
16970
+ height: 20,
16971
+ paddingLeft: 10
16972
+ };
16285
16973
  var cardStyle2 = (bg, radius) => ({
16286
16974
  background: bg,
16287
16975
  borderRadius: radius,
@@ -16289,7 +16977,9 @@ var cardStyle2 = (bg, radius) => ({
16289
16977
  width: "100%",
16290
16978
  boxSizing: "border-box",
16291
16979
  display: "flex",
16980
+ flex: 1,
16292
16981
  flexDirection: "column",
16982
+ justifyContent: "center",
16293
16983
  gap: 4
16294
16984
  });
16295
16985
  var rowStyle6 = {
@@ -16297,10 +16987,25 @@ var rowStyle6 = {
16297
16987
  alignItems: "center",
16298
16988
  gap: 16,
16299
16989
  padding: "12px 16px 12px 12px",
16990
+ // Reserve a stable height (two label lines + vertical padding) so swapping the
16991
+ // leading badge between the pending circle, the spinner, and the completion
16992
+ // checkmark — and the muted→active label restyle — never reflows the card.
16993
+ minHeight: 64,
16300
16994
  borderRadius: 16,
16301
16995
  width: "100%",
16302
16996
  boxSizing: "border-box"
16303
16997
  };
16998
+ var passkeyCircleStyle = (bg, iconColor) => ({
16999
+ width: 40,
17000
+ height: 40,
17001
+ borderRadius: "50%",
17002
+ background: bg,
17003
+ color: iconColor,
17004
+ display: "inline-flex",
17005
+ alignItems: "center",
17006
+ justifyContent: "center",
17007
+ flexShrink: 0
17008
+ });
16304
17009
  var completeBadgeStyle = (color) => ({
16305
17010
  width: 24,
16306
17011
  height: 24,
@@ -16318,13 +17023,13 @@ var pendingBadgeStyle = (color) => ({
16318
17023
  background: color,
16319
17024
  flexShrink: 0
16320
17025
  });
16321
- var labelStyle7 = (color) => ({
17026
+ var labelStyle7 = (color, fontWeight) => ({
16322
17027
  fontSize: "1rem",
16323
- fontWeight: 400,
17028
+ fontWeight,
16324
17029
  lineHeight: 1.25,
16325
17030
  color
16326
17031
  });
16327
- var footerStackStyle6 = {
17032
+ var footerStackStyle5 = {
16328
17033
  display: "flex",
16329
17034
  flexDirection: "column",
16330
17035
  gap: 16,
@@ -16336,32 +17041,6 @@ var retryStackStyle = {
16336
17041
  gap: 8,
16337
17042
  width: "100%"
16338
17043
  };
16339
- var lockBannerStyle3 = {
16340
- display: "flex",
16341
- alignItems: "flex-start",
16342
- gap: 16,
16343
- padding: "12px 16px 12px 12px",
16344
- borderRadius: 16,
16345
- width: "100%",
16346
- boxSizing: "border-box"
16347
- };
16348
- var lockIconWrapStyle3 = (color) => ({
16349
- flexShrink: 0,
16350
- width: 24,
16351
- height: 24,
16352
- display: "inline-flex",
16353
- alignItems: "center",
16354
- justifyContent: "center",
16355
- color
16356
- });
16357
- var lockBannerTextStyle3 = (color) => ({
16358
- margin: 0,
16359
- flex: 1,
16360
- fontSize: 12,
16361
- fontWeight: 500,
16362
- lineHeight: "normal",
16363
- color
16364
- });
16365
17044
  function ConfirmSignScreen({
16366
17045
  walletName,
16367
17046
  chainFamily,
@@ -16487,7 +17166,7 @@ function TokenPickerScreen({
16487
17166
  }
16488
17167
  const entries2 = [];
16489
17168
  for (const wallet of account.wallets) {
16490
- for (const source of wallet.sources) {
17169
+ for (const source of wallet.sources ?? []) {
16491
17170
  const visibleBalance = source.balance.available.amount;
16492
17171
  if (!isSelectableDepositSourceAmountUsd(visibleBalance, minDepositFloor)) continue;
16493
17172
  entries2.push({
@@ -17328,22 +18007,45 @@ function buildOpenWalletScreenProps({
17328
18007
  function isApprovingInWalletAction(actionType) {
17329
18008
  return actionType === "APPROVE_PERMIT2" || actionType === "SIGN_PERMIT2" || actionType === "APPROVE_SPL";
17330
18009
  }
18010
+ function formatUsdTwoDecimals3(value) {
18011
+ return (Number.isFinite(value) ? value : 0).toLocaleString("en-US", {
18012
+ minimumFractionDigits: 2,
18013
+ maximumFractionDigits: 2
18014
+ });
18015
+ }
18016
+ function readMetadataString(metadata, key) {
18017
+ const value = metadata?.[key];
18018
+ return typeof value === "string" && value.trim() !== "" ? value : null;
18019
+ }
17331
18020
  function buildApprovingInWalletScreenProps({
17332
18021
  flow,
17333
18022
  remote,
18023
+ derived,
17334
18024
  handlers
17335
18025
  }) {
17336
18026
  const { state } = flow;
17337
18027
  const setupToken = state.setupDepositToken;
17338
- const actionType = remote.authExecutorCurrentAction?.type ?? null;
18028
+ const currentAction = remote.authExecutorCurrentAction;
18029
+ const actionType = currentAction?.type ?? null;
18030
+ const limit = state.setupSpendingLimit;
18031
+ const spendingLimitLabel = limit == null ? null : "unlimited" in limit ? "Unlimited lifetime spending limit" : `$${formatUsdTwoDecimals3(limit.usd)} lifetime spending limit`;
18032
+ const destinationAddress = remote.authApprovalSmartAccountAddress ?? remote.authExecutorApprovalDestinationAddress ?? readMetadataString(currentAction?.metadata, "smartAccountAddress") ?? readMetadataString(currentAction?.metadata, "ownerPubkey");
17339
18033
  const isSolana = setupToken != null && state.chains.find((c) => c.name === setupToken.chainName)?.chainFamily === "svm";
17340
18034
  const step = actionType === "SIGN_PERMIT2" ? "sign" : actionType === "APPROVE_SPL" ? "spl" : actionType == null && isSolana ? "spl" : "approve";
17341
18035
  const complete = remote.authExecutorCompleted && actionType == null && !remote.authExecutorExecuting;
18036
+ const awaitingApproval = remote.authExecutorAwaitingApproval ?? false;
18037
+ const signing = isApprovingInWalletAction(actionType) && !awaitingApproval && !complete;
17342
18038
  return {
17343
18039
  tokenSymbol: setupToken?.symbol ?? null,
17344
18040
  chainName: setupToken?.chainName ?? null,
17345
18041
  step,
17346
18042
  complete,
18043
+ signing,
18044
+ spendingLimitLabel,
18045
+ destinationAddress,
18046
+ tokenLogoUri: derived.selectedSource?.token.logoURI ?? null,
18047
+ awaitingApproval,
18048
+ onApprove: handlers.onApprove,
17347
18049
  error: flow.state.error || remote.authExecutorError,
17348
18050
  onRetry: handlers.onRetryAuthorization,
17349
18051
  onLogout: flow.isDesktop ? handlers.onLogout : void 0,
@@ -17362,7 +18064,7 @@ function buildLinkTokensScreenProps({
17362
18064
  handlers
17363
18065
  }) {
17364
18066
  const sourceOptions = remote.pendingSelectSource?.metadata?.options ?? [];
17365
- const rowContexts = derived.selectSourceChoices.flatMap(
18067
+ const supportedRows = derived.selectSourceChoices.flatMap(
17366
18068
  (chain) => chain.tokens.map((t) => {
17367
18069
  const rawOption = sourceOptions.find(
17368
18070
  (option) => option.chainName === chain.chainName && option.tokenSymbol === t.tokenSymbol
@@ -17382,6 +18084,10 @@ function buildLinkTokensScreenProps({
17382
18084
  };
17383
18085
  })
17384
18086
  );
18087
+ const rowContexts = [
18088
+ ...supportedRows.filter((row) => isVisibleUsdAmountAtTwoDecimals(row.balanceUsd)),
18089
+ ...supportedRows.filter((row) => !isVisibleUsdAmountAtTwoDecimals(row.balanceUsd))
18090
+ ];
17385
18091
  rowContexts.push(
17386
18092
  ...derived.selectSourceUnsupportedChoices.flatMap(
17387
18093
  (chain) => chain.tokens.map((t) => ({
@@ -17403,6 +18109,17 @@ function buildLinkTokensScreenProps({
17403
18109
  balanceLabel: `${formatNativeAmount(native.amount)} ${native.tokenSymbol}`
17404
18110
  }))
17405
18111
  );
18112
+ const selectedTokenSymbol = flow.state.setupDepositToken?.symbol ?? derived.selectSourceRecommended?.tokenSymbol;
18113
+ const selectedChainName = flow.state.setupDepositToken?.chainName ?? derived.selectSourceRecommended?.chainName;
18114
+ if (selectedTokenSymbol && selectedChainName) {
18115
+ const match = rowContexts.findIndex(
18116
+ (row) => row.symbol === selectedTokenSymbol && row.chainName === selectedChainName
18117
+ );
18118
+ if (match >= 0 && !rowContexts[match].notSupported) {
18119
+ const [pinned] = rowContexts.splice(match, 1);
18120
+ rowContexts.unshift(pinned);
18121
+ }
18122
+ }
17406
18123
  const entries2 = rowContexts.map(
17407
18124
  ({ symbol, chainName, balanceUsd, notSupported, tokenLogoUri, balanceLabel }) => ({
17408
18125
  tokenSymbol: symbol,
@@ -17415,8 +18132,6 @@ function buildLinkTokensScreenProps({
17415
18132
  })
17416
18133
  );
17417
18134
  const firstSupportedIndex = rowContexts.findIndex((row) => !row.notSupported);
17418
- const selectedTokenSymbol = flow.state.setupDepositToken?.symbol ?? derived.selectSourceRecommended?.tokenSymbol;
17419
- const selectedChainName = flow.state.setupDepositToken?.chainName ?? derived.selectSourceRecommended?.chainName;
17420
18135
  const selectedIndex = (() => {
17421
18136
  if (!selectedTokenSymbol || !selectedChainName) {
17422
18137
  return firstSupportedIndex;
@@ -17508,9 +18223,10 @@ function buildDepositScreenProps({
17508
18223
  const registryChainIds = derived.walletConnectChainIdsByAccount[account.id] ?? null;
17509
18224
  const approvedChainIds = account.walletConnect?.approvedChainIds ?? null;
17510
18225
  for (const wallet of account.wallets) {
17511
- for (const source of wallet.sources) {
18226
+ for (const source of wallet.sources ?? []) {
17512
18227
  const balance = source.balance.available.amount;
17513
- if (!isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
18228
+ const isAuthorized = source.token.status === "AUTHORIZED";
18229
+ if (!isAuthorized && !isSelectableDepositSourceAmountUsd(balance, minDepositFloor)) continue;
17514
18230
  const chain = flow.state.chains.find((c) => c.name === wallet.chain.name);
17515
18231
  const requiresAuth = source.token.status != null && source.token.status !== "AUTHORIZED";
17516
18232
  const notSupported = requiresAuth && chain?.commonId != null && registryChainIds != null && !registryChainIds.includes(chain.commonId) && !(approvedChainIds?.includes(chain.commonId) ?? false);
@@ -17532,7 +18248,10 @@ function buildDepositScreenProps({
17532
18248
  }
17533
18249
  return {
17534
18250
  merchantName,
17535
- availableBalance: selectedSource ? selectedSource.balance.available.amount : selectedAccount ? selectedAccount.wallets.reduce((sum, w) => sum + w.balance.available.amount, 0) : maxSourceBalance,
18251
+ // Wallet balances arrive after the (balance-free) accounts load; while they
18252
+ // do, the source pill / token rows shimmer instead of showing $0.00.
18253
+ balancesLoading: state.balancesLoading,
18254
+ availableBalance: selectedSource ? selectedSource.balance.available.amount : selectedAccount ? selectedAccount.wallets.reduce((sum, w) => sum + (w.balance?.available.amount ?? 0), 0) : maxSourceBalance,
17536
18255
  remainingLimit: selectedSource != null ? selectedSource.remainingAllowance ?? null : selectedAccount?.remainingAllowance ?? null,
17537
18256
  tokenCount,
17538
18257
  initialAmount: parsedAmt,
@@ -17723,6 +18442,28 @@ function StepRendererContent({
17723
18442
  screen
17724
18443
  }) {
17725
18444
  const input = { flow, remote, derived, forms, handlers };
18445
+ const s = flow.state;
18446
+ useEffect(() => {
18447
+ if (screen !== "loading") return;
18448
+ appendDebug("warn", "stuck on loading shimmer", {
18449
+ phaseStep: s.phase.step,
18450
+ privyReady: s.privyReady,
18451
+ privyAuthenticated: s.privyAuthenticated,
18452
+ activeCredentialId: s.activeCredentialId,
18453
+ passkeyConfigLoaded: s.passkeyConfigLoaded,
18454
+ loadingData: s.loadingData,
18455
+ initialDataLoaded: s.initialDataLoaded,
18456
+ loginRequested: s.loginRequested,
18457
+ enableFullWidget: s.enableFullWidget,
18458
+ authenticatedOnOpen: s.authenticatedOnOpen,
18459
+ welcomeBackAcknowledged: s.welcomeBackAcknowledged,
18460
+ mobileFlow: s.mobileFlow,
18461
+ error: s.error,
18462
+ accountsCount: s.accounts.length,
18463
+ hasActiveWallet: s.accounts.some((a) => a.wallets.some((w) => w.status === "ACTIVE")),
18464
+ providersCount: s.providers.length
18465
+ });
18466
+ }, [screen, s]);
17726
18467
  switch (screen) {
17727
18468
  case "loading":
17728
18469
  return /* @__PURE__ */ jsx(BlinkLoadingScreen, {});
@@ -17751,11 +18492,9 @@ function StepRendererContent({
17751
18492
  case "wallet-picker":
17752
18493
  return /* @__PURE__ */ jsx(WalletPickerScreen, { ...buildWalletPickerScreenProps(input) });
17753
18494
  case "open-wallet": {
17754
- const currentActionType = input.remote.authExecutorCurrentAction?.type;
17755
- const inSigningAction = isApprovingInWalletAction(currentActionType);
17756
- const pastPairingTransient = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && currentActionType != null && (currentActionType !== "OPEN_PROVIDER" || input.remote.sourceSelectionResolved);
18495
+ const pastSourceSelection = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && input.remote.sourceSelectionResolved;
17757
18496
  const settlingPostSign = input.flow.state.linkSettling;
17758
- if (inSigningAction || pastPairingTransient || settlingPostSign) {
18497
+ if (pastSourceSelection || settlingPostSign) {
17759
18498
  return /* @__PURE__ */ jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17760
18499
  }
17761
18500
  return /* @__PURE__ */ jsx(OpenWalletScreen, { ...buildOpenWalletScreenProps(input) });
@@ -17765,12 +18504,9 @@ function StepRendererContent({
17765
18504
  if (phase.step === "wallet-setup" && phase.pendingSourceWait) {
17766
18505
  return /* @__PURE__ */ jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
17767
18506
  }
17768
- const inSigningAction = isApprovingInWalletAction(
17769
- input.remote.authExecutorCurrentAction?.type
17770
- );
17771
- const justResolvedSelectSource = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null;
18507
+ const pastSourceSelection = input.remote.authExecutorExecuting && input.remote.pendingSelectSource == null && input.remote.sourceSelectionResolved;
17772
18508
  const settlingPostSign = input.flow.state.linkSettling;
17773
- if (inSigningAction || justResolvedSelectSource || settlingPostSign) {
18509
+ if (pastSourceSelection || settlingPostSign) {
17774
18510
  return /* @__PURE__ */ jsx(ApprovingInWalletScreen, { ...buildApprovingInWalletScreenProps(input) });
17775
18511
  }
17776
18512
  return /* @__PURE__ */ jsx(LinkTokensScreen, { ...buildLinkTokensScreenProps(input) });
@@ -17854,11 +18590,12 @@ var PaymentErrorBoundary = class extends Component {
17854
18590
  };
17855
18591
  function selectedSourceForWallet(selectedWallet, selectedTokenSymbol, depositAmount, priorityContext) {
17856
18592
  if (!selectedWallet) return null;
18593
+ const walletSources = selectedWallet.sources ?? [];
17857
18594
  if (selectedTokenSymbol) {
17858
- return selectedWallet.sources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
18595
+ return walletSources.find((s) => s.token.symbol === selectedTokenSymbol) ?? null;
17859
18596
  }
17860
18597
  const walletChainName = selectedWallet.chain.name;
17861
- const ranked = selectedWallet.sources.map((source, index) => {
18598
+ const ranked = walletSources.map((source, index) => {
17862
18599
  return { source, index };
17863
18600
  }).sort((a, b) => {
17864
18601
  const priority = compareDepositSourcePriority(
@@ -17906,7 +18643,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17906
18643
  let maxSourceBalance = 0;
17907
18644
  for (const acct of state.accounts) {
17908
18645
  for (const wallet of acct.wallets) {
17909
- for (const source of wallet.sources) {
18646
+ for (const source of wallet.sources ?? []) {
17910
18647
  if (source.balance.available.amount > maxSourceBalance) {
17911
18648
  maxSourceBalance = source.balance.available.amount;
17912
18649
  }
@@ -17916,7 +18653,7 @@ function computeDerivedState(state, depositAmount = 0, priorityContext) {
17916
18653
  let tokenCount = 0;
17917
18654
  for (const acct of state.accounts) {
17918
18655
  for (const wallet of acct.wallets) {
17919
- tokenCount += wallet.sources.filter((s) => s.balance.available.amount > 0).length;
18656
+ tokenCount += (wallet.sources ?? []).filter((s) => s.balance.available.amount > 0).length;
17920
18657
  }
17921
18658
  }
17922
18659
  return {
@@ -18136,6 +18873,31 @@ function usePasskeyHandlers(dispatch, apiBaseUrl) {
18136
18873
  };
18137
18874
  }
18138
18875
 
18876
+ // src/balancesLoad.ts
18877
+ async function fetchBalancesByAccountId(apiBaseUrl, token, credentialId, accounts) {
18878
+ const entries2 = await Promise.all(
18879
+ accounts.map(async (account) => {
18880
+ try {
18881
+ const balances = await fetchAccountBalances(
18882
+ apiBaseUrl,
18883
+ token,
18884
+ account.id,
18885
+ credentialId
18886
+ );
18887
+ return [account.id, balances];
18888
+ } catch (err) {
18889
+ captureException(err);
18890
+ return null;
18891
+ }
18892
+ })
18893
+ );
18894
+ const result = {};
18895
+ for (const entry of entries2) {
18896
+ if (entry) result[entry[0]] = entry[1];
18897
+ }
18898
+ return result;
18899
+ }
18900
+
18139
18901
  // src/transferSourceResolution.ts
18140
18902
  var STANDARD_TRANSFER_SOURCE_UNAVAILABLE_ERROR = "Selected payment source is no longer available. Choose or link an active wallet before depositing.";
18141
18903
  function resolveStandardTransferSource({
@@ -18187,14 +18949,14 @@ function useTransferHandlers(deps) {
18187
18949
  const processingStartedAtRef = useRef(null);
18188
18950
  const pollingTransferIdRef = useRef(null);
18189
18951
  const depositSelectionReloadCountRef = useRef(0);
18190
- const reloadAccounts = useCallback(async () => {
18952
+ const reloadAccounts = useCallback(async (opts) => {
18191
18953
  depositSelectionReloadCountRef.current += 1;
18192
18954
  if (depositSelectionReloadCountRef.current === 1) {
18193
18955
  dispatch({ type: "SET_DEPOSIT_SELECTION_REFRESHING", value: true });
18194
18956
  }
18195
18957
  try {
18196
18958
  const token = await getAccessToken();
18197
- if (!token || !activeCredentialId) return;
18959
+ if (!token || !activeCredentialId) return null;
18198
18960
  const [accts, prov] = await Promise.all([
18199
18961
  fetchAccounts(apiBaseUrl, token, activeCredentialId),
18200
18962
  fetchProviders(apiBaseUrl, token)
@@ -18218,6 +18980,20 @@ function useTransferHandlers(deps) {
18218
18980
  defaults,
18219
18981
  resetSelectedTokenSymbol
18220
18982
  });
18983
+ const balancesPromise = fetchBalancesByAccountId(
18984
+ apiBaseUrl,
18985
+ token,
18986
+ activeCredentialId,
18987
+ accts
18988
+ ).then((balancesByAccountId) => {
18989
+ dispatch({ type: "BALANCES_LOADED", balancesByAccountId });
18990
+ return balancesByAccountId;
18991
+ }).catch(() => null);
18992
+ if (opts?.awaitBalances) {
18993
+ return await balancesPromise;
18994
+ }
18995
+ void balancesPromise;
18996
+ return null;
18221
18997
  } finally {
18222
18998
  depositSelectionReloadCountRef.current = Math.max(
18223
18999
  0,
@@ -18413,7 +19189,7 @@ function useSourceSelectionHandlers(_dispatch, orchestrator, options) {
18413
19189
  const selectSourceChoices = useMemo(() => {
18414
19190
  if (!pendingSelectSourceAction) return [];
18415
19191
  const sourceOptions = pendingSelectSourceAction.metadata?.options ?? [];
18416
- return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd);
19192
+ return buildSelectSourceChoices(sourceOptions, minTransferAmountUsd, true);
18417
19193
  }, [pendingSelectSourceAction, minTransferAmountUsd]);
18418
19194
  const selectSourceUnsupportedChoices = useMemo(() => {
18419
19195
  if (!pendingSelectSourceAction) return [];
@@ -18599,13 +19375,6 @@ function buildReauthorizationSessionOptions(params) {
18599
19375
  return options;
18600
19376
  }
18601
19377
 
18602
- // src/tokenAuthorizationRunOptions.ts
18603
- function buildDesktopTokenAuthorizationOnlyRunOptions(chainName, tokenSymbol) {
18604
- return {
18605
- autoResolveSource: { chainName, tokenSymbol }
18606
- };
18607
- }
18608
-
18609
19378
  // src/oneTapDefaults.ts
18610
19379
  var DEFAULT_ONE_TAP_ALLOWANCE_USD = 1e4;
18611
19380
  async function ensureDefaultOneTapAllowance(apiBaseUrl, token) {
@@ -18626,7 +19395,7 @@ function resolveReauthorizationTarget(input) {
18626
19395
  };
18627
19396
  }
18628
19397
  const wallet = input.account?.wallets.find((candidate) => candidate.id === input.selectedWalletId);
18629
- const source = wallet?.sources.find(
19398
+ const source = wallet?.sources?.find(
18630
19399
  (candidate) => input.selectedTokenSymbol ? candidate.token.symbol === input.selectedTokenSymbol : candidate.token.status === "AUTHORIZED"
18631
19400
  );
18632
19401
  return {
@@ -18637,6 +19406,28 @@ function resolveReauthorizationTarget(input) {
18637
19406
  };
18638
19407
  }
18639
19408
 
19409
+ // src/depositTokenSelection.ts
19410
+ function isSourceAuthorized(balancesByAccountId, target) {
19411
+ const account = target.accountId != null ? balancesByAccountId?.[target.accountId] : void 0;
19412
+ if (!account) return false;
19413
+ const wallet = account.wallets.find((w) => w.id === target.walletId);
19414
+ if (!wallet) return false;
19415
+ const source = wallet.sources.find((s) => s.token.symbol === target.tokenSymbol);
19416
+ return source?.token.status === "AUTHORIZED";
19417
+ }
19418
+ var defaultSettleDelay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
19419
+ async function settleUntilTokenAuthorized(reloadAccounts, target, opts = {}) {
19420
+ const maxAttempts = opts.maxAttempts ?? 4;
19421
+ const backoffMs = opts.backoffMs ?? 400;
19422
+ const delay = opts.delay ?? defaultSettleDelay;
19423
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
19424
+ const balances = await reloadAccounts({ awaitBalances: true });
19425
+ if (isSourceAuthorized(balances, target)) return true;
19426
+ if (attempt < maxAttempts - 1) await delay(backoffMs);
19427
+ }
19428
+ return false;
19429
+ }
19430
+
18640
19431
  // src/walletConnectLinks.ts
18641
19432
  function buildWalletConnectDeeplink(links, walletConnectUri) {
18642
19433
  const baseLink = links?.native ?? links?.universal;
@@ -18768,6 +19559,8 @@ function useProviderHandlers(deps) {
18768
19559
  setupDepositToken,
18769
19560
  tryStartExtensionConnectForReownWallet
18770
19561
  } = deps;
19562
+ const setupDepositTokenRef = useRef(setupDepositToken);
19563
+ setupDepositTokenRef.current = setupDepositToken;
18771
19564
  const checkWalletConnectChainSupport = useMemo(
18772
19565
  () => deps.checkWalletConnectChainSupport ?? ((reownWalletId, chainId) => checkReownWalletChainSupport(BLINK_WC_PROJECT_ID, reownWalletId, chainId)),
18773
19566
  [deps.checkWalletConnectChainSupport]
@@ -18983,7 +19776,7 @@ function useProviderHandlers(deps) {
18983
19776
  }
18984
19777
  dispatch({ type: "BEGIN_LINK_SETTLING" });
18985
19778
  try {
18986
- await reloadAccounts();
19779
+ await reloadAccounts({ awaitBalances: true });
18987
19780
  } finally {
18988
19781
  dispatch({ type: "END_LINK_SETTLING" });
18989
19782
  }
@@ -19110,7 +19903,7 @@ function useProviderHandlers(deps) {
19110
19903
  }
19111
19904
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19112
19905
  try {
19113
- await reloadAccounts();
19906
+ await reloadAccounts({ awaitBalances: true });
19114
19907
  } finally {
19115
19908
  dispatch({ type: "END_LINK_SETTLING" });
19116
19909
  }
@@ -19334,7 +20127,7 @@ function useProviderHandlers(deps) {
19334
20127
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19335
20128
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19336
20129
  try {
19337
- await reloadAccounts();
20130
+ await reloadAccounts({ awaitBalances: true });
19338
20131
  } finally {
19339
20132
  dispatch({ type: "END_LINK_SETTLING" });
19340
20133
  }
@@ -19436,7 +20229,9 @@ function useProviderHandlers(deps) {
19436
20229
  const result = await orchestrator.run(
19437
20230
  sessionId,
19438
20231
  {
19439
- ...buildDesktopTokenAuthorizationOnlyRunOptions(inlineChain.name, tokenSymbol),
20232
+ // No autoResolveSource: pause at SELECT_SOURCE so LinkTokensScreen
20233
+ // renders with the token preselected (SET_SETUP_DEPOSIT_TOKEN pin
20234
+ // above) and the user can set a spending limit before approving.
19440
20235
  // Pin the existing account id so pairing and signing share one
19441
20236
  // runtime (see handleSelectWalletConnectWallet).
19442
20237
  walletConnectRuntimeKey: runtimeAccountId,
@@ -19444,10 +20239,11 @@ function useProviderHandlers(deps) {
19444
20239
  // Require the target token's chain at pairing. A reused session
19445
20240
  // that doesn't approve it re-pairs with the chain required; a
19446
20241
  // wallet that approves the connection without it fails
19447
- // OPEN_PROVIDER with a clear error instead of the auto-resolved
19448
- // SELECT_SOURCE 422 (INVALID_SELECT_SOURCE_SELECTION) — e.g.
19449
- // authorizing USDC on HyperEVM (999, a WC-optional chain) with a
19450
- // wallet that dropped or doesn't support that chain.
20242
+ // OPEN_PROVIDER with a clear error up front instead of stranding the
20243
+ // user on LinkTokensScreen whose only option (SELECT_SOURCE) would
20244
+ // 422 (INVALID_SELECT_SOURCE_SELECTION) on Approve e.g. authorizing
20245
+ // USDC on HyperEVM (999, a WC-optional chain) with a wallet that
20246
+ // dropped or doesn't support that chain.
19451
20247
  walletConnectRequiredChainId: chainId,
19452
20248
  walletConnectRequiredChainName: inlineChain.name,
19453
20249
  onWalletConnectDisplayUri: (uri) => {
@@ -19483,11 +20279,17 @@ function useProviderHandlers(deps) {
19483
20279
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
19484
20280
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19485
20281
  try {
19486
- await reloadAccounts();
20282
+ await reloadAccounts({ awaitBalances: true });
19487
20283
  } finally {
19488
20284
  dispatch({ type: "END_LINK_SETTLING" });
19489
20285
  }
19490
- dispatch({ type: "SELECT_TOKEN", walletId, tokenSymbol, accountId: selectTokenAccountId });
20286
+ const confirmed = setupDepositTokenRef.current;
20287
+ dispatch({
20288
+ type: "SELECT_TOKEN",
20289
+ walletId: confirmed?.walletId ?? walletId,
20290
+ tokenSymbol: confirmed?.symbol ?? tokenSymbol,
20291
+ accountId: selectTokenAccountId
20292
+ });
19491
20293
  dispatch({ type: "DISCARD_SAVED_SELECTION" });
19492
20294
  } catch (err) {
19493
20295
  captureException(err);
@@ -19802,10 +20604,7 @@ function useProviderHandlers(deps) {
19802
20604
  walletDeeplinks: walletDeeplinks ?? null,
19803
20605
  providerId: matchedProvider?.id ?? null
19804
20606
  });
19805
- const result = await orchestrator.run(
19806
- session.id,
19807
- buildDesktopTokenAuthorizationOnlyRunOptions(inlineChain.name, tokenSymbol)
19808
- );
20607
+ const result = await orchestrator.run(session.id);
19809
20608
  if (result.status === "cancelled") {
19810
20609
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19811
20610
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
@@ -19817,14 +20616,25 @@ function useProviderHandlers(deps) {
19817
20616
  }
19818
20617
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
19819
20618
  dispatch({ type: "SET_STANDARD_DESKTOP_INLINE_OPEN_WALLET", value: false });
20619
+ const confirmed = setupDepositTokenRef.current;
20620
+ const settleTarget = {
20621
+ accountId: _accountId,
20622
+ walletId: confirmed?.walletId ?? _walletId,
20623
+ tokenSymbol: confirmed?.symbol ?? tokenSymbol
20624
+ };
19820
20625
  dispatch({ type: "BEGIN_LINK_SETTLING" });
19821
20626
  try {
19822
- await reloadAccounts();
20627
+ await settleUntilTokenAuthorized(reloadAccounts, settleTarget);
20628
+ dispatch({
20629
+ type: "SELECT_TOKEN",
20630
+ walletId: settleTarget.walletId,
20631
+ tokenSymbol: settleTarget.tokenSymbol,
20632
+ accountId: _accountId
20633
+ });
20634
+ dispatch({ type: "DISCARD_SAVED_SELECTION" });
19823
20635
  } finally {
19824
20636
  dispatch({ type: "END_LINK_SETTLING" });
19825
20637
  }
19826
- dispatch({ type: "SELECT_TOKEN", walletId: _walletId, tokenSymbol, accountId: _accountId });
19827
- dispatch({ type: "DISCARD_SAVED_SELECTION" });
19828
20638
  } catch (err) {
19829
20639
  captureException(err);
19830
20640
  dispatch({ type: "DESKTOP_WAIT_CLEARED" });
@@ -20284,6 +21094,15 @@ function useDataLoadEffect(deps) {
20284
21094
  hasActiveCredential: !!state.activeCredentialId,
20285
21095
  loading: loadingDataRef.current
20286
21096
  });
21097
+ appendDebug("warn", "dataLoadEffect run", {
21098
+ loadAction,
21099
+ authenticated,
21100
+ accountsCount: state.accounts.length,
21101
+ hasActiveCredential: !!state.activeCredentialId,
21102
+ loadingRef: loadingDataRef.current,
21103
+ privyAuthenticated: state.privyAuthenticated,
21104
+ activeCredentialId: state.activeCredentialId ? `${state.activeCredentialId.slice(0, 8)}\u2026` : null
21105
+ });
20287
21106
  if (loadAction === "reset") {
20288
21107
  loadingDataRef.current = false;
20289
21108
  dispatch({ type: "DATA_LOAD_END" });
@@ -20301,13 +21120,21 @@ function useDataLoadEffect(deps) {
20301
21120
  const load = async () => {
20302
21121
  dispatch({ type: "DATA_LOAD_START" });
20303
21122
  try {
21123
+ appendDebug("warn", "dataLoad: requesting access token");
20304
21124
  const token = await getAccessTokenRef.current();
21125
+ appendDebug("warn", "dataLoad: token result", { hasToken: !!token, cancelled });
20305
21126
  if (!token) throw new Error("Not authenticated");
20306
21127
  const [prov, accts, chn] = await Promise.all([
20307
21128
  fetchProviders(apiBaseUrl, token),
20308
21129
  fetchAccounts(apiBaseUrl, token, credentialId),
20309
21130
  fetchChains(apiBaseUrl, token)
20310
21131
  ]);
21132
+ appendDebug("warn", "dataLoad: fetch resolved", {
21133
+ cancelled,
21134
+ providers: prov.length,
21135
+ accounts: accts.length,
21136
+ chains: chn.length
21137
+ });
20311
21138
  if (cancelled) return;
20312
21139
  const parsedAmt = depositAmountRef.current != null ? depositAmountRef.current : 0;
20313
21140
  const priorityContext = resolveDepositPriorityContext(accts, chn, destination);
@@ -20333,7 +21160,16 @@ function useDataLoadEffect(deps) {
20333
21160
  resetSelectedTokenSymbol
20334
21161
  });
20335
21162
  if (clearMobile) clearMobileFlowState();
21163
+ void fetchBalancesByAccountId(apiBaseUrl, token, credentialId, accts).then(
21164
+ (balancesByAccountId) => {
21165
+ dispatch({ type: "BALANCES_LOADED", balancesByAccountId });
21166
+ }
21167
+ );
20336
21168
  } catch (err) {
21169
+ appendDebug("error", "dataLoad: threw", {
21170
+ cancelled,
21171
+ message: err instanceof Error ? err.message : String(err)
21172
+ });
20337
21173
  if (!cancelled) {
20338
21174
  captureException(err);
20339
21175
  dispatch({
@@ -20350,6 +21186,7 @@ function useDataLoadEffect(deps) {
20350
21186
  };
20351
21187
  load();
20352
21188
  return () => {
21189
+ appendDebug("warn", "dataLoadEffect cleanup (cancelling in-flight load)");
20353
21190
  cancelled = true;
20354
21191
  loadingDataRef.current = false;
20355
21192
  dispatch({ type: "DATA_LOAD_END" });
@@ -21485,7 +22322,7 @@ function BlinkPaymentInner({
21485
22322
  const handleSetDepositToken = useCallback((symbol, chainName, walletId, tokenAddress, chainId) => {
21486
22323
  dispatch({ type: "SET_SETUP_DEPOSIT_TOKEN", symbol, chainName, walletId, tokenAddress, chainId });
21487
22324
  }, []);
21488
- const handleConfirmSetupDeposit = useCallback(async () => {
22325
+ const handleConfirmSetupDeposit = useCallback(async (limit) => {
21489
22326
  const plan = planConfirmSetupDeposit({
21490
22327
  setupDepositToken: state.setupDepositToken,
21491
22328
  setupSelectedSourceOption,
@@ -21495,6 +22332,19 @@ function BlinkPaymentInner({
21495
22332
  dispatch({ type: "SET_ERROR", error: plan.error });
21496
22333
  return;
21497
22334
  }
22335
+ const token = await effectiveGetAccessToken();
22336
+ if (!token) {
22337
+ dispatch({ type: "SET_ERROR", error: "Your session expired. Please try again." });
22338
+ return;
22339
+ }
22340
+ const config = "unlimited" in limit ? { unlimitedAllowance: true } : { defaultAllowance: limit.usd, unlimitedAllowance: false };
22341
+ try {
22342
+ await updateUserConfig(apiBaseUrl, token, config);
22343
+ } catch {
22344
+ dispatch({ type: "SET_ERROR", error: "Could not save your spending limit. Please try again." });
22345
+ return;
22346
+ }
22347
+ dispatch({ type: "SET_SETUP_SPENDING_LIMIT", limit });
21498
22348
  for (const action of plan.actions) {
21499
22349
  dispatch(action);
21500
22350
  }
@@ -21503,6 +22353,8 @@ function BlinkPaymentInner({
21503
22353
  state.selectedAccountId,
21504
22354
  state.setupDepositToken,
21505
22355
  setupSelectedSourceOption,
22356
+ effectiveGetAccessToken,
22357
+ apiBaseUrl,
21506
22358
  dispatch,
21507
22359
  orchestrator
21508
22360
  ]);
@@ -21563,6 +22415,9 @@ function BlinkPaymentInner({
21563
22415
  }
21564
22416
  })();
21565
22417
  }, [orchestrator]);
22418
+ const handleApprove = useCallback(() => {
22419
+ authExecutor.approveAuthorization();
22420
+ }, [authExecutor]);
21566
22421
  const handleSetPhase = useCallback((phase) => {
21567
22422
  clearScreenErrors();
21568
22423
  if (phase.step === "deposit") {
@@ -21584,6 +22439,7 @@ function BlinkPaymentInner({
21584
22439
  onConfirmSign: transfer.handleConfirmSign,
21585
22440
  onRetryMobileStatus: mobileFlow.handleRetryMobileStatus,
21586
22441
  onRetryAuthorization: handleAuthorizationRetry,
22442
+ onApprove: handleApprove,
21587
22443
  onRetryTransferSigning: transfer.handleRetryTransferSigning,
21588
22444
  onBackFromOpenWallet: handleBackFromOpenWallet,
21589
22445
  onLogout: handleLogout,
@@ -21654,6 +22510,7 @@ function BlinkPaymentInner({
21654
22510
  handleConfirmSetupDeposit,
21655
22511
  handleBackFromSetupDeposit,
21656
22512
  handleAuthorizationRetry,
22513
+ handleApprove,
21657
22514
  disconnectWallets,
21658
22515
  state.desktopWait?.walletForegroundLink
21659
22516
  ]);
@@ -21693,6 +22550,9 @@ function BlinkPaymentInner({
21693
22550
  authExecutorError: authExecutor.error,
21694
22551
  authExecutorExecuting: authExecutor.executing,
21695
22552
  authExecutorCurrentAction: authExecutor.currentAction,
22553
+ authExecutorAwaitingApproval: authExecutor.awaitingApproval,
22554
+ authExecutorApprovalDestinationAddress: authExecutor.approvalDestinationAddress,
22555
+ authApprovalSmartAccountAddress: orchestrator.approvalSmartAccountAddress,
21696
22556
  authExecutorCompleted: orchestrator.orchestratorCompleted,
21697
22557
  transferSigningSigning: transferSigning.signing,
21698
22558
  transferSigningError: transferSigning.error,