@avail-project/widgets 2.0.0 → 2.0.2

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.
Files changed (33) hide show
  1. package/dist/common/hooks/useNexusError.d.ts +1 -1
  2. package/dist/common/utils/token-pricing.d.ts +1 -1
  3. package/dist/common/utils/token-pricing.d.ts.map +1 -1
  4. package/dist/common/utils/token-pricing.js +4 -1
  5. package/dist/nexus/NexusProvider.d.ts +4 -2
  6. package/dist/nexus/NexusProvider.d.ts.map +1 -1
  7. package/dist/nexus/NexusProvider.js +216 -39
  8. package/dist/nexus-widget/components/deposit-idle-form.d.ts +2 -1
  9. package/dist/nexus-widget/components/deposit-idle-form.d.ts.map +1 -1
  10. package/dist/nexus-widget/components/deposit-idle-form.js +26 -10
  11. package/dist/nexus-widget/components/nexus-widget-progress-screen.d.ts.map +1 -1
  12. package/dist/nexus-widget/components/nexus-widget-progress-screen.js +21 -5
  13. package/dist/nexus-widget/components/pay-with-sources.d.ts.map +1 -1
  14. package/dist/nexus-widget/components/pay-with-sources.js +43 -54
  15. package/dist/nexus-widget/components/send-idle-form.d.ts +2 -1
  16. package/dist/nexus-widget/components/send-idle-form.d.ts.map +1 -1
  17. package/dist/nexus-widget/components/send-idle-form.js +26 -10
  18. package/dist/nexus-widget/components/swap-asset-selector.d.ts +1 -1
  19. package/dist/nexus-widget/components/swap-asset-selector.d.ts.map +1 -1
  20. package/dist/nexus-widget/components/swap-asset-selector.js +198 -140
  21. package/dist/nexus-widget/components/swap-idle-form.d.ts +2 -1
  22. package/dist/nexus-widget/components/swap-idle-form.d.ts.map +1 -1
  23. package/dist/nexus-widget/components/swap-idle-form.js +3 -3
  24. package/dist/nexus-widget/components/swap-intent-preview.d.ts +5 -1
  25. package/dist/nexus-widget/components/swap-intent-preview.d.ts.map +1 -1
  26. package/dist/nexus-widget/components/swap-intent-preview.js +50 -35
  27. package/dist/nexus-widget/nexus-widget.d.ts.map +1 -1
  28. package/dist/nexus-widget/nexus-widget.js +1311 -617
  29. package/dist/nexus-widget/types.d.ts +1 -1
  30. package/dist/nexus-widget/types.d.ts.map +1 -1
  31. package/dist/nexus-widget/utils/deposit-source-selection.d.ts.map +1 -1
  32. package/dist/nexus-widget/utils/deposit-source-selection.js +68 -4
  33. package/package.json +5 -5
@@ -38,7 +38,7 @@ const SCIENTIFIC_DECIMAL_REGEX = /^-?(?:\d+\.?\d*|\.\d+)e[+-]?\d+$/i;
38
38
  const QUOTE_REFRESH_INTERVAL_MS = 30000;
39
39
  const EXACT_OUT_INPUT_DEBOUNCE_MS = 1300;
40
40
  const DRAWER_CLOSE_MS = 220;
41
- const BALANCE_REFRESH_AFTER_TERMINAL_MS = 5000;
41
+ const BALANCE_REFRESH_AFTER_TERMINAL_MS = 3000;
42
42
  const MODAL_HEIGHT_TRANSITION_MS = 220;
43
43
  const ROOT_HEIGHT_TRANSITION_MS = 140;
44
44
  const ASSET_SELECTOR_DRAWER_HEIGHT = "90%";
@@ -47,8 +47,25 @@ const CONFIGURED_RECEIVE_SELECTOR_BASE_HEIGHT = 170;
47
47
  const CONFIGURED_RECEIVE_SELECTOR_ROW_HEIGHT = 62;
48
48
  const BASIS_POINTS = 10000;
49
49
  const PREDICTIVE_EXACT_IN_DISCOUNT_BPS = 50;
50
- const PREDICTIVE_EXACT_OUT_BUFFER_BPS = 100;
50
+ const PREDICTIVE_EXACT_OUT_DESTINATION_BUFFER_PCT = 0.1;
51
+ const PREDICTIVE_EXACT_OUT_DESTINATION_BUFFER_MAX_USD = 2;
52
+ const PREDICTIVE_EXACT_OUT_SOURCE_BUFFER_PCT = 0.02;
53
+ const PREDICTIVE_EXACT_OUT_SOURCE_BUFFER_MAX_USD = 1;
51
54
  const PREDICTIVE_QUOTE_DISPLAY_DECIMALS = 8;
55
+ const DEPOSIT_TOKEN_DISPLAY_DECIMALS = 8;
56
+ const ETHEREUM_MAINNET_CHAIN_ID = 1;
57
+ const SDK_EXACT_OUT_STABLE_SYMBOLS = new Set([
58
+ "USDC",
59
+ "USDT",
60
+ "DAI",
61
+ "BUSD",
62
+ "TUSD",
63
+ "FRAX",
64
+ "LUSD",
65
+ "USDD",
66
+ "USDP",
67
+ "GUSD",
68
+ ]);
52
69
  const SWAP_HISTORY_STORAGE_KEY_PREFIX = "nexus-widget-transaction-history-v1";
53
70
  const TIMEOUT_LABEL = "Timed Out";
54
71
  const PROGRESS_EVENT_NAMES = {
@@ -58,20 +75,12 @@ const PROGRESS_EVENT_NAMES = {
58
75
  SWAP_PLAN_PROGRESS: "swap_plan_progress",
59
76
  };
60
77
  const PLAN_FINAL_STATES = new Set(["completed", "confirmed", "submitted"]);
61
- const PLAN_STEP_FUNDS_MOVED_STATES = new Set([
78
+ const PLAN_STEP_FUNDS_MAY_HAVE_MOVED_STATES = new Set([
62
79
  "completed",
63
80
  "confirmed",
64
81
  "submitted",
82
+ "failed",
65
83
  ]);
66
- const waitForNextPaint = () => new Promise((resolve) => {
67
- if (typeof window === "undefined" || !window.requestAnimationFrame) {
68
- resolve();
69
- return;
70
- }
71
- window.requestAnimationFrame(() => {
72
- window.setTimeout(() => resolve(), 0);
73
- });
74
- });
75
84
  const theme = nexusWidgetTheme;
76
85
  const tooltipSurface = theme.colors.surface;
77
86
  const tooltipText = theme.colors.textStrong;
@@ -80,7 +89,32 @@ const uiFont = theme.fonts.sans;
80
89
  const modalHeightTransitionStyle = {
81
90
  interpolateSize: "allow-keywords",
82
91
  };
92
+ const getCappedTokenDisplayDecimals = (decimals) => {
93
+ const parsedDecimals = Number(decimals);
94
+ if (!Number.isFinite(parsedDecimals) || parsedDecimals < 0) {
95
+ return DEPOSIT_TOKEN_DISPLAY_DECIMALS;
96
+ }
97
+ return Math.min(Math.floor(parsedDecimals), DEPOSIT_TOKEN_DISPLAY_DECIMALS);
98
+ };
83
99
  const modalHeightTransition = `height ${MODAL_HEIGHT_TRANSITION_MS}ms ease, max-height ${MODAL_HEIGHT_TRANSITION_MS}ms ease`;
100
+ const normalizeSdkExactOutStableSymbol = (symbol) => (symbol ?? "")
101
+ .trim()
102
+ .toUpperCase()
103
+ .replaceAll("\u20ae", "T")
104
+ .replaceAll(/[^A-Z0-9]/g, "");
105
+ const isSdkExactOutStableSymbol = (symbol) => SDK_EXACT_OUT_STABLE_SYMBOLS.has(normalizeSdkExactOutStableSymbol(symbol));
106
+ const applyPredictiveExactOutBufferUsd = (amount, pct, maxUsd) => Decimal.min(amount.mul(pct), new Decimal(maxUsd));
107
+ const getPredictiveExactOutSourceTargetUsd = (destinationUsdNeedingSources, cachedSourceUsdRatio) => {
108
+ if (destinationUsdNeedingSources.lte(0))
109
+ return new Decimal(0);
110
+ if (cachedSourceUsdRatio?.gt(0)) {
111
+ return destinationUsdNeedingSources.mul(cachedSourceUsdRatio);
112
+ }
113
+ const destinationBuffer = applyPredictiveExactOutBufferUsd(destinationUsdNeedingSources, PREDICTIVE_EXACT_OUT_DESTINATION_BUFFER_PCT, PREDICTIVE_EXACT_OUT_DESTINATION_BUFFER_MAX_USD);
114
+ const destinationBuffered = destinationUsdNeedingSources.plus(destinationBuffer);
115
+ const sourceBuffer = applyPredictiveExactOutBufferUsd(destinationBuffered, PREDICTIVE_EXACT_OUT_SOURCE_BUFFER_PCT, PREDICTIVE_EXACT_OUT_SOURCE_BUFFER_MAX_USD);
116
+ return destinationBuffered.plus(sourceBuffer);
117
+ };
84
118
  const getSwapHistoryStorageKey = (ownerAddress) => `${SWAP_HISTORY_STORAGE_KEY_PREFIX}:${ownerAddress?.toLowerCase() || "anonymous"}`;
85
119
  const getTokenSelectionKey = (token) => {
86
120
  if (!token)
@@ -90,6 +124,46 @@ const getTokenSelectionKey = (token) => {
90
124
  }
91
125
  return `${token.chainId ?? "unknown"}:${token.contractAddress.toLowerCase()}`;
92
126
  };
127
+ const getDisplaySourceTokenKey = (token) => {
128
+ if (!token)
129
+ return "";
130
+ const chainId = token.chainId ?? "unknown";
131
+ const address = token.contractAddress?.toLowerCase();
132
+ return address
133
+ ? `${chainId}:${address}`
134
+ : `${chainId}:symbol:${token.symbol.toUpperCase()}`;
135
+ };
136
+ const sumDecimalStrings = (left, right) => {
137
+ const leftAmount = parseDecimalLoose(left) ?? new Decimal(0);
138
+ const rightAmount = parseDecimalLoose(right) ?? new Decimal(0);
139
+ const sum = leftAmount.plus(rightAmount);
140
+ return sum.gt(0) ? sum.toFixed() : undefined;
141
+ };
142
+ const mergeDisplaySourceTokens = (tokens) => {
143
+ const merged = new Map();
144
+ for (const token of tokens) {
145
+ const key = getDisplaySourceTokenKey(token);
146
+ if (!key)
147
+ continue;
148
+ const existing = merged.get(key);
149
+ if (!existing) {
150
+ merged.set(key, token);
151
+ continue;
152
+ }
153
+ const userAmount = sumDecimalStrings(existing.userAmount || existing.balance, token.userAmount || token.balance);
154
+ const userAmountUsd = sumDecimalStrings(existing.userAmountUsd || existing.balanceInFiat, token.userAmountUsd || token.balanceInFiat);
155
+ merged.set(key, {
156
+ ...existing,
157
+ balance: userAmount ?? existing.balance,
158
+ balanceInFiat: userAmountUsd ?? existing.balanceInFiat,
159
+ chainLogo: existing.chainLogo || token.chainLogo,
160
+ logo: existing.logo || token.logo,
161
+ userAmount: userAmount ?? existing.userAmount,
162
+ userAmountUsd: userAmountUsd ?? existing.userAmountUsd,
163
+ });
164
+ }
165
+ return Array.from(merged.values());
166
+ };
93
167
  const getTokenQuoteKey = (token) => {
94
168
  if (!token)
95
169
  return "";
@@ -115,6 +189,14 @@ const getSourceTokensQuoteKey = (tokens) => tokens
115
189
  token.userAmountMode ?? "",
116
190
  ].join(":"))
117
191
  .join("|");
192
+ const getSourceTokenSyncKey = (token) => [
193
+ getTokenSelectionKey(token),
194
+ token.userAmount ?? "",
195
+ token.userAmountUsd ?? "",
196
+ token.balance ?? "",
197
+ token.balanceInFiat ?? "",
198
+ ].join(":");
199
+ const getSourceTokenListSyncKey = (tokens) => tokens.map(getSourceTokenSyncKey).join("|");
118
200
  const isSameTokenSelection = (a, b) => Boolean(a && b && getTokenSelectionKey(a) === getTokenSelectionKey(b));
119
201
  const getDepositConfigIdentity = (deposit) => {
120
202
  if (!deposit)
@@ -152,6 +234,7 @@ const normalizeConfiguredAddress = (value) => {
152
234
  const text = normalizeConfiguredString(value);
153
235
  return text && isAddress(text) ? text : undefined;
154
236
  };
237
+ const isPositiveGasLimit = (value) => typeof value === "bigint" && value > BigInt(0);
155
238
  const normalizePositiveNumericString = (value) => {
156
239
  const text = normalizeConfiguredString(value);
157
240
  if (!text)
@@ -768,12 +851,22 @@ const formatTokenDisplay = (value) => {
768
851
  const getSwapTokenUsdValue = (token) => parseDecimalLoose(token.userAmountUsd) ??
769
852
  parseDecimalLoose(token.balanceInFiat) ??
770
853
  new Decimal(0);
854
+ const getSwapTokenBalanceUsdValue = (token) => parseDecimalLoose(token.balanceInFiat) ?? new Decimal(0);
771
855
  const sortSwapTokensByUsdDesc = (tokens) => [...tokens].sort((a, b) => {
772
856
  const usdDelta = getSwapTokenUsdValue(b).cmp(getSwapTokenUsdValue(a));
773
857
  if (usdDelta !== 0)
774
858
  return usdDelta;
775
859
  return (a.symbol ?? "").localeCompare(b.symbol ?? "");
776
860
  });
861
+ const sortDisplaySourcesByBalanceUsdDesc = (tokens) => [...tokens].sort((a, b) => {
862
+ const balanceUsdDelta = getSwapTokenBalanceUsdValue(b).cmp(getSwapTokenBalanceUsdValue(a));
863
+ if (balanceUsdDelta !== 0)
864
+ return balanceUsdDelta;
865
+ const spendUsdDelta = getSwapTokenUsdValue(b).cmp(getSwapTokenUsdValue(a));
866
+ if (spendUsdDelta !== 0)
867
+ return spendUsdDelta;
868
+ return `${a.symbol ?? ""} ${a.chainName ?? ""}`.localeCompare(`${b.symbol ?? ""} ${b.chainName ?? ""}`);
869
+ });
777
870
  const getIntentSourceUsdValue = (source) => parseDecimalLoose(source.value) ?? new Decimal(0);
778
871
  const sortIntentSourcesByUsdDesc = (sources) => [...sources].sort((a, b) => {
779
872
  const usdDelta = getIntentSourceUsdValue(b).cmp(getIntentSourceUsdValue(a));
@@ -1011,7 +1104,9 @@ const getDisplayDestinationSourceRow = (entry) => {
1011
1104
  : undefined;
1012
1105
  return {
1013
1106
  key: `destination-balance-${entry.toToken.chainId}-${entry.toToken.contractAddress}`,
1014
- tokenLogo: entry.toToken.logo,
1107
+ chainId: entry.toToken.chainId,
1108
+ contractAddress: entry.toToken.contractAddress,
1109
+ tokenLogo: entry.intentData?.destination.token.logo || entry.toToken.logo,
1015
1110
  chainLogo: entry.toToken.chainLogo,
1016
1111
  symbol: entry.toToken.symbol,
1017
1112
  chainName: getShortChainName(entry.toToken.chainId, entry.toToken.chainName),
@@ -1023,7 +1118,42 @@ const getDisplayDestinationSourceRow = (entry) => {
1023
1118
  : entry.toToken.balanceInFiat,
1024
1119
  };
1025
1120
  };
1121
+ const getHistorySourceRowMergeKey = (row) => {
1122
+ const chainKey = row.chainId ?? row.chainName;
1123
+ const address = row.contractAddress?.toLowerCase();
1124
+ return address
1125
+ ? `${chainKey}:${address}`
1126
+ : `${chainKey}:symbol:${row.symbol.toUpperCase()}`;
1127
+ };
1128
+ const mergeHistorySourceRows = (rows) => {
1129
+ const merged = new Map();
1130
+ for (const row of rows) {
1131
+ const key = getHistorySourceRowMergeKey(row);
1132
+ const existing = merged.get(key);
1133
+ if (!existing) {
1134
+ merged.set(key, row);
1135
+ continue;
1136
+ }
1137
+ merged.set(key, {
1138
+ ...existing,
1139
+ amount: sumDecimalStrings(existing.amount, row.amount) ?? existing.amount,
1140
+ chainLogo: existing.chainLogo || row.chainLogo,
1141
+ key: existing.key,
1142
+ tokenLogo: existing.tokenLogo || row.tokenLogo,
1143
+ value: sumDecimalStrings(String(existing.value ?? ""), String(row.value ?? "")),
1144
+ });
1145
+ }
1146
+ return Array.from(merged.values());
1147
+ };
1026
1148
  const getProgressStepType = (step) => String(step?.type ?? step?.typeID ?? "").toUpperCase();
1149
+ const isSourceMovementProgressStep = (type, rawStepType) => rawStepType === "source_swap" ||
1150
+ rawStepType === "eoa_to_ephemeral_transfer" ||
1151
+ rawStepType === "bridge_deposit" ||
1152
+ rawStepType === "vault_deposit" ||
1153
+ type.includes("SOURCE_SWAP") ||
1154
+ type.includes("EOA_TO_EPHEMERAL_TRANSFER") ||
1155
+ type.includes("BRIDGE_DEPOSIT") ||
1156
+ type.includes("VAULT_DEPOSIT");
1027
1157
  const isBridgeRefundStepType = (type) => type.includes("BRIDGE_INTENT_SUBMISSION") || type.includes("BRIDGE_DEPOSIT");
1028
1158
  const isSwapSkippedStepType = (type) => type.includes("SWAP_SKIPPED");
1029
1159
  const isAutoRefundAvailableProgressEvent = (event) => event?.name === PROGRESS_EVENT_NAMES.SWAP_PLAN_PROGRESS &&
@@ -1160,6 +1290,12 @@ const normalizeSdkIntentString = (value) => {
1160
1290
  return String(value);
1161
1291
  };
1162
1292
  const normalizeSdkIntentAmount = (value, fallback = "0") => normalizeSdkIntentString(value) ?? fallback;
1293
+ const getLogoFromMetadata = (metadata) => metadata?.logo ??
1294
+ metadata?.logoURI ??
1295
+ metadata?.logoUri ??
1296
+ metadata?.logoUrl ??
1297
+ metadata?.icon ??
1298
+ "";
1163
1299
  const normalizeSdkIntentChain = (chain) => {
1164
1300
  const id = Number(chain?.id ?? chain?.chainId);
1165
1301
  if (!Number.isFinite(id))
@@ -1167,7 +1303,7 @@ const normalizeSdkIntentChain = (chain) => {
1167
1303
  const chainMeta = CHAIN_METADATA[id];
1168
1304
  return {
1169
1305
  id,
1170
- logo: chain?.logo ?? chainMeta?.logo ?? "",
1306
+ logo: getLogoFromMetadata(chain) || chainMeta?.logo || "",
1171
1307
  name: chain?.name ?? chainMeta?.name ?? "",
1172
1308
  };
1173
1309
  };
@@ -1180,6 +1316,7 @@ const normalizeSdkIntentToken = (token, chainId) => {
1180
1316
  token?.tokenAddress ??
1181
1317
  zeroAddress,
1182
1318
  decimals: Number.isFinite(decimals) ? decimals : 18,
1319
+ logo: getLogoFromMetadata(token) || undefined,
1183
1320
  symbol: token?.symbol ?? token?.tokenSymbol ?? "",
1184
1321
  };
1185
1322
  };
@@ -1250,11 +1387,14 @@ const normalizeSwapAndExecuteRequirementIntent = (intent) => {
1250
1387
  };
1251
1388
  const normalizeRenderableSwapIntentData = (rawIntent, bridgeProvider) => {
1252
1389
  const direct = normalizeSwapIntentData(rawIntent);
1253
- const nestedSwap = direct ? null : normalizeSwapIntentData(rawIntent?.swap);
1254
- const requirement = direct || nestedSwap
1390
+ const normalizedIntent = direct
1391
+ ? null
1392
+ : normalizeSwapIntentData(rawIntent?.normalizedIntent);
1393
+ const nestedSwap = direct || normalizedIntent ? null : normalizeSwapIntentData(rawIntent?.swap);
1394
+ const requirement = direct || normalizedIntent || nestedSwap
1255
1395
  ? null
1256
1396
  : normalizeSwapAndExecuteRequirementIntent(rawIntent);
1257
- const normalized = direct ?? nestedSwap ?? requirement;
1397
+ const normalized = direct ?? normalizedIntent ?? nestedSwap ?? requirement;
1258
1398
  if (!normalized)
1259
1399
  return null;
1260
1400
  return bridgeProvider === undefined
@@ -1341,7 +1481,9 @@ const getSourceRows = (entry) => {
1341
1481
  token.symbol === source.token.symbol));
1342
1482
  return {
1343
1483
  key: `${source.chain.id}-${source.token.contractAddress}-${index}`,
1344
- tokenLogo: fallback?.logo,
1484
+ chainId: source.chain.id,
1485
+ contractAddress: source.token.contractAddress,
1486
+ tokenLogo: source.token.logo || fallback?.logo,
1345
1487
  chainLogo: source.chain.logo || fallback?.chainLogo,
1346
1488
  symbol: source.token.symbol,
1347
1489
  chainName: getShortChainName(source.chain.id, source.chain.name),
@@ -1349,12 +1491,14 @@ const getSourceRows = (entry) => {
1349
1491
  value: source.value,
1350
1492
  };
1351
1493
  });
1352
- return displayDestinationSourceRow
1494
+ return mergeHistorySourceRows(displayDestinationSourceRow
1353
1495
  ? [displayDestinationSourceRow, ...sourceRows]
1354
- : sourceRows;
1496
+ : sourceRows);
1355
1497
  }
1356
1498
  const fallbackRows = entry.fromTokens.map((token, index) => ({
1357
1499
  key: `${token.chainId}-${token.contractAddress}-${index}`,
1500
+ chainId: token.chainId,
1501
+ contractAddress: token.contractAddress,
1358
1502
  tokenLogo: token.logo,
1359
1503
  chainLogo: token.chainLogo,
1360
1504
  symbol: token.symbol,
@@ -1362,9 +1506,9 @@ const getSourceRows = (entry) => {
1362
1506
  amount: token.userAmount || "0",
1363
1507
  value: token.balanceInFiat,
1364
1508
  }));
1365
- return displayDestinationSourceRow
1509
+ return mergeHistorySourceRows(displayDestinationSourceRow
1366
1510
  ? [displayDestinationSourceRow, ...fallbackRows]
1367
- : fallbackRows;
1511
+ : fallbackRows);
1368
1512
  };
1369
1513
  function SourceRowsList({ entry, maxHeight = 236, borderTopFirst = true, scrollAfterRows = 4, }) {
1370
1514
  const rows = getSourceRows(entry);
@@ -1437,6 +1581,10 @@ function SwapReceiptPanel({ entry, onDone, }) {
1437
1581
  const chainName = getShortChainName(destination?.chain.id ?? entry.toToken?.chainId, destination?.chain.name || entry.toToken?.chainName || "");
1438
1582
  const depositVenue = entry.opportunity?.title || entry.opportunity?.protocol || chainName;
1439
1583
  const amount = destination?.amount || "";
1584
+ const destinationLogo = destination?.token.logo ||
1585
+ entry.toToken?.logo ||
1586
+ entry.opportunity?.tokenLogo ||
1587
+ (isDeposit ? entry.opportunity?.logo : undefined);
1440
1588
  const requestedExactOutAmount = (isDeposit || isSend) && entry.requestedToAmount
1441
1589
  ? entry.requestedToAmount
1442
1590
  : undefined;
@@ -1489,9 +1637,7 @@ function SwapReceiptPanel({ entry, onDone, }) {
1489
1637
  display: "inline-flex",
1490
1638
  marginBottom: "10px",
1491
1639
  position: "relative",
1492
- }, children: [_jsx(MiniLogo, { fontSize: 17, label: tokenSymbol, size: 45, src: isDeposit
1493
- ? entry.opportunity?.logo || entry.toToken?.logo
1494
- : entry.toToken?.logo }), _jsx("div", { style: {
1640
+ }, children: [_jsx(MiniLogo, { fontSize: 17, label: tokenSymbol, size: 45, src: destinationLogo }), _jsx("div", { style: {
1495
1641
  alignItems: "center",
1496
1642
  background: isFailed
1497
1643
  ? "#E92C2C"
@@ -1719,7 +1865,10 @@ function SwapHistoryPanel({ entries, now, }) {
1719
1865
  width: "100%",
1720
1866
  }, children: sortedEntries.map((entry) => {
1721
1867
  const destination = entry.intentData?.destination;
1722
- const destinationLogo = entry.toToken?.logo;
1868
+ const destinationLogo = destination?.token.logo ||
1869
+ entry.toToken?.logo ||
1870
+ entry.opportunity?.tokenLogo ||
1871
+ "";
1723
1872
  const destinationChainLogo = destination?.chain.logo || entry.toToken?.chainLogo || "";
1724
1873
  const destinationChainName = getShortChainName(destination?.chain.id ?? entry.toToken?.chainId, destination?.chain.name || entry.toToken?.chainName || "");
1725
1874
  const destinationSymbol = destination?.token.symbol || entry.toToken?.symbol || "";
@@ -1865,10 +2014,11 @@ export function NexusWidget(props) {
1865
2014
  }, type: "button", children: "Reload Page" })] }), children: _jsx(NexusWidgetInner, { ...props }) }));
1866
2015
  }
1867
2016
  function NexusWidgetInner({ config: rawConfig, embed = true, className, connectedAddress, open: controlledOpen, onOpenChange, defaultOpen = false, onComplete, onConnectClick, onStart, onError, onReceiveAssetChange, onClose, onConnectWallet, }) {
1868
- const { nexusSDK, bridgableBalance, swapBalance, getFiatValue, resolveTokenUsdRate, swapSupportedChainsAndTokens, supportedChainsAndTokens, fetchSwapBalance, handleInit, swapIntent: providerSwapIntent, network, loading: nexusLoading, } = useNexus();
2017
+ const { nexusSDK, bridgableBalance, swapBalance, swapBalanceLoading, getFiatValue, resolveTokenUsdRate, swapSupportedChainsAndTokens, supportedChainsAndTokens, fetchSwapBalance, handleInit, swapIntent: providerSwapIntent, network, loading: nexusLoading, } = useNexus();
1869
2018
  const appConfig = useMemo(() => ({
1870
2019
  nexusNetwork: network ?? "mainnet",
1871
2020
  }), [network]);
2021
+ const isSwapBalancePending = Boolean(nexusSDK) && (swapBalanceLoading || swapBalance === null);
1872
2022
  const normalizedWidgetConfig = useMemo(() => normalizeNexusWidgetConfig(rawConfig), [rawConfig]);
1873
2023
  const config = normalizedWidgetConfig.config;
1874
2024
  const activeMode = normalizedWidgetConfig.activeMode;
@@ -1878,7 +2028,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
1878
2028
  const isConfiguredAmountFixed = normalizedWidgetConfig.isAmountFixed;
1879
2029
  const isConfiguredRecipientLocked = normalizedWidgetConfig.isRecipientLocked;
1880
2030
  const primaryColor = normalizeNexusWidgetPrimaryColor(appearanceConfig?.primaryColor);
1881
- const primaryButtonBackground = primaryColor ?? nexusWidgetTheme.colors.text;
2031
+ const primaryButtonBackground = primaryColor ?? NEXUS_WIDGET_DEFAULT_PRIMARY_COLOR;
1882
2032
  const primaryButtonForeground = getReadableTextColor(primaryButtonBackground);
1883
2033
  const theme = useMemo(() => ({
1884
2034
  ...nexusWidgetTheme,
@@ -1920,7 +2070,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
1920
2070
  preloadReceiveTokens();
1921
2071
  }
1922
2072
  }, [nexusSDK]);
1923
- const { connector, status: walletStatus } = useAccount();
2073
+ const { address: accountAddress, connector, status: walletStatus, } = useAccount();
1924
2074
  const { connectors, connectAsync, isPending: isWalletConnectPending, } = useConnect();
1925
2075
  const { data: walletClient } = useWalletClient();
1926
2076
  const { data: connectorClient } = useConnectorClient();
@@ -1930,12 +2080,66 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
1930
2080
  isAddress(connectedAddress) &&
1931
2081
  connectedAddress.toLowerCase() !== zeroAddress
1932
2082
  ? connectedAddress
1933
- : walletClientAddress &&
1934
- isAddress(walletClientAddress) &&
1935
- walletClientAddress.toLowerCase() !== zeroAddress
1936
- ? walletClientAddress
2083
+ : accountAddress &&
2084
+ isAddress(accountAddress) &&
2085
+ accountAddress.toLowerCase() !== zeroAddress
2086
+ ? accountAddress
2087
+ : walletClientAddress &&
2088
+ isAddress(walletClientAddress) &&
2089
+ walletClientAddress.toLowerCase() !== zeroAddress
2090
+ ? walletClientAddress
2091
+ : undefined;
2092
+ const getEffectiveWalletProvider = useCallback(async (activeConnector = connector) => {
2093
+ let connectorProvider;
2094
+ try {
2095
+ connectorProvider = await activeConnector?.getProvider();
2096
+ }
2097
+ catch {
2098
+ connectorProvider = undefined;
2099
+ }
2100
+ const connectorClientProvider = connectorClient
2101
+ ? {
2102
+ request: (args) => connectorClient.request(args),
2103
+ }
2104
+ : undefined;
2105
+ const walletClientProvider = walletClient
2106
+ ? {
2107
+ request: (args) => walletClient.request(args),
2108
+ }
2109
+ : undefined;
2110
+ const windowProvider = typeof window !== "undefined"
2111
+ ? window.ethereum
1937
2112
  : undefined;
2113
+ const effectiveProvider = connectorProvider &&
2114
+ typeof connectorProvider.request === "function"
2115
+ ? connectorProvider
2116
+ : (connectorClientProvider ?? walletClientProvider ?? windowProvider);
2117
+ return effectiveProvider &&
2118
+ typeof effectiveProvider.request === "function"
2119
+ ? effectiveProvider
2120
+ : undefined;
2121
+ }, [connector, connectorClient, walletClient]);
1938
2122
  const historyStorageKey = getSwapHistoryStorageKey(ownerAddress);
2123
+ useEffect(() => {
2124
+ if (walletStatus !== "connected" || !ownerAddress) {
2125
+ return;
2126
+ }
2127
+ let cancelled = false;
2128
+ void (async () => {
2129
+ const effectiveProvider = await getEffectiveWalletProvider();
2130
+ if (cancelled || !effectiveProvider) {
2131
+ return;
2132
+ }
2133
+ await handleInit(effectiveProvider, ownerAddress);
2134
+ })().catch((error) => {
2135
+ if (!cancelled) {
2136
+ console.error("Unable to initialize Nexus for wallet address:", error);
2137
+ }
2138
+ });
2139
+ return () => {
2140
+ cancelled = true;
2141
+ };
2142
+ }, [getEffectiveWalletProvider, handleInit, ownerAddress, walletStatus]);
1939
2143
  // Global form state
1940
2144
  const [amount, setAmount] = useState("");
1941
2145
  const [recipientAddress, setRecipientAddress] = useState("");
@@ -1961,7 +2165,6 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
1961
2165
  : hasCustomSwapRecipient
1962
2166
  ? recipientAddress
1963
2167
  : undefined;
1964
- const previousDefaultRecipientRef = useRef(defaultRecipientAddress);
1965
2168
  // Swap-specific
1966
2169
  const [swapType, setSwapType] = useState("exactIn");
1967
2170
  const [swapStep, setSwapStep] = useState("idle");
@@ -1978,6 +2181,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
1978
2181
  const [fromTokens, setFromTokens] = useState([]);
1979
2182
  const [sourceSelectionTouched, setSourceSelectionTouched] = useState(false);
1980
2183
  const [sourceSelectionRevision, setSourceSelectionRevision] = useState(0);
2184
+ const [sourceSelectionRequiredUsdDisplay, setSourceSelectionRequiredUsdDisplay,] = useState(undefined);
2185
+ const [sourcePickerDraftTokens, setSourcePickerDraftTokens] = useState(null);
2186
+ const sourcePickerDraftTokensRef = useRef(null);
2187
+ const sourcePickerDraftDepositFilterRef = useRef("all");
2188
+ const sourcePickerDraftTouchedRef = useRef(false);
2189
+ const sourcePickerDraftModeRef = useRef("all");
1981
2190
  const [exactOutQuoteSourceMode, setExactOutQuoteSourceMode] = useState("all");
1982
2191
  const exactOutQuoteSourceModeRef = useRef("all");
1983
2192
  const [toToken, setToToken] = useState(undefined);
@@ -2060,17 +2269,14 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2060
2269
  setExactOutQuoteSourceModeValue("all");
2061
2270
  }, [activeMode, setExactOutQuoteSourceModeValue]);
2062
2271
  useEffect(() => {
2063
- const previousDefault = previousDefaultRecipientRef.current;
2064
- previousDefaultRecipientRef.current = defaultRecipientAddress;
2065
- if (activeMode !== "swap" || !defaultRecipientAddress)
2272
+ if (activeMode !== "swap")
2066
2273
  return;
2067
2274
  setRecipientAddress((current) => {
2068
- if (!current ||
2069
- (previousDefault &&
2070
- current.toLowerCase() === previousDefault.toLowerCase())) {
2071
- return defaultRecipientAddress;
2072
- }
2073
- return current;
2275
+ if (!defaultRecipientAddress)
2276
+ return current ? "" : current;
2277
+ return current.toLowerCase() === defaultRecipientAddress.toLowerCase()
2278
+ ? current
2279
+ : defaultRecipientAddress;
2074
2280
  });
2075
2281
  }, [activeMode, defaultRecipientAddress]);
2076
2282
  const { steps, seed, onStepsList, onStepComplete, reset: resetSteps, } = useTransactionSteps();
@@ -2095,6 +2301,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2095
2301
  const widgetOpenedFiredRef = useRef(false);
2096
2302
  const reachedTerminalRef = useRef(false);
2097
2303
  const lastIntentSourceTokensRef = useRef([]);
2304
+ const lastAutoIntentSourceTokensRef = useRef([]);
2098
2305
  const immediateQuoteAfterSourceEditRef = useRef(false);
2099
2306
  const amountEnteredLastValueRef = useRef("");
2100
2307
  const lastInputMethodRef = useRef("typed");
@@ -2123,11 +2330,14 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2123
2330
  const [quoteRefreshing, setQuoteRefreshing] = useState(false);
2124
2331
  const [receiveMaxCalculating, setReceiveMaxCalculating] = useState(false);
2125
2332
  const [maxCalculationPercent, setMaxCalculationPercent] = useState(null);
2126
- const maxSwapQuoteCacheRef = useRef({});
2127
2333
  const intentDestinationUsdRateCacheRef = useRef({});
2128
2334
  const intentSymbolUsdRateCacheRef = useRef({});
2129
2335
  const predictiveQuoteCacheRef = useRef({});
2130
2336
  const predictiveQuoteRunRef = useRef(0);
2337
+ const latestIntentFetchRunIdRef = useRef(0);
2338
+ const intentFetchTimingRef = useRef(null);
2339
+ const intentUiRenderLoggedRunIdRef = useRef(null);
2340
+ const [intentFetchTiming, setIntentFetchTiming] = useState(null);
2131
2341
  const [predictiveQuote, setPredictiveQuote] = useState(null);
2132
2342
  const maxPercentRunRef = useRef(0);
2133
2343
  const [previewQuoteRefreshing, setPreviewQuoteRefreshing] = useState(false);
@@ -2154,6 +2364,68 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2154
2364
  const activeQuoteInputKeyRef = useRef("");
2155
2365
  // Ref to store swap intent hook allow/deny callbacks
2156
2366
  const swapIntentRef = useRef(null);
2367
+ const startIntentFetchTiming = useCallback(({ background, operation, quoteInputKey, runId, }) => {
2368
+ const startTimeMs = Date.now();
2369
+ const timing = {
2370
+ background,
2371
+ operation,
2372
+ quoteInputKey,
2373
+ runId,
2374
+ startTimeMs,
2375
+ walletAddress: ownerAddress ?? null,
2376
+ };
2377
+ latestIntentFetchRunIdRef.current = runId;
2378
+ intentFetchTimingRef.current = timing;
2379
+ intentUiRenderLoggedRunIdRef.current = null;
2380
+ setIntentFetchTiming(timing);
2381
+ console.log("[NexusWidget] intent fetch start", {
2382
+ runId,
2383
+ walletAddress: timing.walletAddress,
2384
+ operation,
2385
+ quoteInputKey,
2386
+ background,
2387
+ dateNow: startTimeMs,
2388
+ date: new Date(startTimeMs),
2389
+ });
2390
+ return timing;
2391
+ }, [ownerAddress]);
2392
+ const finishIntentFetchTiming = useCallback((runId, status = "resolved") => {
2393
+ const timing = intentFetchTimingRef.current;
2394
+ if (!timing || timing.runId !== runId)
2395
+ return;
2396
+ if (timing.endTimeMs !== undefined)
2397
+ return;
2398
+ const endTimeMs = Date.now();
2399
+ if (latestIntentFetchRunIdRef.current !== runId) {
2400
+ return;
2401
+ }
2402
+ const fetchDurationMs = endTimeMs - timing.startTimeMs;
2403
+ const completedTiming = {
2404
+ ...timing,
2405
+ endTimeMs,
2406
+ fetchDurationMs,
2407
+ };
2408
+ intentFetchTimingRef.current = completedTiming;
2409
+ setIntentFetchTiming(completedTiming);
2410
+ console.log("[NexusWidget] intent fetch end", {
2411
+ runId,
2412
+ walletAddress: timing.walletAddress,
2413
+ operation: timing.operation,
2414
+ quoteInputKey: timing.quoteInputKey,
2415
+ status,
2416
+ dateNow: endTimeMs,
2417
+ date: new Date(endTimeMs),
2418
+ });
2419
+ console.log("[NexusWidget] intent fetch total", {
2420
+ runId,
2421
+ walletAddress: timing.walletAddress,
2422
+ operation: timing.operation,
2423
+ quoteInputKey: timing.quoteInputKey,
2424
+ totalTimeSeconds: fetchDurationMs / 1000,
2425
+ elapsedMs: fetchDurationMs,
2426
+ dateNow: endTimeMs,
2427
+ });
2428
+ }, []);
2157
2429
  useEffect(() => {
2158
2430
  swapStepRef.current = swapStep;
2159
2431
  }, [swapStep]);
@@ -2297,6 +2569,112 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2297
2569
  return () => window.clearInterval(timer);
2298
2570
  }, [swapStep]);
2299
2571
  const normalizeAddress = (value) => (value ?? "").toLowerCase();
2572
+ const getTokenLookupAddress = (value) => {
2573
+ if (!value)
2574
+ return "";
2575
+ return isNativeTokenAddress(value) ? zeroAddress : value.toLowerCase();
2576
+ };
2577
+ const findBalanceTokenLogo = (chainId, contractAddress, symbol) => {
2578
+ if (!chainId)
2579
+ return "";
2580
+ const lookupAddress = getTokenLookupAddress(contractAddress);
2581
+ const lookupSymbol = symbol?.toUpperCase();
2582
+ let symbolMatchLogo = "";
2583
+ for (const asset of swapBalance ?? []) {
2584
+ for (const breakdown of asset.breakdown ?? []) {
2585
+ if (breakdown.chain?.id !== chainId)
2586
+ continue;
2587
+ const breakdownAddress = getTokenLookupAddress(breakdown.contractAddress);
2588
+ const addressMatches = lookupAddress && breakdownAddress === lookupAddress;
2589
+ const symbolMatches = lookupSymbol &&
2590
+ [breakdown.symbol, asset.symbol]
2591
+ .filter(Boolean)
2592
+ .some((candidate) => candidate.toUpperCase() === lookupSymbol);
2593
+ const logo = getLogoFromMetadata(breakdown) || getLogoFromMetadata(asset);
2594
+ if (addressMatches && logo)
2595
+ return logo;
2596
+ if (!symbolMatchLogo && symbolMatches && logo) {
2597
+ symbolMatchLogo = logo;
2598
+ }
2599
+ }
2600
+ }
2601
+ return symbolMatchLogo;
2602
+ };
2603
+ const findSupportedTokenLogo = (chains, chainId, contractAddress, symbol) => {
2604
+ if (!chainId)
2605
+ return "";
2606
+ const chain = chains?.find((item) => Number(item?.id ?? item?.chainId) === chainId);
2607
+ const tokens = chain?.tokens ?? chain?.assets ?? [];
2608
+ const lookupAddress = getTokenLookupAddress(contractAddress);
2609
+ const lookupSymbol = symbol?.toUpperCase();
2610
+ const addressMatch = lookupAddress &&
2611
+ tokens.find((token) => {
2612
+ const tokenAddress = getTokenLookupAddress(token?.contractAddress ?? token?.address ?? token?.tokenAddress);
2613
+ return tokenAddress === lookupAddress;
2614
+ });
2615
+ const symbolMatch = lookupSymbol &&
2616
+ tokens.find((token) => {
2617
+ const tokenSymbol = (token?.symbol ?? token?.tokenSymbol ?? "")
2618
+ .toString()
2619
+ .toUpperCase();
2620
+ return tokenSymbol === lookupSymbol;
2621
+ });
2622
+ return getLogoFromMetadata(addressMatch) || getLogoFromMetadata(symbolMatch);
2623
+ };
2624
+ const resolveTokenLogo = (token, chainId) => {
2625
+ const explicitLogo = getLogoFromMetadata(token);
2626
+ if (explicitLogo)
2627
+ return explicitLogo;
2628
+ const contractAddress = token?.contractAddress ?? token?.address ?? token?.tokenAddress;
2629
+ const symbol = token?.symbol ?? token?.tokenSymbol;
2630
+ const citreaToken = findCitreaReceiveToken({
2631
+ address: contractAddress,
2632
+ chainId,
2633
+ symbol,
2634
+ });
2635
+ const tokenMeta = symbol
2636
+ ? TOKEN_METADATA[symbol]
2637
+ : undefined;
2638
+ return (findBalanceTokenLogo(chainId, contractAddress, symbol) ||
2639
+ findSupportedTokenLogo(swapSupportedChainsAndTokens, chainId, contractAddress, symbol) ||
2640
+ findSupportedTokenLogo(supportedChainsAndTokens, chainId, contractAddress, symbol) ||
2641
+ citreaToken?.logo ||
2642
+ tokenMeta?.logo ||
2643
+ (contractAddress && isNativeTokenAddress(contractAddress)
2644
+ ? CHAIN_METADATA[chainId ?? 0]?.logo
2645
+ : "") ||
2646
+ "");
2647
+ };
2648
+ const enrichIntentToken = (token, chainId) => {
2649
+ const logo = resolveTokenLogo(token, chainId);
2650
+ return logo && logo !== token.logo ? { ...token, logo } : token;
2651
+ };
2652
+ const enrichTokenOptionLogo = (token) => {
2653
+ if (!token)
2654
+ return undefined;
2655
+ const chainMeta = token.chainId ? CHAIN_METADATA[token.chainId] : undefined;
2656
+ const logo = resolveTokenLogo(token, token.chainId);
2657
+ const next = {
2658
+ ...token,
2659
+ chainLogo: token.chainLogo || chainMeta?.logo,
2660
+ logo: token.logo || logo,
2661
+ };
2662
+ if (next.chainLogo === token.chainLogo && next.logo === token.logo) {
2663
+ return token;
2664
+ }
2665
+ return next;
2666
+ };
2667
+ const enrichSwapIntentTokenMetadata = (intent) => ({
2668
+ ...intent,
2669
+ destination: {
2670
+ ...intent.destination,
2671
+ token: enrichIntentToken(intent.destination.token, intent.destination.chain.id),
2672
+ },
2673
+ sources: (intent.sources ?? []).map((source) => ({
2674
+ ...source,
2675
+ token: enrichIntentToken(source.token, source.chain.id),
2676
+ })),
2677
+ });
2300
2678
  const buildIntentSourceToken = (source) => {
2301
2679
  let matchedAsset;
2302
2680
  let matchedBreakdown;
@@ -2326,7 +2704,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2326
2704
  const sourceDecimals = isNativeSource && nativeCurrency?.decimals !== undefined
2327
2705
  ? nativeCurrency.decimals
2328
2706
  : source.token.decimals;
2329
- const sourceLogo = matchedAsset?.logo ?? (isNativeSource ? chainMeta?.logo : "");
2707
+ const sourceLogo = resolveTokenLogo(source.token, source.chain.id);
2330
2708
  return {
2331
2709
  contractAddress: source.token.contractAddress,
2332
2710
  symbol: sourceSymbol,
@@ -2412,8 +2790,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2412
2790
  return undefined;
2413
2791
  }
2414
2792
  };
2415
- const minimumSourceUsd = new Decimal(0);
2416
- const hasMinimumSourceUsdBalance = (token) => (parseFiatNumber(token.balanceInFiat) ?? new Decimal(0)).gte(minimumSourceUsd);
2793
+ const minimumSourceUsd = new Decimal(1);
2794
+ const hasMinimumSourceUsdValue = (value) => (parseFiatNumber(value) ?? new Decimal(0)).gte(minimumSourceUsd);
2795
+ const hasMinimumSourceUsdBalance = (token) => hasMinimumSourceUsdValue(token.balanceInFiat);
2417
2796
  const filterMinimumSourceUsdTokens = (tokens) => tokens.filter(hasMinimumSourceUsdBalance);
2418
2797
  const getTokenUsdRateCacheKeyFromParts = (chainId, contractAddress, symbol) => {
2419
2798
  if (!chainId || !symbol)
@@ -2485,6 +2864,10 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2485
2864
  if (tokenBalance.gt(0) && fiatBalance.gt(0)) {
2486
2865
  return fiatBalance.div(tokenBalance);
2487
2866
  }
2867
+ const priceUsd = parseFiatNumber(token.priceUSD);
2868
+ if (priceUsd && priceUsd.gt(0)) {
2869
+ return priceUsd;
2870
+ }
2488
2871
  const fallbackRate = getFiatValue(1, token.symbol);
2489
2872
  if (Number.isFinite(fallbackRate) && fallbackRate > 0) {
2490
2873
  return new Decimal(fallbackRate);
@@ -2502,42 +2885,80 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2502
2885
  const rate = parseFiatNumber(cached?.rate);
2503
2886
  return rate && rate.gt(0) ? rate : new Decimal(0);
2504
2887
  };
2505
- const getTotalBalancePercentUsdAmount = (pct) => getSwapBalanceTotalUsd().mul(pct).div(100);
2506
- const formatTokenAmountFromUsd = (usdAmount, token) => {
2507
- const rate = getUsdRateForSymbol(token.symbol);
2508
- if (rate.lte(0))
2888
+ const getFeeTokenLookupAddress = (value) => {
2889
+ const lower = (value ?? "").toLowerCase();
2890
+ if (!lower || lower === "0x" || isNativeTokenAddress(lower)) {
2891
+ return zeroAddress;
2892
+ }
2893
+ return lower;
2894
+ };
2895
+ const getTokenUsdRateFromBalances = (chainId, contractAddress, symbol) => {
2896
+ if (!chainId)
2509
2897
  return undefined;
2510
- return usdAmount
2511
- .div(rate)
2512
- .toDecimalPlaces(Math.max(0, token.decimals ?? 18), Decimal.ROUND_DOWN)
2513
- .toFixed();
2898
+ const lookupAddress = getFeeTokenLookupAddress(contractAddress);
2899
+ const lookupSymbol = symbol?.toUpperCase();
2900
+ for (const asset of swapBalance ?? []) {
2901
+ for (const breakdown of asset.breakdown ?? []) {
2902
+ if (breakdown.chain?.id !== chainId)
2903
+ continue;
2904
+ const breakdownAddress = getFeeTokenLookupAddress(breakdown.contractAddress);
2905
+ const addressMatches = lookupAddress && breakdownAddress === lookupAddress;
2906
+ const symbolMatches = lookupSymbol &&
2907
+ [breakdown.symbol, asset.symbol]
2908
+ .filter(Boolean)
2909
+ .some((candidate) => candidate.toUpperCase() === lookupSymbol);
2910
+ if (!addressMatches && !symbolMatches)
2911
+ continue;
2912
+ const balance = parseFiatNumber(breakdown.balance);
2913
+ const fiatBalance = parseFiatNumber(breakdown.balanceInFiat);
2914
+ if (balance && fiatBalance && balance.gt(0) && fiatBalance.gt(0)) {
2915
+ return fiatBalance.div(balance);
2916
+ }
2917
+ }
2918
+ }
2919
+ return undefined;
2514
2920
  };
2515
- const getMaxSwapQuoteCacheKey = (token) => {
2516
- if (!token?.chainId)
2517
- return "";
2518
- return [
2519
- token.chainId,
2520
- (token.contractAddress || zeroAddress).toLowerCase(),
2521
- token.symbol.toUpperCase(),
2522
- ].join(":");
2921
+ const getTokenUsdRateFromSupportedChains = (chains, chainId, contractAddress, symbol) => {
2922
+ if (!chainId)
2923
+ return undefined;
2924
+ const chain = chains?.find((item) => Number(item?.id ?? item?.chainId) === chainId);
2925
+ const tokens = chain?.tokens ?? chain?.assets ?? [];
2926
+ const lookupAddress = getFeeTokenLookupAddress(contractAddress);
2927
+ const lookupSymbol = symbol?.toUpperCase();
2928
+ const matchedToken = tokens.find((token) => {
2929
+ const tokenAddress = getFeeTokenLookupAddress(token?.contractAddress ?? token?.address ?? token?.tokenAddress);
2930
+ const addressMatches = lookupAddress && tokenAddress === lookupAddress;
2931
+ const tokenSymbol = (token?.symbol ?? token?.tokenSymbol ?? "")
2932
+ .toString()
2933
+ .toUpperCase();
2934
+ const symbolMatches = lookupSymbol && tokenSymbol === lookupSymbol;
2935
+ return addressMatches || symbolMatches;
2936
+ });
2937
+ const priceUsd = parseFiatNumber(matchedToken?.priceUSD ??
2938
+ matchedToken?.priceUsd ??
2939
+ matchedToken?.usdPrice ??
2940
+ matchedToken?.price);
2941
+ return priceUsd && priceUsd.gt(0) ? priceUsd : undefined;
2523
2942
  };
2524
- const getCachedMaxSwapQuote = (token) => {
2525
- const key = getMaxSwapQuoteCacheKey(token);
2526
- return key ? maxSwapQuoteCacheRef.current[key] : undefined;
2943
+ const getIntentDestinationGasUsdValue = (intent) => {
2944
+ const gas = intent?.destination?.gas;
2945
+ const explicitValue = parseFiatNumber(gas?.value);
2946
+ if (explicitValue && explicitValue.gt(0))
2947
+ return explicitValue;
2948
+ const amount = parseFiatNumber(gas?.amount);
2949
+ if (!amount || amount.lte(0))
2950
+ return undefined;
2951
+ const chainId = intent?.destination?.chain?.id;
2952
+ const contractAddress = gas?.token?.contractAddress;
2953
+ const symbol = gas?.token?.symbol;
2954
+ const rate = getTokenUsdRateFromBalances(chainId, contractAddress, symbol) ??
2955
+ getTokenUsdRateFromSupportedChains(swapSupportedChainsAndTokens, chainId, contractAddress, symbol) ??
2956
+ getTokenUsdRateFromSupportedChains(supportedChainsAndTokens, chainId, contractAddress, symbol) ??
2957
+ getUsdRateForSymbol(symbol);
2958
+ return rate.gt(0) ? amount.mul(rate) : undefined;
2527
2959
  };
2528
2960
  const getCachedDestinationUsdRate = (token) => {
2529
- const intentCachedRate = getCachedIntentUsdRate(token);
2530
- if (intentCachedRate && intentCachedRate.gt(0)) {
2531
- return intentCachedRate;
2532
- }
2533
- const cached = getCachedMaxSwapQuote(token);
2534
- if (!cached ||
2535
- !cached.maxUsdAmount ||
2536
- cached.maxUsdAmount.lte(0) ||
2537
- cached.maxTokenAmount.lte(0)) {
2538
- return undefined;
2539
- }
2540
- return cached.maxUsdAmount.div(cached.maxTokenAmount);
2961
+ return getCachedIntentUsdRate(token);
2541
2962
  };
2542
2963
  const resolveUsdRateForSymbol = async (symbol) => {
2543
2964
  if (!symbol)
@@ -2555,79 +2976,6 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2555
2976
  return new Decimal(0);
2556
2977
  }
2557
2978
  };
2558
- const resolveMaxSwapQuote = async (token) => {
2559
- const key = getMaxSwapQuoteCacheKey(token);
2560
- if (!key)
2561
- return undefined;
2562
- const cached = maxSwapQuoteCacheRef.current[key];
2563
- if (cached)
2564
- return cached;
2565
- const calculateMaxForSwap = nexusSDK?.calculateMaxForSwap;
2566
- if (typeof calculateMaxForSwap !== "function" || !token.chainId) {
2567
- return undefined;
2568
- }
2569
- const max = await calculateMaxForSwap({
2570
- toChainId: token.chainId,
2571
- toTokenAddress: (token.contractAddress || zeroAddress),
2572
- });
2573
- const decimals = Number.isFinite(Number(max.decimals))
2574
- ? Number(max.decimals)
2575
- : token.decimals || 18;
2576
- const maxAmount = parseFiatNumber(max.maxAmount) ??
2577
- (max.maxAmountRaw !== undefined
2578
- ? new Decimal(max.maxAmountRaw.toString()).div(new Decimal(10).pow(decimals))
2579
- : undefined);
2580
- if (!maxAmount || maxAmount.lte(0))
2581
- return undefined;
2582
- const safeMaxAmount = maxAmount.mul(receiveMaxSafetyMultiplier);
2583
- const destinationRate = await resolveUsdRateForSymbol(max.symbol || token.symbol);
2584
- let maxUsdAmount = destinationRate.gt(0)
2585
- ? safeMaxAmount.mul(destinationRate)
2586
- : undefined;
2587
- if (!maxUsdAmount || maxUsdAmount.lte(0)) {
2588
- const sourcesUsd = await (max.sources ?? []).reduce(async (sumPromise, source) => {
2589
- const sum = await sumPromise;
2590
- const amount = parseFiatNumber(source.amount) ?? new Decimal(0);
2591
- if (amount.lte(0))
2592
- return sum;
2593
- const sourceRate = await resolveUsdRateForSymbol(source.symbol);
2594
- return sourceRate.gt(0) ? sum.plus(amount.mul(sourceRate)) : sum;
2595
- }, Promise.resolve(new Decimal(0)));
2596
- if (sourcesUsd.gt(0)) {
2597
- maxUsdAmount = sourcesUsd.mul(receiveMaxSafetyMultiplier);
2598
- }
2599
- }
2600
- const quote = {
2601
- decimals,
2602
- maxTokenAmount: safeMaxAmount,
2603
- maxUsdAmount,
2604
- symbol: max.symbol || token.symbol,
2605
- };
2606
- maxSwapQuoteCacheRef.current[key] = quote;
2607
- return quote;
2608
- };
2609
- const getPercentAmountFromMaxQuote = async (token, pct, preferUsd) => {
2610
- const maxQuote = await resolveMaxSwapQuote(token);
2611
- if (!maxQuote)
2612
- return undefined;
2613
- const ratio = new Decimal(pct).div(100);
2614
- if (preferUsd && maxQuote.maxUsdAmount && maxQuote.maxUsdAmount.gt(0)) {
2615
- return {
2616
- amount: maxQuote.maxUsdAmount
2617
- .mul(ratio)
2618
- .toDecimalPlaces(2, Decimal.ROUND_DOWN)
2619
- .toFixed(),
2620
- mode: "usd",
2621
- };
2622
- }
2623
- return {
2624
- amount: maxQuote.maxTokenAmount
2625
- .mul(ratio)
2626
- .toDecimalPlaces(Math.max(0, maxQuote.decimals), Decimal.ROUND_DOWN)
2627
- .toFixed(),
2628
- mode: "token",
2629
- };
2630
- };
2631
2979
  const getTokenUsdValue = (token, fallbackAmount) => {
2632
2980
  const amountNumber = parseFiatNumber(token.userAmount || fallbackAmount) ?? new Decimal(0);
2633
2981
  if (amountNumber.lte(0))
@@ -2642,6 +2990,50 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2642
2990
  };
2643
2991
  const getTokenBalanceAmount = (token) => parseFiatNumber(token.balance) ?? new Decimal(0);
2644
2992
  const getTokenBalanceUsd = (token) => parseFiatNumber(token.balanceInFiat) ?? new Decimal(0);
2993
+ const getSdkExactOutSourcePriority = (token) => {
2994
+ const isSameChain = Boolean(token.chainId && toToken?.chainId) &&
2995
+ token.chainId === toToken?.chainId;
2996
+ const isSameToken = Boolean(token.contractAddress && toToken?.contractAddress) &&
2997
+ ((isNativeTokenAddress(token.contractAddress) &&
2998
+ isNativeTokenAddress(toToken?.contractAddress)) ||
2999
+ token.contractAddress.toLowerCase() ===
3000
+ toToken?.contractAddress.toLowerCase());
3001
+ const isStable = isSdkExactOutStableSymbol(token.symbol);
3002
+ const isGas = isNativeTokenAddress(token.contractAddress);
3003
+ const isEthereum = token.chainId === ETHEREUM_MAINNET_CHAIN_ID;
3004
+ if (isSameChain) {
3005
+ if (isSameToken)
3006
+ return 1;
3007
+ if (isStable)
3008
+ return 2;
3009
+ if (isGas)
3010
+ return 3;
3011
+ return 4;
3012
+ }
3013
+ if (isEthereum) {
3014
+ if (isSameToken)
3015
+ return 8;
3016
+ if (isStable)
3017
+ return 9;
3018
+ if (isGas)
3019
+ return 10;
3020
+ return 11;
3021
+ }
3022
+ if (isSameToken)
3023
+ return 5;
3024
+ if (isStable)
3025
+ return 6;
3026
+ return 7;
3027
+ };
3028
+ const sortExactOutSourcesBySdkPriority = (tokens) => [...tokens].sort((a, b) => {
3029
+ const priorityDiff = getSdkExactOutSourcePriority(a) - getSdkExactOutSourcePriority(b);
3030
+ if (priorityDiff !== 0)
3031
+ return priorityDiff;
3032
+ const usdDiff = getTokenBalanceUsd(b).cmp(getTokenBalanceUsd(a));
3033
+ if (usdDiff !== 0)
3034
+ return usdDiff;
3035
+ return `${a.symbol} ${a.chainName ?? ""}`.localeCompare(`${b.symbol} ${b.chainName ?? ""}`);
3036
+ });
2645
3037
  const getTokenAmountForUsd = (token, usdAmount) => {
2646
3038
  const rate = getTokenUsdRate(token);
2647
3039
  if (rate.lte(0) || usdAmount.lte(0))
@@ -2974,6 +3366,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
2974
3366
  : [token])
2975
3367
  .filter(hasMinimumSourceUsdBalance);
2976
3368
  const hasPositiveDecimalInput = (value) => Boolean(parseFiatNumber(value)?.gt(0));
3369
+ const hasPositiveSourceSpend = (token) => hasPositiveDecimalInput(token.userAmount) ||
3370
+ hasPositiveDecimalInput(token.userAmountUsd);
2977
3371
  const getReadyExactInSourceTokens = (tokens) => getExactInSourceTokens(tokens).filter((token) => Boolean(token.chainId && token.contractAddress) &&
2978
3372
  hasPositiveDecimalInput(token.userAmount));
2979
3373
  const hasReadyExactInSwapInput = (tokens, destination) => Boolean(destination?.chainId &&
@@ -3062,6 +3456,51 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3062
3456
  }
3063
3457
  return getExpandedSourceTokens(tokens).filter(hasGasForSource);
3064
3458
  };
3459
+ const getMinimumBalanceSourceTokens = () => filterMinimumSourceUsdTokens(getExpandedSourceTokens(swapBalance
3460
+ ? deriveTokenOptions(swapBalance, swapSupportedChainsAndTokens)
3461
+ : []));
3462
+ const getHeldDestinationTokenOption = () => {
3463
+ if (!toToken?.chainId || !toToken.contractAddress)
3464
+ return undefined;
3465
+ for (const asset of swapBalance ?? []) {
3466
+ for (const breakdown of asset.breakdown ?? []) {
3467
+ const chainId = breakdown.chain?.id;
3468
+ if (chainId !== toToken.chainId)
3469
+ continue;
3470
+ const breakdownAddress = breakdown.contractAddress;
3471
+ const addressMatches = breakdownAddress &&
3472
+ (breakdownAddress.toLowerCase() ===
3473
+ toToken.contractAddress.toLowerCase() ||
3474
+ (isNativeTokenAddress(breakdownAddress) &&
3475
+ isNativeTokenAddress(toToken.contractAddress)));
3476
+ const symbolMatches = (breakdown.symbol ?? asset.symbol ?? "").toUpperCase() ===
3477
+ toToken.symbol.toUpperCase();
3478
+ if (!addressMatches && !symbolMatches)
3479
+ continue;
3480
+ const balanceAmount = parseFiatNumber(breakdown.balance);
3481
+ if (!balanceAmount || balanceAmount.lte(0))
3482
+ continue;
3483
+ const chainMeta = CHAIN_METADATA[chainId];
3484
+ const symbol = breakdown.symbol ?? asset.symbol ?? toToken.symbol;
3485
+ const fiatBalance = parseFiatNumber(breakdown.balanceInFiat);
3486
+ return {
3487
+ chainId,
3488
+ chainLogo: chainMeta?.logo ?? breakdown.chain?.logo ?? toToken.chainLogo,
3489
+ chainName: getShortChainName(chainId, chainMeta?.name ?? breakdown.chain?.name ?? toToken.chainName),
3490
+ contractAddress: breakdown.contractAddress ?? toToken.contractAddress,
3491
+ decimals: breakdown.decimals ?? asset.decimals ?? toToken.decimals ?? 18,
3492
+ logo: asset.logo ?? toToken.logo,
3493
+ name: symbol,
3494
+ symbol,
3495
+ balance: `${breakdown.balance} ${symbol}`,
3496
+ balanceInFiat: fiatBalance !== undefined
3497
+ ? `$${fiatBalance.toDecimalPlaces(2).toFixed()}`
3498
+ : "$0.00",
3499
+ };
3500
+ }
3501
+ }
3502
+ return undefined;
3503
+ };
3065
3504
  const getDepositDestinationForSourceSelection = () => {
3066
3505
  const destination = activeMode === "deposit" ? selectedOpportunity : toToken;
3067
3506
  const chainId = destination?.chainId;
@@ -3160,7 +3599,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3160
3599
  const getExactOutSourceTokens = (mode = exactOutQuoteSourceModeRef.current, targetAmountUsd) => {
3161
3600
  if (activeMode === "deposit") {
3162
3601
  const selection = getResolvedDepositSourceSelection({ targetAmountUsd });
3163
- return getDepositSourceTokensForIds(selection.selectedSourceIds);
3602
+ return getDepositSourceTokensForIds(mode === "all" ? selection.sourcePoolIds : selection.selectedSourceIds);
3164
3603
  }
3165
3604
  if (activeMode === "send" && mode === "selected" && fromTokens.length > 0) {
3166
3605
  return filterMinimumSourceUsdTokens(getExpandedSourceTokens(fromTokens)).filter(hasGasForSource);
@@ -3194,60 +3633,16 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3194
3633
  return true;
3195
3634
  });
3196
3635
  };
3197
- const getHeldNativeGasSourceForChain = (chainId) => {
3198
- if (!chainId)
3199
- return undefined;
3200
- const nativeSymbol = CHAIN_METADATA[chainId]?.nativeCurrency?.symbol?.toUpperCase();
3201
- for (const asset of swapBalance ?? []) {
3202
- for (const breakdown of asset.breakdown ?? []) {
3203
- if (breakdown.chain?.id !== chainId)
3204
- continue;
3205
- const breakdownSymbol = (breakdown.symbol ??
3206
- asset.symbol ??
3207
- "").toUpperCase();
3208
- const assetSymbol = (asset.symbol ?? "").toUpperCase();
3209
- const isNativeBalance = isNativeTokenAddress(breakdown.contractAddress) ||
3210
- Boolean(nativeSymbol &&
3211
- (breakdownSymbol === nativeSymbol || assetSymbol === nativeSymbol));
3212
- const balance = parseFiatNumber(breakdown.balance) ?? new Decimal(0);
3213
- if (!isNativeBalance || balance.lte(0))
3214
- continue;
3215
- return {
3216
- chainId,
3217
- tokenAddress: (breakdown.contractAddress ||
3218
- zeroAddress),
3219
- };
3220
- }
3221
- }
3222
- return undefined;
3223
- };
3224
3636
  const getHeldDestinationTokenSource = () => {
3225
- if (!toToken?.chainId || !toToken.contractAddress)
3637
+ const heldDestinationToken = getHeldDestinationTokenOption();
3638
+ if (!heldDestinationToken?.chainId ||
3639
+ !heldDestinationToken.contractAddress) {
3226
3640
  return undefined;
3227
- for (const asset of swapBalance ?? []) {
3228
- for (const breakdown of asset.breakdown ?? []) {
3229
- const chainId = breakdown.chain?.id;
3230
- if (chainId !== toToken.chainId)
3231
- continue;
3232
- const breakdownAddress = breakdown.contractAddress;
3233
- const addressMatches = breakdownAddress &&
3234
- (breakdownAddress.toLowerCase() ===
3235
- toToken.contractAddress.toLowerCase() ||
3236
- (isNativeTokenAddress(breakdownAddress) &&
3237
- isNativeTokenAddress(toToken.contractAddress)));
3238
- const symbolMatches = (breakdown.symbol ?? asset.symbol ?? "").toUpperCase() ===
3239
- toToken.symbol.toUpperCase();
3240
- const balance = parseFiatNumber(breakdown.balance) ?? new Decimal(0);
3241
- if ((!addressMatches && !symbolMatches) || balance.lte(0))
3242
- continue;
3243
- return {
3244
- chainId,
3245
- tokenAddress: (breakdown.contractAddress ||
3246
- toToken.contractAddress),
3247
- };
3248
- }
3249
3641
  }
3250
- return undefined;
3642
+ return {
3643
+ chainId: heldDestinationToken.chainId,
3644
+ tokenAddress: heldDestinationToken.contractAddress,
3645
+ };
3251
3646
  };
3252
3647
  const shouldSendExactOutSourceAllowlist = () => {
3253
3648
  if (activeMode === "deposit") {
@@ -3262,14 +3657,13 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3262
3657
  if (activeMode !== "deposit" && activeMode !== "send") {
3263
3658
  return buildExplicitSourcesPayload(tokens);
3264
3659
  }
3265
- if (!shouldSendExactOutSourceAllowlist()) {
3266
- return {};
3267
- }
3268
- const explicitSources = buildExplicitSourcesPayload(tokens).sources;
3660
+ const sourceTokens = shouldSendExactOutSourceAllowlist()
3661
+ ? tokens
3662
+ : getExactOutSourceTokens("all");
3663
+ const explicitSources = buildExplicitSourcesPayload(sourceTokens).sources;
3269
3664
  const sources = dedupeSdkSources([
3270
3665
  ...explicitSources,
3271
3666
  getHeldDestinationTokenSource(),
3272
- getHeldNativeGasSourceForChain(toToken?.chainId),
3273
3667
  ]);
3274
3668
  return sources.length > 0 ? { sources } : {};
3275
3669
  };
@@ -3277,16 +3671,16 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3277
3671
  if (requiredSourceUsd.lte(0))
3278
3672
  return [];
3279
3673
  const destinationKey = getTokenSelectionKey(toToken);
3280
- const candidates = getExactOutSourceTokens(exactOutQuoteSourceModeRef.current, requiredSourceUsd)
3674
+ const candidates = sortExactOutSourcesBySdkPriority(getExactOutSourceTokens(exactOutQuoteSourceModeRef.current, requiredSourceUsd)
3281
3675
  .filter((token) => getTokenSelectionKey(token) !== destinationKey)
3282
- .filter((token) => getTokenBalanceUsd(token).gt(0));
3676
+ .filter(hasMinimumSourceUsdBalance));
3283
3677
  const sources = [];
3284
3678
  let remainingUsd = requiredSourceUsd;
3285
3679
  for (const token of candidates) {
3286
3680
  if (remainingUsd.lte(0))
3287
3681
  break;
3288
3682
  const availableUsd = getTokenBalanceUsd(token);
3289
- if (availableUsd.lte(0))
3683
+ if (availableUsd.lt(minimumSourceUsd))
3290
3684
  continue;
3291
3685
  const rate = await resolveUsdRateForToken(token);
3292
3686
  if (rate.lte(0))
@@ -3343,27 +3737,91 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3343
3737
  if (!amountNumber || amountNumber.lte(0) || !toToken?.symbol) {
3344
3738
  return undefined;
3345
3739
  }
3740
+ if (activeMode === "deposit" && depositAmountMode === "usd") {
3741
+ return amountNumber;
3742
+ }
3346
3743
  const fiatValue = getFiatValue(amountNumber.toNumber(), toToken.symbol);
3347
3744
  return Number.isFinite(fiatValue) && fiatValue > 0
3348
3745
  ? new Decimal(fiatValue)
3349
3746
  : undefined;
3350
3747
  };
3351
- const getExactOutAvailableSourceUsd = () => {
3352
- const selectedSourceTotal = exactOutQuoteSourceModeRef.current === "selected" && fromTokens.length > 0
3353
- ? fromTokens.reduce((sum, token) => {
3748
+ const getExactOutAvailableSourceUsd = (sourceTokensOverride) => {
3749
+ const sumTokensWithDestinationCredit = (tokens) => {
3750
+ const heldDestinationToken = getHeldDestinationTokenOption();
3751
+ const heldDestinationKey = getTokenSelectionKey(heldDestinationToken);
3752
+ let hasHeldDestinationToken = false;
3753
+ const sourceTotal = tokens.reduce((sum, token) => {
3354
3754
  const value = parseFiatNumber(token.balanceInFiat) ?? new Decimal(0);
3355
- return value.gte(minimumSourceUsd) ? sum.plus(value) : sum;
3356
- }, new Decimal(0))
3357
- : undefined;
3358
- if (selectedSourceTotal && selectedSourceTotal.gt(0)) {
3359
- return selectedSourceTotal;
3755
+ const isHeldDestinationToken = heldDestinationKey &&
3756
+ getTokenSelectionKey(token) === heldDestinationKey;
3757
+ if (isHeldDestinationToken) {
3758
+ hasHeldDestinationToken = true;
3759
+ }
3760
+ return value.gt(0) &&
3761
+ (isHeldDestinationToken || value.gte(minimumSourceUsd))
3762
+ ? sum.plus(value)
3763
+ : sum;
3764
+ }, new Decimal(0));
3765
+ if (!heldDestinationToken || hasHeldDestinationToken) {
3766
+ return sourceTotal;
3767
+ }
3768
+ const heldDestinationUsd = parseFiatNumber(heldDestinationToken.balanceInFiat) ?? new Decimal(0);
3769
+ return heldDestinationUsd.gt(0)
3770
+ ? sourceTotal.plus(heldDestinationUsd)
3771
+ : sourceTotal;
3772
+ };
3773
+ if (sourceTokensOverride ||
3774
+ exactOutQuoteSourceModeRef.current === "selected") {
3775
+ const selectedSourceTokens = sourceTokensOverride ?? fromTokens;
3776
+ return sumTokensWithDestinationCredit(selectedSourceTokens);
3360
3777
  }
3361
- const allSourceTotal = getGasCapableBalanceSourceTokens().reduce((sum, token) => {
3362
- const value = parseFiatNumber(token.balanceInFiat) ?? new Decimal(0);
3363
- return value.gte(minimumSourceUsd) ? sum.plus(value) : sum;
3364
- }, new Decimal(0));
3778
+ return getExactOutTotalSourceBalanceUsd();
3779
+ };
3780
+ const getExactOutTotalSourceBalanceUsd = () => {
3781
+ const allSourceTotal = getExactOutAvailableSourceUsd(getMinimumBalanceSourceTokens());
3365
3782
  return allSourceTotal.gt(0) ? allSourceTotal : getSwapBalanceTotalUsd();
3366
3783
  };
3784
+ const getExactOutIntentSourceUsd = () => {
3785
+ const intentSourceUsd = (intentData?.sources ?? []).reduce((sum, source) => sum.plus(parseFiatNumber(source.value) ?? new Decimal(0)), new Decimal(0));
3786
+ return intentSourceUsd.gt(0) ? intentSourceUsd : undefined;
3787
+ };
3788
+ const getExactOutHeldDestinationUsd = () => {
3789
+ const heldDestinationToken = getHeldDestinationTokenOption();
3790
+ const value = parseFiatNumber(heldDestinationToken?.balanceInFiat);
3791
+ return value && value.gt(0) ? value : new Decimal(0);
3792
+ };
3793
+ const exactOutIntentIncludesHeldDestination = () => {
3794
+ const heldDestinationToken = getHeldDestinationTokenOption();
3795
+ const heldDestinationKey = getTokenSelectionKey(heldDestinationToken);
3796
+ if (!heldDestinationKey)
3797
+ return false;
3798
+ return (intentData?.sources ?? []).some((source) => {
3799
+ const chainId = source.chain?.id;
3800
+ const tokenAddress = source.token?.contractAddress;
3801
+ if (!chainId || !tokenAddress)
3802
+ return false;
3803
+ return (getTokenSelectionKey({
3804
+ chainId,
3805
+ contractAddress: tokenAddress,
3806
+ }) === heldDestinationKey);
3807
+ });
3808
+ };
3809
+ const getExactOutRequiredSourceUsd = () => {
3810
+ const intentSourceUsd = getExactOutIntentSourceUsd();
3811
+ if (intentSourceUsd)
3812
+ return intentSourceUsd;
3813
+ return undefined;
3814
+ };
3815
+ const getExactOutRequiredFundingUsd = () => {
3816
+ const sourceRequiredUsd = getExactOutRequiredSourceUsd();
3817
+ if (sourceRequiredUsd) {
3818
+ const destinationCreditUsd = exactOutIntentIncludesHeldDestination()
3819
+ ? new Decimal(0)
3820
+ : getExactOutHeldDestinationUsd();
3821
+ return sourceRequiredUsd.plus(destinationCreditUsd);
3822
+ }
3823
+ return getExactOutRequestedUsd();
3824
+ };
3367
3825
  const getExactInSourceDeficitUsd = () => {
3368
3826
  if (swapType !== "exactIn" || fromTokens.length === 0)
3369
3827
  return undefined;
@@ -3402,7 +3860,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3402
3860
  details.availableAmountUsd ??
3403
3861
  details.availableAmount ??
3404
3862
  details.available) ?? parseLabeledErrorDecimal(errorText, "available");
3405
- const requestedUsd = getExactOutRequestedUsd();
3863
+ const requestedUsd = getExactOutRequiredFundingUsd();
3406
3864
  const availableUsd = getExactOutAvailableSourceUsd();
3407
3865
  const exactInSourceDeficitUsd = getExactInSourceDeficitUsd();
3408
3866
  let missingUsd = exactInSourceDeficitUsd && exactInSourceDeficitUsd.gt(0)
@@ -3429,11 +3887,11 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3429
3887
  message: "Add more source balance across your assets",
3430
3888
  };
3431
3889
  };
3432
- const buildExactOutSourceBalanceIssue = () => {
3890
+ const buildExactOutSourceBalanceIssue = (sourceTokensOverride) => {
3433
3891
  if (activeMode !== "deposit" && activeMode !== "send")
3434
3892
  return null;
3435
- const requestedUsd = getExactOutRequestedUsd();
3436
- const availableUsd = getExactOutAvailableSourceUsd();
3893
+ const requestedUsd = getExactOutRequiredFundingUsd();
3894
+ const availableUsd = getExactOutAvailableSourceUsd(sourceTokensOverride);
3437
3895
  if (!requestedUsd || requestedUsd.lte(0) || !availableUsd)
3438
3896
  return null;
3439
3897
  const missingUsd = requestedUsd.minus(availableUsd);
@@ -3757,22 +4215,29 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3757
4215
  ...intent,
3758
4216
  sources: sortIntentSourcesByUsdDesc(intent.sources ?? []),
3759
4217
  };
3760
- const sortedIntentSourceTokens = sortSwapTokensByUsdDesc((sortedIntent.sources ?? []).map(buildIntentSourceToken));
4218
+ const enrichedIntent = enrichSwapIntentTokenMetadata(sortedIntent);
4219
+ const sortedIntentSourceTokens = sortSwapTokensByUsdDesc((enrichedIntent.sources ?? []).map(buildIntentSourceToken));
3761
4220
  lastSwapIntentRefreshAtRef.current = Date.now();
3762
4221
  lastIntentSourceTokensRef.current = sortedIntentSourceTokens;
3763
- cacheDestinationUsdRateFromIntent(sortedIntent);
3764
- cachePredictiveBaselineFromIntent(sortedIntent);
3765
- setIntentData(sortedIntent);
3766
- setIntentToAmount(sortedIntent.destination?.amount || undefined);
4222
+ if (!sourceSelectionTouched &&
4223
+ (activeMode === "send" ||
4224
+ (activeMode === "deposit" && swapType === "exactOut"))) {
4225
+ lastAutoIntentSourceTokensRef.current = sortedIntentSourceTokens;
4226
+ }
4227
+ cacheDestinationUsdRateFromIntent(enrichedIntent);
4228
+ cachePredictiveBaselineFromIntent(enrichedIntent);
4229
+ setIntentData(enrichedIntent);
4230
+ setIntentToAmount(enrichedIntent.destination?.amount || undefined);
3767
4231
  setSwapQuoteIssue(null);
3768
4232
  if (!sourceSelectionTouched &&
4233
+ swapStepRef.current !== "choose-swap-asset" &&
3769
4234
  (activeMode === "send" ||
3770
4235
  (activeMode === "deposit" && swapType === "exactOut"))) {
3771
4236
  syncingIntentSourcesRef.current = true;
3772
4237
  setFromTokens(sortedIntentSourceTokens);
3773
4238
  }
3774
4239
  try {
3775
- const bridgeFees = sortedIntent.feesAndBuffer?.bridge;
4240
+ const bridgeFees = enrichedIntent.feesAndBuffer?.bridge;
3776
4241
  const bridgeFeeData = bridgeFees && typeof bridgeFees === "object" ? bridgeFees : undefined;
3777
4242
  const collectionFee = parseFiatNumber(bridgeFeeData?.collection);
3778
4243
  const fulfilmentFee = parseFiatNumber(bridgeFeeData?.fulfilment);
@@ -3780,20 +4245,29 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3780
4245
  (collectionFee !== undefined || fulfilmentFee !== undefined
3781
4246
  ? (collectionFee ?? new Decimal(0)).plus(fulfilmentFee ?? new Decimal(0))
3782
4247
  : undefined);
4248
+ const bridgeGasSuppliedFee = parseFiatNumber(bridgeFeeData?.gasSupplied);
4249
+ const destinationGasSuppliedFee = getIntentDestinationGasUsdValue(enrichedIntent);
4250
+ const gasSuppliedFee = bridgeGasSuppliedFee ?? destinationGasSuppliedFee;
3783
4251
  const bridgeComponentsTotal = bridgeFeeData
3784
4252
  ? [
3785
4253
  executionGasFee,
3786
4254
  parseFiatNumber(bridgeFeeData.protocol),
3787
4255
  parseFiatNumber(bridgeFeeData.solver),
3788
- parseFiatNumber(bridgeFeeData.gasSupplied),
4256
+ gasSuppliedFee,
3789
4257
  ].reduce((sum, value) => sum.plus(value ?? new Decimal(0)), new Decimal(0))
3790
4258
  : undefined;
3791
- const bridgeTotal = typeof bridgeFees === "string"
4259
+ const rawBridgeTotal = typeof bridgeFees === "string"
3792
4260
  ? parseFiatNumber(bridgeFees)
3793
- : (parseFiatNumber(bridgeFeeData?.total) ??
4261
+ : parseFiatNumber(bridgeFeeData?.total);
4262
+ const bridgeTotal = rawBridgeTotal &&
4263
+ !bridgeGasSuppliedFee &&
4264
+ destinationGasSuppliedFee &&
4265
+ destinationGasSuppliedFee.gt(0)
4266
+ ? rawBridgeTotal.plus(destinationGasSuppliedFee)
4267
+ : (rawBridgeTotal ??
3794
4268
  (bridgeComponentsTotal && bridgeComponentsTotal.gt(0)
3795
4269
  ? bridgeComponentsTotal
3796
- : undefined));
4270
+ : destinationGasSuppliedFee));
3797
4271
  if (bridgeTotal !== undefined) {
3798
4272
  setIntentFeeUsd(bridgeTotal.gt(0) ? bridgeTotal.toDecimalPlaces(6).toFixed() : "0");
3799
4273
  }
@@ -3808,14 +4282,20 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3808
4282
  }, [
3809
4283
  activeMode,
3810
4284
  fromTokens,
4285
+ getFiatValue,
3811
4286
  sourceSelectionTouched,
3812
4287
  swapType,
3813
4288
  swapBalance,
4289
+ swapSupportedChainsAndTokens,
4290
+ supportedChainsAndTokens,
3814
4291
  toToken,
3815
4292
  ]);
3816
4293
  const handleSwapIntentCallback = useCallback((data, runId, quoteInputKey) => {
3817
4294
  const { intent, allow, deny, refresh } = data;
3818
- const bridgeProvider = normalizeBridgeProvider(data?.bridgeProvider ?? intent?.bridgeProvider ?? intent?.swap?.bridgeProvider);
4295
+ const bridgeProvider = normalizeBridgeProvider(data?.bridgeProvider ??
4296
+ intent?.bridgeProvider ??
4297
+ intent?.normalizedIntent?.bridgeProvider ??
4298
+ intent?.swap?.bridgeProvider);
3819
4299
  const intentWithBridgeProvider = normalizeRenderableSwapIntentData(intent, bridgeProvider);
3820
4300
  logSdkIntentEvent("onIntent", data, {
3821
4301
  bridgeProvider,
@@ -3834,11 +4314,21 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3834
4314
  deny();
3835
4315
  return;
3836
4316
  }
4317
+ if (quoteInputKey && activeQuoteInputKeyRef.current !== quoteInputKey) {
4318
+ logSdkIntentEvent("ignored stale quote input onIntent", data, {
4319
+ currentQuoteInputKey: activeQuoteInputKeyRef.current,
4320
+ quoteInputKey,
4321
+ runId,
4322
+ });
4323
+ deny();
4324
+ return;
4325
+ }
3837
4326
  if (!intentWithBridgeProvider) {
3838
4327
  console.warn("[NexusWidget SDK][intent] Unsupported intent shape", {
3839
4328
  intent,
3840
4329
  raw: data,
3841
4330
  });
4331
+ finishIntentFetchTiming(runId, "failed");
3842
4332
  deny();
3843
4333
  setIntentLoading(false);
3844
4334
  setQuoteRefreshing(false);
@@ -3847,16 +4337,18 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3847
4337
  setTxError("Quote unavailable");
3848
4338
  return;
3849
4339
  }
3850
- const resolvedQuoteInputKey = activeQuoteInputKeyRef.current || quoteInputKey;
4340
+ const resolvedQuoteInputKey = quoteInputKey;
3851
4341
  const normalizedRefresh = typeof refresh === "function"
3852
4342
  ? async (...args) => {
3853
4343
  const refreshed = await refresh(...args);
3854
4344
  const refreshedBridgeProvider = normalizeBridgeProvider(refreshed?.bridgeProvider ??
4345
+ refreshed?.normalizedIntent?.bridgeProvider ??
3855
4346
  refreshed?.swap?.bridgeProvider ??
3856
4347
  bridgeProvider);
3857
4348
  return (normalizeRenderableSwapIntentData(refreshed, refreshedBridgeProvider) ?? refreshed);
3858
4349
  }
3859
4350
  : refresh;
4351
+ finishIntentFetchTiming(runId);
3860
4352
  providerSwapIntent.current = {
3861
4353
  intent: intentWithBridgeProvider,
3862
4354
  allow,
@@ -3878,7 +4370,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
3878
4370
  setReceiveMaxCalculating(false);
3879
4371
  setPreviewQuoteRefreshing(false);
3880
4372
  });
3881
- }, [applySwapIntent, providerSwapIntent]);
4373
+ }, [applySwapIntent, finishIntentFetchTiming, providerSwapIntent]);
3882
4374
  // Deposit-specific
3883
4375
  const [selectedOpportunity, setSelectedOpportunity] = useState(() => (activeMode === "deposit" ? configuredDeposit : undefined));
3884
4376
  const selectedOpportunityIdentity = getDepositConfigIdentity(selectedOpportunity);
@@ -4328,11 +4820,16 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4328
4820
  : undefined;
4329
4821
  const usdValue = getFiatValue(Number(amount) || 0, currentAsset?.symbol || "USDC");
4330
4822
  const getDepositTokenUsdRate = () => {
4331
- if (!selectedOpportunity?.tokenSymbol)
4332
- return new Decimal(0);
4333
- const fiat = getFiatValue(1, selectedOpportunity.tokenSymbol);
4334
- if (Number.isFinite(fiat) && fiat > 0) {
4335
- return new Decimal(fiat);
4823
+ if (selectedOpportunity?.tokenSymbol) {
4824
+ const fiat = getFiatValue(1, selectedOpportunity.tokenSymbol);
4825
+ if (Number.isFinite(fiat) && fiat > 0) {
4826
+ return new Decimal(fiat);
4827
+ }
4828
+ }
4829
+ if (toToken) {
4830
+ const tokenRate = getTokenUsdRate(toToken);
4831
+ if (tokenRate.gt(0))
4832
+ return tokenRate;
4336
4833
  }
4337
4834
  return getCachedDestinationUsdRate(toToken) ?? new Decimal(0);
4338
4835
  };
@@ -4356,8 +4853,45 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4356
4853
  : new Decimal(0);
4357
4854
  const depositUsdDisplay = depositUsdDecimal.toDecimalPlaces(2).toFixed();
4358
4855
  const depositTokenDisplay = depositTokenAmountForQuote
4359
- ?.toDecimalPlaces(toToken?.decimals ?? 18)
4856
+ ?.toDecimalPlaces(getCappedTokenDisplayDecimals(toToken?.decimals), Decimal.ROUND_DOWN)
4360
4857
  .toFixed() ?? "0";
4858
+ const isExactOutMode = activeMode === "deposit" || activeMode === "send";
4859
+ const getActiveTotalBalanceUsd = () => isExactOutMode
4860
+ ? getExactOutTotalSourceBalanceUsd()
4861
+ : getSwapBalanceTotalUsd();
4862
+ const getTotalBalancePercentUsdAmount = (pct) => {
4863
+ const ratio = isExactOutMode && pct === 100
4864
+ ? receiveMaxSafetyMultiplier
4865
+ : new Decimal(pct).div(100);
4866
+ return getActiveTotalBalanceUsd().mul(ratio);
4867
+ };
4868
+ const getExactOutDestinationUsdRate = (token) => {
4869
+ if (activeMode === "deposit") {
4870
+ return getDepositTokenUsdRate();
4871
+ }
4872
+ return getTokenUsdRate(token);
4873
+ };
4874
+ const getExactOutPercentAmountFromBalance = (token, pct, preferUsd) => {
4875
+ const usdAmount = getTotalBalancePercentUsdAmount(pct);
4876
+ if (usdAmount.lte(0))
4877
+ return undefined;
4878
+ if (preferUsd) {
4879
+ return {
4880
+ amount: usdAmount.toDecimalPlaces(2, Decimal.ROUND_DOWN).toFixed(),
4881
+ mode: "usd",
4882
+ };
4883
+ }
4884
+ const rate = getExactOutDestinationUsdRate(token);
4885
+ if (rate.lte(0))
4886
+ return undefined;
4887
+ return {
4888
+ amount: usdAmount
4889
+ .div(rate)
4890
+ .toDecimalPlaces(getCappedTokenDisplayDecimals(token.decimals), Decimal.ROUND_DOWN)
4891
+ .toFixed(),
4892
+ mode: "token",
4893
+ };
4894
+ };
4361
4895
  const depositSourceTargetUsdKey = activeMode === "deposit"
4362
4896
  ? (getDepositSourceTargetUsd()?.toFixed() ?? "")
4363
4897
  : "";
@@ -4394,14 +4928,60 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4394
4928
  ].join(":")
4395
4929
  : "",
4396
4930
  ].join("|");
4931
+ const exactOutAutoSourceInputKey = [
4932
+ activeMode,
4933
+ normalizedQuoteAmountKey,
4934
+ toTokenQuoteKey,
4935
+ quoteRecipientKey.toLowerCase(),
4936
+ activeMode === "deposit"
4937
+ ? [
4938
+ depositAmountMode,
4939
+ depositQuoteAmountKey,
4940
+ selectedOpportunityIdentity,
4941
+ depositSourceTargetUsdKey,
4942
+ ].join(":")
4943
+ : "",
4944
+ ].join("|");
4945
+ activeQuoteInputKeyRef.current = activeQuoteInputKey;
4946
+ useEffect(() => {
4947
+ lastAutoIntentSourceTokensRef.current = [];
4948
+ }, [exactOutAutoSourceInputKey]);
4397
4949
  useEffect(() => {
4398
- activeQuoteInputKeyRef.current = activeQuoteInputKey;
4399
4950
  setTxError(null);
4400
4951
  }, [activeQuoteInputKey]);
4401
4952
  const hasCurrentQuoteIntent = Boolean(intentData &&
4402
4953
  swapIntentRef.current &&
4403
4954
  swapIntentRef.current.runId === swapRunIdRef.current &&
4404
4955
  swapIntentRef.current.quoteInputKey === activeQuoteInputKey);
4956
+ useEffect(() => {
4957
+ if (swapStep !== "idle")
4958
+ return;
4959
+ if (!hasCurrentQuoteIntent)
4960
+ return;
4961
+ if (sourceSelectionTouched)
4962
+ return;
4963
+ if (activeMode !== "send" &&
4964
+ !(activeMode === "deposit" && swapType === "exactOut")) {
4965
+ return;
4966
+ }
4967
+ const nextTokens = lastIntentSourceTokensRef.current;
4968
+ if (nextTokens.length === 0)
4969
+ return;
4970
+ setFromTokens((current) => {
4971
+ if (getSourceTokenListSyncKey(current) ===
4972
+ getSourceTokenListSyncKey(nextTokens)) {
4973
+ return current;
4974
+ }
4975
+ syncingIntentSourcesRef.current = true;
4976
+ return nextTokens;
4977
+ });
4978
+ }, [
4979
+ activeMode,
4980
+ hasCurrentQuoteIntent,
4981
+ sourceSelectionTouched,
4982
+ swapStep,
4983
+ swapType,
4984
+ ]);
4405
4985
  useEffect(() => {
4406
4986
  if (activeMode !== "deposit")
4407
4987
  return;
@@ -4506,15 +5086,17 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4506
5086
  : activeMode === "send"
4507
5087
  ? Boolean(hasPositiveDecimalInput(amount) && toToken)
4508
5088
  : false;
4509
- const invalidateExactOutQuoteForRefresh = () => {
5089
+ const invalidateExactOutQuoteForRefresh = (options) => {
4510
5090
  immediateQuoteAfterSourceEditRef.current = true;
4511
- const receiveIssue = buildReceiveAmountIssue();
5091
+ const receiveIssue = buildReceiveAmountIssue({
5092
+ sourceTokens: options?.sourceTokens,
5093
+ });
4512
5094
  applyReceiveAmountIssue(receiveIssue);
4513
5095
  const configuredIssue = buildConfiguredAmountIssue();
4514
5096
  if (configuredIssue) {
4515
5097
  clearPreviewForBlockingAmountIssue();
4516
5098
  }
4517
- const sourceBalanceIssue = buildExactOutSourceBalanceIssue();
5099
+ const sourceBalanceIssue = buildExactOutSourceBalanceIssue(options?.sourceTokens);
4518
5100
  if (sourceBalanceIssue) {
4519
5101
  clearPreviewForBlockingAmountIssue();
4520
5102
  setSwapQuoteIssue(sourceBalanceIssue);
@@ -4666,13 +5248,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4666
5248
  });
4667
5249
  const destinationUsdNeedingSources = Decimal.max(destinationUsd.minus(destinationCoverage?.usd ?? new Decimal(0)), new Decimal(0));
4668
5250
  const cachedSourceUsdRatio = parseFiatNumber(baseline?.exactOutSourceUsdPerDestinationUsd);
4669
- const requiredSourceUsd = destinationUsdNeedingSources.lte(0)
4670
- ? new Decimal(0)
4671
- : cachedSourceUsdRatio && cachedSourceUsdRatio.gt(0)
4672
- ? destinationUsdNeedingSources.mul(cachedSourceUsdRatio)
4673
- : destinationUsdNeedingSources
4674
- .mul(BASIS_POINTS + PREDICTIVE_EXACT_OUT_BUFFER_BPS)
4675
- .div(BASIS_POINTS);
5251
+ const requiredSourceUsd = getPredictiveExactOutSourceTargetUsd(destinationUsdNeedingSources, cachedSourceUsdRatio);
4676
5252
  const sources = requiredSourceUsd.gt(0)
4677
5253
  ? await buildPredictiveExactOutSources(requiredSourceUsd)
4678
5254
  : [];
@@ -4741,49 +5317,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4741
5317
  requiredDestinationTokenAmount.lte(0)) {
4742
5318
  return [];
4743
5319
  }
4744
- for (const asset of swapBalance ?? []) {
4745
- for (const breakdown of asset.breakdown ?? []) {
4746
- const chainId = breakdown.chain?.id;
4747
- if (chainId !== toToken.chainId)
4748
- continue;
4749
- const breakdownAddress = breakdown.contractAddress;
4750
- const addressMatches = breakdownAddress &&
4751
- toToken.contractAddress &&
4752
- (breakdownAddress.toLowerCase() ===
4753
- toToken.contractAddress.toLowerCase() ||
4754
- (isNativeTokenAddress(breakdownAddress) &&
4755
- isNativeTokenAddress(toToken.contractAddress)));
4756
- const symbolMatches = (breakdown.symbol ?? asset.symbol ?? "").toUpperCase() ===
4757
- toToken.symbol.toUpperCase();
4758
- if (!addressMatches && !symbolMatches)
4759
- continue;
4760
- const balanceAmount = parseFiatNumber(breakdown.balance);
4761
- if (!balanceAmount || balanceAmount.lte(0))
4762
- continue;
4763
- const chainMeta = CHAIN_METADATA[chainId];
4764
- const symbol = breakdown.symbol ?? asset.symbol ?? toToken.symbol;
4765
- const fiatBalance = parseFiatNumber(breakdown.balanceInFiat);
4766
- if (!fiatBalance || fiatBalance.lt(minimumSourceUsd))
4767
- continue;
4768
- return [
4769
- {
4770
- chainId,
4771
- chainLogo: chainMeta?.logo ?? breakdown.chain?.logo ?? toToken.chainLogo,
4772
- chainName: getShortChainName(chainId, chainMeta?.name ?? breakdown.chain?.name ?? toToken.chainName),
4773
- contractAddress: breakdown.contractAddress ?? toToken.contractAddress,
4774
- decimals: breakdown.decimals ?? asset.decimals ?? toToken.decimals ?? 18,
4775
- logo: asset.logo ?? toToken.logo,
4776
- name: symbol,
4777
- symbol,
4778
- balance: `${breakdown.balance} ${symbol}`,
4779
- balanceInFiat: fiatBalance !== undefined
4780
- ? `$${fiatBalance.toDecimalPlaces(2).toFixed()}`
4781
- : "$0.00",
4782
- },
4783
- ];
4784
- }
4785
- }
4786
- return [];
5320
+ const heldDestinationToken = getHeldDestinationTokenOption();
5321
+ return heldDestinationToken ? [heldDestinationToken] : [];
4787
5322
  }, [
4788
5323
  activeMode,
4789
5324
  requiredDestinationTokenAmount?.toFixed(),
@@ -4796,6 +5331,120 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4796
5331
  toToken?.logo,
4797
5332
  toToken?.symbol,
4798
5333
  ]);
5334
+ const setSourcePickerDraftSelection = useCallback((tokens) => {
5335
+ const nextTokens = tokens.map((token) => ({
5336
+ ...token,
5337
+ userAmount: "",
5338
+ }));
5339
+ sourcePickerDraftTokensRef.current = nextTokens;
5340
+ setSourcePickerDraftTokens(nextTokens);
5341
+ }, []);
5342
+ const getAutoExactOutSourceTokensForPicker = useCallback(() => {
5343
+ const autoIntentTokens = lastAutoIntentSourceTokensRef.current;
5344
+ if (autoIntentTokens.length > 0) {
5345
+ return autoIntentTokens;
5346
+ }
5347
+ return sourceSelectionTouched ? [] : fromTokens;
5348
+ }, [fromTokens, sourceSelectionTouched]);
5349
+ const resetSourcePickerDraft = useCallback(() => {
5350
+ sourcePickerDraftTokensRef.current = null;
5351
+ setSourcePickerDraftTokens(null);
5352
+ }, []);
5353
+ const beginSourcePickerEdit = useCallback(() => {
5354
+ if (activeMode !== "deposit" && activeMode !== "send") {
5355
+ resetSourcePickerDraft();
5356
+ return;
5357
+ }
5358
+ sourcePickerDraftDepositFilterRef.current = depositSourceFilter;
5359
+ sourcePickerDraftTouchedRef.current = sourceSelectionTouched;
5360
+ sourcePickerDraftModeRef.current = exactOutQuoteSourceModeRef.current;
5361
+ setSourcePickerDraftSelection(fromTokens);
5362
+ }, [
5363
+ activeMode,
5364
+ depositSourceFilter,
5365
+ fromTokens,
5366
+ resetSourcePickerDraft,
5367
+ setSourcePickerDraftSelection,
5368
+ sourceSelectionTouched,
5369
+ ]);
5370
+ const handleSourcePickerCancel = useCallback(() => {
5371
+ resetSourcePickerDraft();
5372
+ closeDrawerToIdle();
5373
+ }, [closeDrawerToIdle, resetSourcePickerDraft]);
5374
+ const handleSourcePickerDraftSelectionChange = useCallback((tokens) => {
5375
+ if (activeMode !== "deposit" && activeMode !== "send")
5376
+ return;
5377
+ setSourcePickerDraftSelection(tokens);
5378
+ sourcePickerDraftTouchedRef.current = true;
5379
+ sourcePickerDraftModeRef.current = "selected";
5380
+ if (activeMode === "deposit") {
5381
+ sourcePickerDraftDepositFilterRef.current = "custom";
5382
+ }
5383
+ }, [activeMode, setSourcePickerDraftSelection]);
5384
+ const handleSourcePickerFilterTabSelect = useCallback((tab) => {
5385
+ if (activeMode !== "deposit" && activeMode !== "send")
5386
+ return;
5387
+ const nextFilter = tab === "stables" ? "stablecoins" : tab;
5388
+ if (tab === "all") {
5389
+ sourcePickerDraftDepositFilterRef.current = nextFilter;
5390
+ sourcePickerDraftTouchedRef.current = false;
5391
+ sourcePickerDraftModeRef.current = "all";
5392
+ setSourcePickerDraftSelection(getAutoExactOutSourceTokensForPicker());
5393
+ return;
5394
+ }
5395
+ if (activeMode !== "deposit")
5396
+ return;
5397
+ const selection = getResolvedDepositSourceSelection({
5398
+ filter: nextFilter,
5399
+ isManualSelection: false,
5400
+ });
5401
+ const sourcePoolTokens = getDepositSourceTokensForIds(selection.sourcePoolIds);
5402
+ sourcePickerDraftDepositFilterRef.current = nextFilter;
5403
+ sourcePickerDraftTouchedRef.current = false;
5404
+ sourcePickerDraftModeRef.current = "all";
5405
+ setSourcePickerDraftSelection(sourcePoolTokens);
5406
+ }, [
5407
+ activeMode,
5408
+ getAutoExactOutSourceTokensForPicker,
5409
+ getDepositSourceTokensForIds,
5410
+ getResolvedDepositSourceSelection,
5411
+ setSourcePickerDraftSelection,
5412
+ ]);
5413
+ const commitSourcePickerDraft = useCallback((tokens) => {
5414
+ if (activeMode !== "deposit" && activeMode !== "send") {
5415
+ resetSourcePickerDraft();
5416
+ closeDrawerToIdle();
5417
+ return;
5418
+ }
5419
+ const nextTokens = tokens ?? sourcePickerDraftTokensRef.current ?? fromTokens;
5420
+ const normalizedTokens = nextTokens.map((token) => ({
5421
+ ...token,
5422
+ userAmount: "",
5423
+ }));
5424
+ setSourceSelectionTouched(sourcePickerDraftTouchedRef.current);
5425
+ setExactOutQuoteSourceModeValue(sourcePickerDraftModeRef.current);
5426
+ if (activeMode === "deposit") {
5427
+ setDepositSourceFilter(sourcePickerDraftDepositFilterRef.current);
5428
+ }
5429
+ invalidateExactOutQuoteForRefresh({
5430
+ sourceTokens: normalizedTokens,
5431
+ });
5432
+ setSourceSelectionRevision((current) => current + 1);
5433
+ setFromTokens(normalizedTokens);
5434
+ resetSourcePickerDraft();
5435
+ closeDrawerToIdle();
5436
+ }, [
5437
+ activeMode,
5438
+ closeDrawerToIdle,
5439
+ fromTokens,
5440
+ invalidateExactOutQuoteForRefresh,
5441
+ resetSourcePickerDraft,
5442
+ setExactOutQuoteSourceModeValue,
5443
+ ]);
5444
+ const sourcePickerSelectedTokens = (activeMode === "deposit" || activeMode === "send") &&
5445
+ swapStep === "choose-swap-asset"
5446
+ ? (sourcePickerDraftTokens ?? fromTokens)
5447
+ : fromTokens;
4799
5448
  useEffect(() => {
4800
5449
  if (activeMode !== "deposit" && activeMode !== "send")
4801
5450
  return;
@@ -4949,8 +5598,15 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4949
5598
  applyConfiguredPrefillsAfterReset();
4950
5599
  rotateAttempt();
4951
5600
  };
5601
+ const resetSourceSelectionAfterFailure = () => {
5602
+ lastAutoIntentSourceTokensRef.current = [];
5603
+ lastIntentSourceTokensRef.current = [];
5604
+ resetSourcePickerDraft();
5605
+ resetExactOutSourcesToAuto();
5606
+ };
4952
5607
  const handleFailureBack = () => {
4953
5608
  clearPendingSwapIntent();
5609
+ resetSourceSelectionAfterFailure();
4954
5610
  setTxError(null);
4955
5611
  void refreshSelectedSourceBalances();
4956
5612
  setSwapStep("idle");
@@ -4959,7 +5615,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4959
5615
  currentSwapStartedAtRef.current = 0;
4960
5616
  rotateAttempt();
4961
5617
  };
4962
- const resetInputsAfterSuccessfulExecution = () => {
5618
+ const clearTerminalInputState = () => {
4963
5619
  setAmount("");
4964
5620
  setRecipientAddress("");
4965
5621
  setTxError(null);
@@ -4971,6 +5627,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4971
5627
  setSourceSelectionTouched(false);
4972
5628
  setDepositSourceFilter("all");
4973
5629
  setDepositAmountMode("token");
5630
+ };
5631
+ const resetInputsAfterSuccessfulExecution = () => {
5632
+ clearTerminalInputState();
4974
5633
  if (activeMode === "deposit") {
4975
5634
  setSelectedOpportunity(configuredDeposit);
4976
5635
  setToToken(configuredDeposit
@@ -4981,6 +5640,17 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
4981
5640
  setToToken(undefined);
4982
5641
  }
4983
5642
  };
5643
+ const handleTerminalReceiptDone = () => {
5644
+ clearPendingSwapIntent();
5645
+ clearTerminalInputState();
5646
+ void refreshSelectedSourceBalances();
5647
+ setSwapStep("idle");
5648
+ setCurrentSwapId(null);
5649
+ currentSwapIdRef.current = null;
5650
+ currentSwapStartedAtRef.current = 0;
5651
+ amountEnteredLastValueRef.current = "";
5652
+ rotateAttempt();
5653
+ };
4984
5654
  const handleModalOpenChange = useCallback((open) => {
4985
5655
  if (!open && swapStepRef.current === "progress")
4986
5656
  return;
@@ -5031,33 +5701,14 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5031
5701
  throw new Error("No wallet connector available.");
5032
5702
  }
5033
5703
  await connectAsync({ connector: nextConnector });
5034
- activeConnector = nextConnector;
5704
+ return;
5035
5705
  }
5036
- const connectorProvider = await activeConnector
5037
- ?.getProvider()
5038
- .catch(() => undefined);
5039
- const connectorClientProvider = connectorClient
5040
- ? {
5041
- request: (args) => connectorClient.request(args),
5042
- }
5043
- : undefined;
5044
- const walletClientProvider = walletClient
5045
- ? {
5046
- request: (args) => walletClient.request(args),
5047
- }
5048
- : undefined;
5049
- const windowProvider = typeof window !== "undefined"
5050
- ? window.ethereum
5051
- : undefined;
5052
- const effectiveProvider = connectorProvider &&
5053
- typeof connectorProvider.request === "function"
5054
- ? connectorProvider
5055
- : (connectorClientProvider ?? walletClientProvider ?? windowProvider);
5706
+ const effectiveProvider = await getEffectiveWalletProvider(activeConnector);
5056
5707
  if (!effectiveProvider ||
5057
5708
  typeof effectiveProvider.request !== "function") {
5058
5709
  throw new Error("Wallet provider is not ready yet.");
5059
5710
  }
5060
- await handleInit(effectiveProvider);
5711
+ await handleInit(effectiveProvider, ownerAddress);
5061
5712
  }
5062
5713
  catch (error) {
5063
5714
  setTxError(error?.message || "Unable to connect wallet.");
@@ -5267,6 +5918,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5267
5918
  const rawStepType = String(event?.stepType ?? step?.type ?? step?.typeID ?? "").toLowerCase();
5268
5919
  const rawState = String(event?.state ?? "").toLowerCase();
5269
5920
  const explorerUrl = getPlanStepExplorerUrl(event, step);
5921
+ const sourceMovementStep = isSourceMovementProgressStep(type, rawStepType);
5922
+ const hasSourceMovementTransaction = Boolean(explorerUrl || getPlanStepTransactionHash(event, step));
5270
5923
  const intentExplorerUrl = getEventIntentExplorerUrl(appConfig.nexusNetwork, event, step);
5271
5924
  patchCurrentIntentExplorerUrl(intentExplorerUrl);
5272
5925
  if (type === "TRANSACTION_SENT" ||
@@ -5280,12 +5933,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5280
5933
  type === "SWAP_SKIPPED") {
5281
5934
  markSwapExecutionStarted();
5282
5935
  }
5283
- if (PLAN_STEP_FUNDS_MOVED_STATES.has(rawState) &&
5284
- (rawStepType === "source_swap" ||
5285
- rawStepType === "eoa_to_ephemeral_transfer" ||
5286
- rawStepType === "bridge_deposit" ||
5287
- type.includes("SOURCE_SWAP") ||
5288
- type === "BRIDGE_DEPOSIT")) {
5936
+ if (sourceMovementStep &&
5937
+ hasSourceMovementTransaction &&
5938
+ PLAN_STEP_FUNDS_MAY_HAVE_MOVED_STATES.has(rawState)) {
5289
5939
  fundsMovedRef.current = true;
5290
5940
  }
5291
5941
  if (explorerUrl) {
@@ -5296,11 +5946,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5296
5946
  type === "TRANSACTION_CONFIRMED") {
5297
5947
  mergeExplorerUrls({ destinationExplorerUrl: explorerUrl });
5298
5948
  }
5299
- else if (rawStepType === "source_swap" ||
5300
- rawStepType === "eoa_to_ephemeral_transfer" ||
5301
- rawStepType === "bridge_deposit" ||
5302
- type.includes("SOURCE_SWAP") ||
5303
- type === "BRIDGE_DEPOSIT") {
5949
+ else if (sourceMovementStep) {
5304
5950
  mergeExplorerUrls({ sourceExplorerUrl: explorerUrl });
5305
5951
  }
5306
5952
  if (!intentUrlRef.current &&
@@ -5512,6 +6158,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5512
6158
  quoteInputKey,
5513
6159
  runId,
5514
6160
  });
6161
+ startIntentFetchTiming({
6162
+ background,
6163
+ operation: "swapWithExactIn",
6164
+ quoteInputKey,
6165
+ runId,
6166
+ });
5515
6167
  const result = await nexusSDK.swapWithExactIn(exactInSwapPayload, {
5516
6168
  hooks: {
5517
6169
  onIntent: (data) => handleSwapIntentCallback(data, runId, quoteInputKey),
@@ -5540,6 +6192,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5540
6192
  quoteInputKey,
5541
6193
  runId,
5542
6194
  });
6195
+ startIntentFetchTiming({
6196
+ background,
6197
+ operation: "swapWithExactIn",
6198
+ quoteInputKey,
6199
+ runId,
6200
+ });
5543
6201
  const result = await nexusSDK.swapWithExactIn(exactInSwapPayload, {
5544
6202
  hooks: {
5545
6203
  onIntent: (data) => handleSwapIntentCallback(data, runId, quoteInputKey),
@@ -5604,12 +6262,15 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5604
6262
  if (activeMode === "deposit" && selectedOpportunity) {
5605
6263
  const user = (ownerAddress ?? connectedAddress);
5606
6264
  const executeParams = selectedOpportunity.executeDeposit(selectedOpportunity.tokenSymbol, selectedOpportunity.tokenAddress, amountBigInt, selectedOpportunity.chainId, user);
6265
+ if (!isPositiveGasLimit(executeParams.gas)) {
6266
+ throw new Error("Deposit config executeDeposit must return a positive gas limit.");
6267
+ }
5607
6268
  executeConfig = {
5608
6269
  to: executeParams.to,
5609
6270
  value: executeParams.value,
5610
6271
  data: executeParams.data,
5611
6272
  tokenApproval: executeParams.tokenApproval,
5612
- gas: executeParams.gas ?? BigInt(400_000),
6273
+ gas: executeParams.gas,
5613
6274
  };
5614
6275
  }
5615
6276
  else if ((activeMode === "send" || hasCustomSwapRecipient) &&
@@ -5654,6 +6315,14 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5654
6315
  quoteInputKey,
5655
6316
  runId,
5656
6317
  });
6318
+ startIntentFetchTiming({
6319
+ background,
6320
+ operation: isTransferExactOut
6321
+ ? "swapAndTransfer exactOut"
6322
+ : "swapAndExecute exactOut",
6323
+ quoteInputKey,
6324
+ runId,
6325
+ });
5657
6326
  const result = isTransferExactOut
5658
6327
  ? await sdkWithOptionalTransfer.swapAndTransfer(exactOutOperationInput, {
5659
6328
  onEvent,
@@ -5704,6 +6373,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5704
6373
  quoteInputKey,
5705
6374
  runId,
5706
6375
  });
6376
+ startIntentFetchTiming({
6377
+ background,
6378
+ operation: "swapWithExactOut",
6379
+ quoteInputKey,
6380
+ runId,
6381
+ });
5707
6382
  const result = await nexusSDK.swapWithExactOut(exactOutSwapInput, {
5708
6383
  hooks: {
5709
6384
  onIntent: (data) => handleSwapIntentCallback(data, runId, quoteInputKey),
@@ -5758,6 +6433,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5758
6433
  if (swapRunIdRef.current !== runId || !isCurrentQuoteInput()) {
5759
6434
  return;
5760
6435
  }
6436
+ finishIntentFetchTiming(runId, "failed");
5761
6437
  if (activeMode === "deposit" && err?.code !== "USER_DENIED_INTENT") {
5762
6438
  const hasActiveExecution = swapStepRef.current === "progress" &&
5763
6439
  Boolean(currentSwapIdRef.current);
@@ -5779,19 +6455,26 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5779
6455
  ? "no_eligible_sources"
5780
6456
  : !hasActiveExecution
5781
6457
  ? "quote_failed"
5782
- : "execution_failed";
6458
+ : fundsMovedRef.current
6459
+ ? "pending_after_funds_moved"
6460
+ : "execution_failed";
5783
6461
  reachedTerminalRef.current = true;
5784
6462
  if (fundsMovedRef.current) {
5785
- trackDeposit("deposit_partial_movement_detected", {
6463
+ trackDeposit("deposit_pending_after_funds_moved", {
6464
+ errorCode: err?.code ?? "UNKNOWN",
6465
+ errorMessage: errMessage || "Transaction pending.",
5786
6466
  intentUrl: intentUrlRef.current,
6467
+ failedAtStep,
6468
+ });
6469
+ }
6470
+ else {
6471
+ trackDeposit("deposit_failed", {
6472
+ errorCode: err?.code ?? "UNKNOWN",
6473
+ errorCategory,
6474
+ errorMessage: errMessage || "Transaction failed.",
6475
+ failedAtStep,
5787
6476
  });
5788
6477
  }
5789
- trackDeposit("deposit_failed", {
5790
- errorCode: err?.code ?? "UNKNOWN",
5791
- errorCategory,
5792
- errorMessage: errMessage || "Transaction failed.",
5793
- failedAtStep,
5794
- });
5795
6478
  }
5796
6479
  setQuoteRefreshing(false);
5797
6480
  setIntentLoading(false);
@@ -5879,6 +6562,13 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5879
6562
  setTxError(null);
5880
6563
  return;
5881
6564
  }
6565
+ if (hasActiveExecution && fundsMovedRef.current) {
6566
+ showTimeoutReceipt(errorMessage, {
6567
+ failureDescription: "Funds have already moved and the transaction is still pending. Check the intent explorer for the latest status.",
6568
+ });
6569
+ setTxError(null);
6570
+ return;
6571
+ }
5882
6572
  if (hasActiveExecution) {
5883
6573
  showFailedProgressThenReceipt(errorMessage);
5884
6574
  }
@@ -5896,7 +6586,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5896
6586
  return;
5897
6587
  if (syncingIntentSourcesRef.current) {
5898
6588
  syncingIntentSourcesRef.current = false;
5899
- return;
6589
+ if (hasCurrentQuoteIntent) {
6590
+ setIntentLoading(false);
6591
+ setQuoteRefreshing(false);
6592
+ setReceiveMaxCalculating(false);
6593
+ return;
6594
+ }
5900
6595
  }
5901
6596
  if (hasReceiveAmountQuoteIssue) {
5902
6597
  clearPendingSwapIntent(true);
@@ -5926,10 +6621,11 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5926
6621
  clearPendingSwapIntent(true, { keepQuoteRefreshing: true });
5927
6622
  setQuoteRefreshing(true);
5928
6623
  let quoteStarted = false;
6624
+ const quoteDelay = getQuoteRequestDelay();
5929
6625
  const timer = window.setTimeout(() => {
5930
6626
  quoteStarted = true;
5931
6627
  void handleEnterPreview({ background: true });
5932
- }, EXACT_OUT_INPUT_DEBOUNCE_MS);
6628
+ }, quoteDelay);
5933
6629
  return () => {
5934
6630
  window.clearTimeout(timer);
5935
6631
  if (syncingIntentSourcesRef.current)
@@ -5947,6 +6643,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5947
6643
  hasCurrentQuoteIntent,
5948
6644
  hasInsufficientSourcesQuoteIssue,
5949
6645
  hasReceiveAmountQuoteIssue,
6646
+ getQuoteRequestDelay,
5950
6647
  nexusSDK,
5951
6648
  recipientAddress,
5952
6649
  swapStep,
@@ -5957,7 +6654,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5957
6654
  return;
5958
6655
  if (syncingIntentSourcesRef.current) {
5959
6656
  syncingIntentSourcesRef.current = false;
5960
- return;
6657
+ if (hasCurrentQuoteIntent) {
6658
+ setIntentLoading(false);
6659
+ setQuoteRefreshing(false);
6660
+ setReceiveMaxCalculating(false);
6661
+ return;
6662
+ }
5961
6663
  }
5962
6664
  if (hasReceiveAmountQuoteIssue) {
5963
6665
  clearPendingSwapIntent(true);
@@ -5990,10 +6692,11 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
5990
6692
  clearPendingSwapIntent(true, { keepQuoteRefreshing: true });
5991
6693
  setQuoteRefreshing(true);
5992
6694
  let quoteStarted = false;
6695
+ const quoteDelay = getQuoteRequestDelay();
5993
6696
  const timer = window.setTimeout(() => {
5994
6697
  quoteStarted = true;
5995
6698
  void handleEnterPreview({ background: true });
5996
- }, EXACT_OUT_INPUT_DEBOUNCE_MS);
6699
+ }, quoteDelay);
5997
6700
  return () => {
5998
6701
  window.clearTimeout(timer);
5999
6702
  if (syncingIntentSourcesRef.current)
@@ -6011,6 +6714,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6011
6714
  hasCurrentQuoteIntent,
6012
6715
  hasInsufficientSourcesQuoteIssue,
6013
6716
  hasReceiveAmountQuoteIssue,
6717
+ getQuoteRequestDelay,
6014
6718
  nexusSDK,
6015
6719
  sourceSelectionRevision,
6016
6720
  selectedOpportunityIdentity,
@@ -6022,7 +6726,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6022
6726
  return;
6023
6727
  if (syncingIntentSourcesRef.current) {
6024
6728
  syncingIntentSourcesRef.current = false;
6025
- return;
6729
+ if (hasCurrentQuoteIntent) {
6730
+ setIntentLoading(false);
6731
+ setQuoteRefreshing(false);
6732
+ setReceiveMaxCalculating(false);
6733
+ return;
6734
+ }
6026
6735
  }
6027
6736
  if (hasReceiveAmountQuoteIssue) {
6028
6737
  clearPendingSwapIntent(true);
@@ -6052,10 +6761,11 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6052
6761
  clearPendingSwapIntent(true, { keepQuoteRefreshing: true });
6053
6762
  setQuoteRefreshing(true);
6054
6763
  let quoteStarted = false;
6764
+ const quoteDelay = getQuoteRequestDelay();
6055
6765
  const timer = window.setTimeout(() => {
6056
6766
  quoteStarted = true;
6057
6767
  void handleEnterPreview({ background: true });
6058
- }, EXACT_OUT_INPUT_DEBOUNCE_MS);
6768
+ }, quoteDelay);
6059
6769
  return () => {
6060
6770
  window.clearTimeout(timer);
6061
6771
  if (syncingIntentSourcesRef.current)
@@ -6071,6 +6781,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6071
6781
  hasCurrentQuoteIntent,
6072
6782
  hasInsufficientSourcesQuoteIssue,
6073
6783
  hasReceiveAmountQuoteIssue,
6784
+ getQuoteRequestDelay,
6074
6785
  nexusSDK,
6075
6786
  sourceSelectionRevision,
6076
6787
  swapStep,
@@ -6102,6 +6813,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6102
6813
  try {
6103
6814
  const updatedRaw = await activeIntent.refresh();
6104
6815
  const updatedBridgeProvider = normalizeBridgeProvider(updatedRaw?.bridgeProvider ??
6816
+ updatedRaw?.normalizedIntent?.bridgeProvider ??
6105
6817
  updatedRaw?.swap?.bridgeProvider ??
6106
6818
  activeIntent.intent?.bridgeProvider);
6107
6819
  const updated = normalizeRenderableSwapIntentData(updatedRaw, updatedBridgeProvider);
@@ -6330,7 +7042,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6330
7042
  return;
6331
7043
  }
6332
7044
  if (swapStep === "choose-swap-asset") {
6333
- closeDrawerToIdle();
7045
+ handleSourcePickerCancel();
6334
7046
  return;
6335
7047
  }
6336
7048
  if (swapStep === "choose-receive-asset") {
@@ -6472,7 +7184,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6472
7184
  if (parsedAmount.gt(0) && rate.gt(0)) {
6473
7185
  const converted = depositAmountMode === "token"
6474
7186
  ? parsedAmount.mul(rate).toDecimalPlaces(2)
6475
- : parsedAmount.div(rate).toDecimalPlaces(toToken?.decimals ?? 18);
7187
+ : parsedAmount
7188
+ .div(rate)
7189
+ .toDecimalPlaces(getCappedTokenDisplayDecimals(toToken?.decimals), Decimal.ROUND_DOWN);
6476
7190
  setAmount(converted.toFixed());
6477
7191
  }
6478
7192
  clearPendingSwapIntent();
@@ -6498,87 +7212,33 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6498
7212
  ? "percent_75"
6499
7213
  : "percent_max";
6500
7214
  if (pct !== 100) {
6501
- const usdAmount = getTotalBalancePercentUsdAmount(pct);
6502
- const shouldUseMaxQuoteFallback = depositAmountMode === "usd" && getDepositTokenUsdRate().lte(0);
6503
- const nextAmount = depositAmountMode === "usd"
6504
- ? usdAmount.toDecimalPlaces(2, Decimal.ROUND_DOWN).toFixed()
6505
- : formatTokenAmountFromUsd(usdAmount, toToken);
6506
- if (nextAmount && !shouldUseMaxQuoteFallback) {
7215
+ const nextAmount = getExactOutPercentAmountFromBalance(toToken, pct, depositAmountMode === "usd");
7216
+ if (nextAmount) {
6507
7217
  setQuoteRefreshing(false);
6508
7218
  setReceiveMaxCalculating(false);
6509
7219
  setMaxCalculationPercent(null);
6510
- handleDepositAmountChange(nextAmount);
7220
+ setDepositAmountMode(nextAmount.mode);
7221
+ handleDepositAmountChange(nextAmount.amount);
6511
7222
  return;
6512
7223
  }
6513
7224
  setQuoteRefreshing(false);
6514
- setReceiveMaxCalculating(true);
6515
- setMaxCalculationPercent(pct);
6516
- try {
6517
- await waitForNextPaint();
6518
- const fallback = await getPercentAmountFromMaxQuote(toToken, pct, depositAmountMode === "usd");
6519
- if (runId !== maxPercentRunRef.current)
6520
- return;
6521
- if (!fallback) {
6522
- setQuoteRefreshing(false);
6523
- setReceiveMaxCalculating(false);
6524
- setMaxCalculationPercent(null);
6525
- setTxError("Unable to calculate this percentage for the deposit asset.");
6526
- return;
6527
- }
6528
- setDepositAmountMode(fallback.mode);
6529
- setReceiveMaxCalculating(false);
6530
- setMaxCalculationPercent(null);
6531
- handleDepositAmountChange(fallback.amount);
6532
- }
6533
- catch (error) {
6534
- if (runId !== maxPercentRunRef.current)
6535
- return;
6536
- console.error("Unable to calculate percentage deposit amount", error);
6537
- setReceiveMaxCalculating(false);
6538
- setMaxCalculationPercent(null);
6539
- setQuoteRefreshing(false);
6540
- if (isInsufficientSourcesError(error)) {
6541
- setSwapQuoteIssue(buildInsufficientSourcesIssue(error));
6542
- return;
6543
- }
6544
- setTxError(error?.message ||
6545
- "Unable to calculate this percentage for the deposit asset.");
6546
- }
6547
- return;
6548
- }
6549
- setQuoteRefreshing(false);
6550
- setReceiveMaxCalculating(true);
6551
- setMaxCalculationPercent(100);
6552
- try {
6553
- await waitForNextPaint();
6554
- const maxAmount = await getPercentAmountFromMaxQuote(toToken, 100, depositAmountMode === "usd");
6555
- if (runId !== maxPercentRunRef.current)
6556
- return;
6557
- if (!maxAmount) {
6558
- setReceiveMaxCalculating(false);
6559
- setMaxCalculationPercent(null);
6560
- setQuoteRefreshing(false);
6561
- setTxError("No depositable amount is available for this deposit.");
6562
- return;
6563
- }
6564
- setDepositAmountMode(maxAmount.mode);
6565
7225
  setReceiveMaxCalculating(false);
6566
7226
  setMaxCalculationPercent(null);
6567
- handleDepositAmountChange(maxAmount.amount);
7227
+ setTxError("Unable to calculate this percentage for the deposit asset.");
7228
+ return;
6568
7229
  }
6569
- catch (error) {
6570
- if (runId !== maxPercentRunRef.current)
6571
- return;
6572
- console.error("Unable to calculate max deposit amount", error);
6573
- setReceiveMaxCalculating(false);
6574
- setMaxCalculationPercent(null);
6575
- setQuoteRefreshing(false);
6576
- if (isInsufficientSourcesError(error)) {
6577
- setSwapQuoteIssue(buildInsufficientSourcesIssue(error));
6578
- return;
6579
- }
6580
- setTxError(error?.message || "Unable to calculate the max deposit amount.");
7230
+ setQuoteRefreshing(false);
7231
+ setReceiveMaxCalculating(false);
7232
+ setMaxCalculationPercent(null);
7233
+ const maxAmount = getExactOutPercentAmountFromBalance(toToken, 100, depositAmountMode === "usd");
7234
+ if (runId !== maxPercentRunRef.current)
7235
+ return;
7236
+ if (!maxAmount) {
7237
+ setTxError("No depositable amount is available for this deposit.");
7238
+ return;
6581
7239
  }
7240
+ setDepositAmountMode(maxAmount.mode);
7241
+ handleDepositAmountChange(maxAmount.amount);
6582
7242
  };
6583
7243
  const handleSendPercentSelect = async (pct) => {
6584
7244
  if (isConfiguredAmountFixed)
@@ -6600,82 +7260,31 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6600
7260
  ? "percent_75"
6601
7261
  : "percent_max";
6602
7262
  if (pct !== 100) {
6603
- const usdAmount = getTotalBalancePercentUsdAmount(pct);
6604
- const nextAmount = formatTokenAmountFromUsd(usdAmount, toToken);
7263
+ const nextAmount = getExactOutPercentAmountFromBalance(toToken, pct, false);
6605
7264
  if (nextAmount) {
6606
7265
  setQuoteRefreshing(false);
6607
7266
  setReceiveMaxCalculating(false);
6608
7267
  setMaxCalculationPercent(null);
6609
- handleSendAmountChange(nextAmount);
7268
+ handleSendAmountChange(nextAmount.amount);
6610
7269
  return;
6611
7270
  }
6612
7271
  setQuoteRefreshing(false);
6613
- setReceiveMaxCalculating(true);
6614
- setMaxCalculationPercent(pct);
6615
- try {
6616
- await waitForNextPaint();
6617
- const fallback = await getPercentAmountFromMaxQuote(toToken, pct, false);
6618
- if (runId !== maxPercentRunRef.current)
6619
- return;
6620
- if (!fallback) {
6621
- setQuoteRefreshing(false);
6622
- setReceiveMaxCalculating(false);
6623
- setMaxCalculationPercent(null);
6624
- setTxError("Unable to calculate this percentage for the send asset.");
6625
- return;
6626
- }
6627
- setReceiveMaxCalculating(false);
6628
- setMaxCalculationPercent(null);
6629
- handleSendAmountChange(fallback.amount);
6630
- }
6631
- catch (error) {
6632
- if (runId !== maxPercentRunRef.current)
6633
- return;
6634
- console.error("Unable to calculate percentage send amount", error);
6635
- setReceiveMaxCalculating(false);
6636
- setMaxCalculationPercent(null);
6637
- setQuoteRefreshing(false);
6638
- if (isInsufficientSourcesError(error)) {
6639
- setSwapQuoteIssue(buildInsufficientSourcesIssue(error));
6640
- return;
6641
- }
6642
- setTxError(error?.message ||
6643
- "Unable to calculate this percentage for the send asset.");
6644
- }
6645
- return;
6646
- }
6647
- setQuoteRefreshing(false);
6648
- setReceiveMaxCalculating(true);
6649
- setMaxCalculationPercent(100);
6650
- try {
6651
- await waitForNextPaint();
6652
- const maxAmount = await getPercentAmountFromMaxQuote(toToken, 100, false);
6653
- if (runId !== maxPercentRunRef.current)
6654
- return;
6655
- if (!maxAmount) {
6656
- setReceiveMaxCalculating(false);
6657
- setMaxCalculationPercent(null);
6658
- setQuoteRefreshing(false);
6659
- setTxError("No transferable amount is available for this asset.");
6660
- return;
6661
- }
6662
7272
  setReceiveMaxCalculating(false);
6663
7273
  setMaxCalculationPercent(null);
6664
- handleSendAmountChange(maxAmount.amount);
7274
+ setTxError("Unable to calculate this percentage for the send asset.");
7275
+ return;
6665
7276
  }
6666
- catch (error) {
6667
- if (runId !== maxPercentRunRef.current)
6668
- return;
6669
- console.error("Unable to calculate max send amount", error);
6670
- setReceiveMaxCalculating(false);
6671
- setMaxCalculationPercent(null);
6672
- setQuoteRefreshing(false);
6673
- if (isInsufficientSourcesError(error)) {
6674
- setSwapQuoteIssue(buildInsufficientSourcesIssue(error));
6675
- return;
6676
- }
6677
- setTxError(error?.message || "Unable to calculate the max send amount.");
7277
+ setQuoteRefreshing(false);
7278
+ setReceiveMaxCalculating(false);
7279
+ setMaxCalculationPercent(null);
7280
+ const maxAmount = getExactOutPercentAmountFromBalance(toToken, 100, false);
7281
+ if (runId !== maxPercentRunRef.current)
7282
+ return;
7283
+ if (!maxAmount) {
7284
+ setTxError("No transferable amount is available for this asset.");
7285
+ return;
6678
7286
  }
7287
+ handleSendAmountChange(maxAmount.amount);
6679
7288
  };
6680
7289
  // ---------------------------------------------------------------------------
6681
7290
  // Render
@@ -6706,6 +7315,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6706
7315
  const hasPositiveRootAmount = hasPositiveDecimalInput(amount);
6707
7316
  const hasReadySwapQuoteInput = hasReadyExactInSwapInput(fromTokens, toToken);
6708
7317
  const needsWalletConnection = !ownerAddress || !nexusSDK;
7318
+ const isExactOutPaymentQuotePending = isExactOutPaymentFlow && (quoteRefreshing || intentLoading);
6709
7319
  const walletConnectBusy = walletActionPending ||
6710
7320
  nexusLoading ||
6711
7321
  isWalletConnectPending ||
@@ -6727,10 +7337,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6727
7337
  : !hasPositiveRootAmount ||
6728
7338
  !toToken ||
6729
7339
  receiveMaxCalculating ||
7340
+ isExactOutPaymentQuotePending ||
6730
7341
  (!hasCurrentExactOutPaymentIntent &&
6731
- (quoteRefreshing ||
6732
- intentLoading ||
6733
- isQuoteUnavailableForAutoSourceFlow)) ||
7342
+ isQuoteUnavailableForAutoSourceFlow) ||
6734
7343
  Boolean(blockingQuoteIssue);
6735
7344
  const sendNeedsRecipient = activeMode === "send" && !recipientAddress;
6736
7345
  const isSendCtaDisabled = needsWalletConnection
@@ -6740,10 +7349,9 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6740
7349
  hasSameOwnerSendRecipient ||
6741
7350
  receiveMaxCalculating ||
6742
7351
  (!sendNeedsRecipient &&
6743
- !hasCurrentExactOutPaymentIntent &&
6744
- (quoteRefreshing ||
6745
- intentLoading ||
6746
- isQuoteUnavailableForAutoSourceFlow)) ||
7352
+ (isExactOutPaymentQuotePending ||
7353
+ (!hasCurrentExactOutPaymentIntent &&
7354
+ isQuoteUnavailableForAutoSourceFlow))) ||
6747
7355
  Boolean(blockingQuoteIssue);
6748
7356
  const quoteCtaLabel = (fallback) => {
6749
7357
  if (needsWalletConnection)
@@ -6756,7 +7364,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6756
7364
  return configuredAmountIssue.ctaLabel;
6757
7365
  if (receiveMaxCalculating)
6758
7366
  return "Calculating...";
6759
- if (!hasCurrentExactOutPaymentIntent && (quoteRefreshing || intentLoading)) {
7367
+ if (isExactOutPaymentQuotePending ||
7368
+ (!hasCurrentExactOutPaymentIntent && (quoteRefreshing || intentLoading))) {
6760
7369
  return "Fetching quotes...";
6761
7370
  }
6762
7371
  if (isQuoteUnavailableForAutoSourceFlow)
@@ -6822,6 +7431,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6822
7431
  const previewToAmountUsd = previewDestinationUsdNumber && previewDestinationUsdNumber.gt(0)
6823
7432
  ? previewDestinationUsdNumber.toDecimalPlaces(6).toFixed()
6824
7433
  : undefined;
7434
+ const previewDestinationGasFeeUsd = (() => {
7435
+ const value = getIntentDestinationGasUsdValue(intentData);
7436
+ return value && value.gt(0)
7437
+ ? value.toDecimalPlaces(6, Decimal.ROUND_DOWN).toFixed()
7438
+ : undefined;
7439
+ })();
6825
7440
  const predictiveExactInQuote = predictiveQuote?.mode === "exactIn" &&
6826
7441
  predictiveQuote.key === getPredictiveQuoteCacheKey("swap", "exactIn")
6827
7442
  ? predictiveQuote
@@ -6830,13 +7445,20 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6830
7445
  predictiveQuote.key === getPredictiveQuoteCacheKey(activeMode, "exactOut")
6831
7446
  ? predictiveQuote
6832
7447
  : null;
7448
+ const shouldUseCurrentExactOutIntentSources = isExactOutPaymentFlow && hasCurrentQuoteIntent && hasIntentSources;
7449
+ const currentExactOutIntentSourceTokens = shouldUseCurrentExactOutIntentSources
7450
+ ? sortSwapTokensByUsdDesc((intentData?.sources ?? [])
7451
+ .map(buildIntentSourceToken)
7452
+ .filter(hasPositiveSourceSpend))
7453
+ : [];
6833
7454
  const resolvedToToken = toToken ??
6834
7455
  (activeMode === "deposit" && selectedOpportunity
6835
7456
  ? toTokenFromOpportunity(selectedOpportunity)
6836
7457
  : undefined);
6837
- const toTokenWithFetchedBalance = resolvedToToken && destinationBalance
6838
- ? { ...resolvedToToken, balance: destinationBalance }
6839
- : resolvedToToken;
7458
+ const enrichedResolvedToToken = enrichTokenOptionLogo(resolvedToToken);
7459
+ const toTokenWithFetchedBalance = enrichedResolvedToToken && destinationBalance
7460
+ ? { ...enrichedResolvedToToken, balance: destinationBalance }
7461
+ : enrichedResolvedToToken;
6840
7462
  const idleReceiveQuoteAmount = activeMode === "swap" && swapType === "exactIn"
6841
7463
  ? (intentToAmount ?? predictiveExactInQuote?.toAmount)
6842
7464
  : undefined;
@@ -6861,14 +7483,18 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6861
7483
  Boolean(predictiveExactOutQuote &&
6862
7484
  ((predictiveExactOutQuote.sources?.length ?? 0) > 0 ||
6863
7485
  destinationBalanceDisplayToken));
6864
- const baseDisplayFromTokens = shouldShowPredictiveExactOutDisplay
6865
- ? (predictiveExactOutQuote?.sources ?? fromTokens)
6866
- : fromTokens;
7486
+ const baseDisplayFromTokens = shouldUseCurrentExactOutIntentSources
7487
+ ? currentExactOutIntentSourceTokens
7488
+ : shouldShowPredictiveExactOutDisplay
7489
+ ? (predictiveExactOutQuote?.sources ?? fromTokens)
7490
+ : fromTokens;
6867
7491
  const displayFromTokens = (() => {
6868
- if (!destinationBalanceDisplayToken ||
6869
- (activeMode !== "deposit" && activeMode !== "send")) {
7492
+ if (activeMode !== "deposit" && activeMode !== "send") {
6870
7493
  return baseDisplayFromTokens;
6871
7494
  }
7495
+ if (!destinationBalanceDisplayToken) {
7496
+ return mergeDisplaySourceTokens(sortDisplaySourcesByBalanceUsdDesc(baseDisplayFromTokens));
7497
+ }
6872
7498
  const destinationKey = getTokenSelectionKey(destinationBalanceDisplayToken);
6873
7499
  let replacedEmptyDestinationToken = false;
6874
7500
  const tokens = baseDisplayFromTokens.map((token) => {
@@ -6881,21 +7507,124 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6881
7507
  }
6882
7508
  return token;
6883
7509
  });
6884
- return replacedEmptyDestinationToken
7510
+ const displayTokens = replacedEmptyDestinationToken
6885
7511
  ? tokens
6886
7512
  : [...tokens, destinationBalanceDisplayToken];
7513
+ return mergeDisplaySourceTokens(sortDisplaySourcesByBalanceUsdDesc(displayTokens));
6887
7514
  })();
6888
7515
  const displayExactOutRouteLoading = isExactOutRouteLoading && !shouldShowPredictiveExactOutDisplay;
6889
- const totalSwapBalanceUsd = getSwapBalanceTotalUsd()
7516
+ const isSourcePickerRefreshDisabled = quoteRefreshing || intentLoading || previewQuoteRefreshing;
7517
+ const isIntentSourceUiActionable = activeMode === "deposit"
7518
+ ? !isDepositCtaDisabled
7519
+ : activeMode === "send"
7520
+ ? !isSendCtaDisabled
7521
+ : !isSwapCtaDisabled;
7522
+ useEffect(() => {
7523
+ if (!intentFetchTiming?.endTimeMs) {
7524
+ return;
7525
+ }
7526
+ if (latestIntentFetchRunIdRef.current !== intentFetchTiming.runId) {
7527
+ return;
7528
+ }
7529
+ if (intentUiRenderLoggedRunIdRef.current === intentFetchTiming.runId) {
7530
+ return;
7531
+ }
7532
+ const hasRenderedSources = displayFromTokens.length > 0 ||
7533
+ hasIntentSources ||
7534
+ Boolean(intentData?.destination);
7535
+ if (!hasRenderedSources)
7536
+ return;
7537
+ if (isSwapBalancePending ||
7538
+ isSourcePickerRefreshDisabled ||
7539
+ quoteRefreshing ||
7540
+ intentLoading ||
7541
+ previewQuoteRefreshing ||
7542
+ receiveMaxCalculating ||
7543
+ displayExactOutRouteLoading ||
7544
+ !isIntentSourceUiActionable) {
7545
+ return;
7546
+ }
7547
+ const renderTimeMs = Date.now();
7548
+ const renderDurationMs = renderTimeMs - intentFetchTiming.startTimeMs;
7549
+ const fetchDurationMs = intentFetchTiming.fetchDurationMs ??
7550
+ intentFetchTiming.endTimeMs - intentFetchTiming.startTimeMs;
7551
+ const differenceMs = renderDurationMs - fetchDurationMs;
7552
+ console.log("[NexusWidget] intent sources UI render complete", {
7553
+ runId: intentFetchTiming.runId,
7554
+ walletAddress: intentFetchTiming.walletAddress,
7555
+ operation: intentFetchTiming.operation,
7556
+ quoteInputKey: intentFetchTiming.quoteInputKey,
7557
+ sourceCount: displayFromTokens.length,
7558
+ dateNow: renderTimeMs,
7559
+ date: new Date(renderTimeMs),
7560
+ totalRenderTimeSeconds: renderDurationMs / 1000,
7561
+ elapsedMs: renderDurationMs,
7562
+ });
7563
+ console.log("[NexusWidget] intent render/fetch difference", {
7564
+ runId: intentFetchTiming.runId,
7565
+ walletAddress: intentFetchTiming.walletAddress,
7566
+ operation: intentFetchTiming.operation,
7567
+ quoteInputKey: intentFetchTiming.quoteInputKey,
7568
+ differenceMs,
7569
+ dateNow: renderTimeMs,
7570
+ date: new Date(renderTimeMs),
7571
+ });
7572
+ intentUiRenderLoggedRunIdRef.current = intentFetchTiming.runId;
7573
+ }, [
7574
+ displayExactOutRouteLoading,
7575
+ displayFromTokens.length,
7576
+ hasIntentSources,
7577
+ intentLoading,
7578
+ intentData?.destination,
7579
+ intentFetchTiming,
7580
+ isDepositCtaDisabled,
7581
+ isIntentSourceUiActionable,
7582
+ isSendCtaDisabled,
7583
+ isSourcePickerRefreshDisabled,
7584
+ isSwapCtaDisabled,
7585
+ isSwapBalancePending,
7586
+ previewQuoteRefreshing,
7587
+ quoteRefreshing,
7588
+ receiveMaxCalculating,
7589
+ ]);
7590
+ const totalSwapBalanceUsd = getActiveTotalBalanceUsd()
6890
7591
  .toDecimalPlaces(2)
6891
7592
  .toFixed();
6892
- const sendAmountUsd = amount && toToken
7593
+ const requestedOutputUsd = amount && toToken
6893
7594
  ? getTokenUsdValue({
6894
7595
  ...toToken,
6895
7596
  userAmount: amount,
6896
7597
  userAmountMode: "token",
6897
- }, amount).toNumber()
6898
- : 0;
7598
+ }, amount)
7599
+ : new Decimal(0);
7600
+ const sendAmountUsd = requestedOutputUsd.toNumber();
7601
+ const exactOutRequiredUsdAmount = (() => {
7602
+ if (activeMode !== "deposit" && activeMode !== "send")
7603
+ return undefined;
7604
+ const issueMissingUsd = parseFiatNumber(insufficientSourceIssue?.missingUsd);
7605
+ if (issueMissingUsd && issueMissingUsd.gt(0)) {
7606
+ return getExactOutAvailableSourceUsd().plus(issueMissingUsd);
7607
+ }
7608
+ const requiredFundingUsd = getExactOutRequiredFundingUsd();
7609
+ if (requiredFundingUsd?.gt(0))
7610
+ return requiredFundingUsd;
7611
+ if (requestedOutputUsd.gt(0))
7612
+ return requestedOutputUsd;
7613
+ return undefined;
7614
+ })();
7615
+ const exactOutRequiredUsdDisplay = exactOutRequiredUsdAmount
7616
+ ?.toDecimalPlaces(2)
7617
+ .toFixed();
7618
+ const assetSelectorRequiredUsdDisplay = (activeMode === "deposit" || activeMode === "send") &&
7619
+ (swapStep === "choose-swap-asset" ||
7620
+ closingDrawerStep === "choose-swap-asset")
7621
+ ? (sourceSelectionRequiredUsdDisplay ?? exactOutRequiredUsdDisplay)
7622
+ : exactOutRequiredUsdDisplay;
7623
+ const captureSourceSelectionRequiredUsd = () => {
7624
+ if (activeMode !== "deposit" && activeMode !== "send")
7625
+ return;
7626
+ setSourceSelectionRequiredUsdDisplay(exactOutRequiredUsdDisplay);
7627
+ };
6899
7628
  const isIdleSwapQuoteLoading = activeMode === "swap" &&
6900
7629
  swapStep === "idle" &&
6901
7630
  (quoteRefreshing || intentLoading);
@@ -6939,6 +7668,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6939
7668
  swapStep === "choose-receive-asset" ||
6940
7669
  swapStep === "enter-recipient" ||
6941
7670
  closingDrawerStep !== null;
7671
+ const isPreviewIntentScreen = swapStep === "preview-intent";
6942
7672
  const displayedRootContentHeight = hasMeasuredRootContent && rootContentHeight
6943
7673
  ? Math.max(rootContentHeight, tokenAssetDrawerMinRootContentHeight)
6944
7674
  : null;
@@ -6976,8 +7706,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
6976
7706
  lineHeight: "17px",
6977
7707
  margin: "auto",
6978
7708
  overflowX: "hidden",
6979
- overflowY: isDrawerOverlayActive ? "hidden" : "auto",
6980
- overscrollBehavior: isDrawerOverlayActive ? "contain" : "auto",
7709
+ overflowY: isDrawerOverlayActive || isPreviewIntentScreen ? "hidden" : "auto",
7710
+ overscrollBehavior: isDrawerOverlayActive || isPreviewIntentScreen ? "contain" : "auto",
6981
7711
  padding: "16px",
6982
7712
  scrollbarColor: `${theme.colors.textEmpty} transparent`,
6983
7713
  scrollbarWidth: "thin",
@@ -7086,7 +7816,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7086
7816
  swapStep !== "choose-swap-asset" &&
7087
7817
  swapStep !== "choose-receive-asset" &&
7088
7818
  swapStep !== "enter-recipient" && (_jsxs(_Fragment, { children: [swapStep === "preview-intent" && (_jsx("div", { className: "w-full", style: {
7089
- maxHeight: "calc(90dvh - 66px)",
7819
+ maxHeight: "calc(90dvh - 92px)",
7090
7820
  minHeight: 0,
7091
7821
  overflowX: "hidden",
7092
7822
  overflowY: isPreviewTransitioning ? "hidden" : "auto",
@@ -7094,7 +7824,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7094
7824
  scrollbarColor: "#C8C8C7 transparent",
7095
7825
  scrollbarWidth: "thin",
7096
7826
  width: "100%",
7097
- }, children: _jsx(SwapIntentPreview, { activeMode: activeMode, estimatedTime: "10s", explorerUrls: explorerUrls, fromAmount: amount, fromAmountUsd: previewFromAmountUsd, fromToken: fromTokens[0], fromTokens: fromTokens, intentData: intentData, isLoading: intentLoading, isRefreshing: previewQuoteRefreshing, mode: activeMode, onAccept: handleSwapAccept, onReject: () => {
7827
+ }, children: _jsx(SwapIntentPreview, { activeMode: activeMode, destinationGasFeeUsd: previewDestinationGasFeeUsd, estimatedTime: "10s", explorerUrls: explorerUrls, fromAmount: amount, fromAmountUsd: previewFromAmountUsd, fromToken: fromTokens[0], fromTokens: fromTokens, intentData: intentData, isLoading: intentLoading, isRefreshing: previewQuoteRefreshing, mode: activeMode, onAccept: handleSwapAccept, onReject: () => {
7098
7828
  clearPendingSwapIntent();
7099
7829
  setSwapStep("idle");
7100
7830
  }, onTransitionChange: setIsPreviewTransitioning, opportunity: selectedOpportunity, recipientAddress: transferRecipientAddress, steps: steps, supportedTokenAssets: supportedChainsAndTokens, swapBalances: swapBalance, swapType: swapType, toAmount: previewDestinationAmount, toAmountTokens: previewDestinationAmount
@@ -7102,13 +7832,13 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7102
7832
  : undefined, toAmountUsd: previewToAmountUsd, toToken: toTokenWithFetchedBalance, totalFeeUsd: intentFeeUsd }) })), swapStep === "progress" && (_jsx(NexusWidgetProgressScreen, { failedStep: failedProgressStep, fromAmountUsd: previewFromAmountUsd, fromTokens: fromTokens, intentData: intentData, mode: activeMode, opportunity: selectedOpportunity, progressEvents: progressEvents, recipientAddress: transferRecipientAddress, steps: steps, toAmount: previewDestinationAmount, toAmountUsd: previewToAmountUsd, toToken: toTokenWithFetchedBalance })), (swapStep === "success" || swapStep === "failed") &&
7103
7833
  currentSwapEntry && (_jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4 duration-500 w-full", children: _jsx(SwapReceiptPanel, { entry: currentSwapEntry, onDone: swapStep === "failed"
7104
7834
  ? handleFailureBack
7105
- : handleReset }) }))] })), swapStep === "history" && (_jsx(SwapHistoryPanel, { entries: swapHistory, now: historyNow })), activeMode === "swap" &&
7835
+ : handleTerminalReceiptDone }) }))] })), swapStep === "history" && (_jsx(SwapHistoryPanel, { entries: swapHistory, now: historyNow })), activeMode === "swap" &&
7106
7836
  [
7107
7837
  "idle",
7108
7838
  "choose-swap-asset",
7109
7839
  "choose-receive-asset",
7110
7840
  "enter-recipient",
7111
- ].includes(swapStep) && (_jsxs(_Fragment, { children: [_jsx(SwapIdleForm, { amount: amount, defaultRecipientAddress: defaultRecipientAddress, fromTokens: fromTokens, isReceiveAmountLoading: isReceiveAmountLoading, isReceiveUsdLoading: isReceiveUsdLoading, isAmountReadOnly: isConfiguredAmountFixed, isDestinationPickerDisabled: isReceiveAssetPickerLocked, hideDestinationTokenDropdownIcon: hideDestinationTokenDropdownIcon, onAmountChange: (val, panel) => {
7841
+ ].includes(swapStep) && (_jsxs(_Fragment, { children: [_jsx(SwapIdleForm, { amount: amount, defaultRecipientAddress: defaultRecipientAddress, fromTokens: fromTokens, isReceiveAmountLoading: isReceiveAmountLoading, isReceiveUsdLoading: isReceiveUsdLoading, isAmountReadOnly: isConfiguredAmountFixed, isBalanceLoading: isSwapBalancePending, isDestinationPickerDisabled: isReceiveAssetPickerLocked, hideDestinationTokenDropdownIcon: hideDestinationTokenDropdownIcon, onAmountChange: (val, panel) => {
7112
7842
  handleSwapAmountChange(val, panel);
7113
7843
  }, onOpenDestPicker: () => openDrawerStep("choose-receive-asset"), onOpenRecipientPicker: handleOpenRecipientEditor, onOpenSourcePicker: (index) => {
7114
7844
  if (needsWalletConnection) {
@@ -7116,6 +7846,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7116
7846
  return;
7117
7847
  }
7118
7848
  setEditingAssetIndex(index ?? null);
7849
+ beginSourcePickerEdit();
7119
7850
  openDrawerStep("choose-swap-asset");
7120
7851
  }, onUpdateTokens: handleSwapTokensUpdate, receiveQuoteAmount: swapType === "exactIn" ? idleReceiveQuoteAmount : undefined, receiveQuoteUsd: idleReceiveQuoteUsd, recipientAddress: effectiveRecipientAddress, sourceRouteMessage: insufficientSourceIssue?.message, sourceRouteStatus: insufficientSourceIssue
7121
7852
  ? "insufficient"
@@ -7187,12 +7918,14 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7187
7918
  "choose-swap-asset",
7188
7919
  "choose-receive-asset",
7189
7920
  "enter-recipient",
7190
- ].includes(swapStep) && (_jsx(_Fragment, { children: selectedOpportunity && (_jsxs(_Fragment, { children: [_jsx(DepositIdleForm, { amount: amount, amountMode: depositAmountMode, calculatingPercent: maxCalculationPercent, fromTokens: displayFromTokens, isAmountReadOnly: isConfiguredAmountFixed, isCalculatingMax: receiveMaxCalculating, isQuoteRefreshing: !hasCurrentExactOutPaymentIntent &&
7191
- (quoteRefreshing || intentLoading), onAmountChange: handleDepositAmountChange, onAmountModeToggle: handleDepositAmountModeToggle, onOpenSourcePicker: () => {
7921
+ ].includes(swapStep) && (_jsx(_Fragment, { children: selectedOpportunity && (_jsxs(_Fragment, { children: [_jsx(DepositIdleForm, { amount: amount, amountMode: depositAmountMode, calculatingPercent: maxCalculationPercent, fromTokens: displayFromTokens, isAmountReadOnly: isConfiguredAmountFixed, isBalanceLoading: isSwapBalancePending, isCalculatingMax: receiveMaxCalculating, isQuoteRefreshing: !hasCurrentExactOutPaymentIntent &&
7922
+ (quoteRefreshing || intentLoading), isSourcePickerDisabled: isSourcePickerRefreshDisabled, onAmountChange: handleDepositAmountChange, onAmountModeToggle: handleDepositAmountModeToggle, onOpenSourcePicker: () => {
7192
7923
  if (needsWalletConnection) {
7193
7924
  void handleConnectWallet();
7194
7925
  return;
7195
7926
  }
7927
+ captureSourceSelectionRequiredUsd();
7928
+ beginSourcePickerEdit();
7196
7929
  openDrawerStep("choose-swap-asset");
7197
7930
  }, onOpenTokenPicker: () => openDrawerStep("choose-receive-asset"), onSetPercent: handleDepositPercentSelect, routeMessage: insufficientSourceIssue?.message, routeStatus: insufficientSourceIssue
7198
7931
  ? "insufficient"
@@ -7238,8 +7971,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7238
7971
  height: "14px",
7239
7972
  width: "14px",
7240
7973
  } })) : (needsWalletConnection && walletConnectBusy) ||
7241
- (!hasCurrentExactOutPaymentIntent &&
7242
- (quoteRefreshing || intentLoading)) ||
7974
+ isExactOutPaymentQuotePending ||
7243
7975
  receiveMaxCalculating ? (_jsx(Loader2, { className: "animate-spin", style: {
7244
7976
  color: isDepositCtaDisabled
7245
7977
  ? theme.colors.muted
@@ -7263,8 +7995,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7263
7995
  "choose-swap-asset",
7264
7996
  "choose-receive-asset",
7265
7997
  "enter-recipient",
7266
- ].includes(swapStep) && (_jsxs(_Fragment, { children: [_jsx(SendIdleForm, { amount: amount, calculatingPercent: maxCalculationPercent, fromTokens: displayFromTokens, isAmountReadOnly: isConfiguredAmountFixed, isAssetPickerDisabled: isReceiveAssetPickerLocked, hideDestinationTokenDropdownIcon: hideDestinationTokenDropdownIcon, isCalculatingMax: receiveMaxCalculating, isQuoteRefreshing: !hasCurrentExactOutPaymentIntent &&
7267
- (quoteRefreshing || intentLoading), onAmountChange: handleSendAmountChange, onOpenAssetPicker: () => openDrawerStep("choose-receive-asset"), isRecipientLocked: isConfiguredRecipientLocked, onOpenRecipientPicker: isConfiguredRecipientLocked
7998
+ ].includes(swapStep) && (_jsxs(_Fragment, { children: [_jsx(SendIdleForm, { amount: amount, calculatingPercent: maxCalculationPercent, fromTokens: displayFromTokens, isAmountReadOnly: isConfiguredAmountFixed, isAssetPickerDisabled: isReceiveAssetPickerLocked, isBalanceLoading: isSwapBalancePending, hideDestinationTokenDropdownIcon: hideDestinationTokenDropdownIcon, isCalculatingMax: receiveMaxCalculating, isQuoteRefreshing: !hasCurrentExactOutPaymentIntent &&
7999
+ (quoteRefreshing || intentLoading), isSourcePickerDisabled: isSourcePickerRefreshDisabled, onAmountChange: handleSendAmountChange, onOpenAssetPicker: () => openDrawerStep("choose-receive-asset"), isRecipientLocked: isConfiguredRecipientLocked, onOpenRecipientPicker: isConfiguredRecipientLocked
7268
8000
  ? () => undefined
7269
8001
  : handleOpenRecipientEditor, onOpenSourcePicker: () => {
7270
8002
  if (needsWalletConnection) {
@@ -7272,6 +8004,8 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7272
8004
  return;
7273
8005
  }
7274
8006
  setEditingAssetIndex(null);
8007
+ captureSourceSelectionRequiredUsd();
8008
+ beginSourcePickerEdit();
7275
8009
  openDrawerStep("choose-swap-asset");
7276
8010
  }, onSetPercent: handleSendPercentSelect, recipientAddress: recipientAddress || "", routeMessage: insufficientSourceIssue?.message, routeStatus: insufficientSourceIssue
7277
8011
  ? "insufficient"
@@ -7320,8 +8054,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7320
8054
  width: "14px",
7321
8055
  } })) : (needsWalletConnection && walletConnectBusy) ||
7322
8056
  (!sendNeedsRecipient &&
7323
- ((!hasCurrentExactOutPaymentIntent &&
7324
- (quoteRefreshing || intentLoading)) ||
8057
+ (isExactOutPaymentQuotePending ||
7325
8058
  receiveMaxCalculating)) ? (_jsx(Loader2, { className: "animate-spin", style: {
7326
8059
  color: isSendCtaDisabled
7327
8060
  ? theme.colors.muted
@@ -7508,7 +8241,7 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7508
8241
  display: "flex",
7509
8242
  flexDirection: "column",
7510
8243
  justifyContent: "flex-end",
7511
- }, children: [_jsx("div", { onClick: closeDrawerToIdle, style: {
8244
+ }, children: [_jsx("div", { onClick: handleSourcePickerCancel, style: {
7512
8245
  position: "absolute",
7513
8246
  top: 0,
7514
8247
  left: 0,
@@ -7546,31 +8279,16 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7546
8279
  willChange: "height, max-height, transform, opacity",
7547
8280
  }, children: _jsx(SwapAssetSelector, { allowSelectedTokenRemoval: false, allowUnified: activeMode === "deposit" ||
7548
8281
  activeMode === "send" ||
7549
- activeMode === "swap", autoSelectFilterTabs: activeMode === "deposit" || activeMode === "send", editingAssetIndex: editingAssetIndex, filterTabBehavior: activeMode === "deposit" ? "source-pool" : "select-all", hideCustomTab: activeMode === "swap", isMulti: activeMode === "deposit" || activeMode === "send", lockedTokens: lockedDestinationSourceTokens, onBack: closeDrawerToIdle, onClearSelection: activeMode === "deposit" || activeMode === "send"
7550
- ? () => {
7551
- setSourceSelectionTouched(true);
7552
- setExactOutQuoteSourceModeValue("selected");
7553
- if (activeMode === "deposit") {
7554
- setDepositSourceFilter("custom");
7555
- }
7556
- invalidateExactOutQuoteForRefresh();
7557
- setSourceSelectionRevision((current) => current + 1);
7558
- setFromTokens((current) => current.length === 0 ? current : []);
7559
- }
7560
- : undefined, onDone: closeDrawerToIdle, onFilterTabSelect: activeMode === "deposit"
7561
- ? (tab) => {
7562
- const nextFilter = tab === "stables" ? "stablecoins" : tab;
7563
- setDepositSourceFilter(nextFilter);
7564
- setSourceSelectionTouched(false);
7565
- setExactOutQuoteSourceModeValue("all");
7566
- invalidateExactOutQuoteForRefresh();
7567
- setSourceSelectionRevision((current) => current + 1);
7568
- const selection = getResolvedDepositSourceSelection({
7569
- filter: nextFilter,
7570
- isManualSelection: false,
7571
- });
7572
- setFromTokens(getDepositSourceTokensForIds(selection.selectedSourceIds));
7573
- }
8282
+ activeMode === "swap", autoSelectFilterTabs: activeMode === "deposit" || activeMode === "send", editingAssetIndex: editingAssetIndex, filterTabBehavior: activeMode === "deposit" ? "source-pool" : "select-all", hideCustomTab: activeMode === "swap", initialFilterTab: activeMode === "deposit"
8283
+ ? depositSourceFilter === "stablecoins"
8284
+ ? "stables"
8285
+ : depositSourceFilter
8286
+ : activeMode === "send" && sourceSelectionTouched
8287
+ ? "custom"
8288
+ : "all", isMulti: activeMode === "deposit" || activeMode === "send", lockedTokens: lockedDestinationSourceTokens, onBack: handleSourcePickerCancel, onDone: activeMode === "deposit" || activeMode === "send"
8289
+ ? commitSourcePickerDraft
8290
+ : closeDrawerToIdle, onFilterTabSelect: activeMode === "deposit" || activeMode === "send"
8291
+ ? handleSourcePickerFilterTabSelect
7574
8292
  : undefined, onSelect: (token) => {
7575
8293
  if (activeMode === "swap") {
7576
8294
  const next = [...fromTokens];
@@ -7606,71 +8324,43 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7606
8324
  }
7607
8325
  else if (activeMode === "deposit" ||
7608
8326
  activeMode === "send") {
7609
- setSourceSelectionTouched(true);
7610
- setExactOutQuoteSourceModeValue("selected");
7611
- if (activeMode === "deposit") {
7612
- setDepositSourceFilter("custom");
7613
- }
7614
- invalidateExactOutQuoteForRefresh();
7615
- setSourceSelectionRevision((current) => current + 1);
7616
- setFromTokens([{ ...token, userAmount: amount }]);
7617
- closeDrawerToIdle();
8327
+ handleSourcePickerDraftSelectionChange([
8328
+ { ...token, userAmount: "" },
8329
+ ]);
7618
8330
  }
7619
8331
  }, onSelectionChange: activeMode === "deposit" || activeMode === "send"
7620
- ? (tokens) => {
7621
- setSourceSelectionTouched(true);
7622
- setExactOutQuoteSourceModeValue("selected");
7623
- if (activeMode === "deposit") {
7624
- setDepositSourceFilter("custom");
7625
- }
7626
- invalidateExactOutQuoteForRefresh();
7627
- setSourceSelectionRevision((current) => current + 1);
7628
- setFromTokens(tokens.map((token) => ({
7629
- ...token,
7630
- userAmount: "",
7631
- })));
7632
- }
8332
+ ? handleSourcePickerDraftSelectionChange
7633
8333
  : undefined, onToggle: (token) => {
7634
- if (activeMode === "deposit" || activeMode === "send") {
7635
- setSourceSelectionTouched(true);
7636
- setExactOutQuoteSourceModeValue("selected");
7637
- if (activeMode === "deposit") {
7638
- setDepositSourceFilter("custom");
8334
+ const isDepositOrSendSourcePicker = activeMode === "deposit" || activeMode === "send";
8335
+ const prevTokens = isDepositOrSendSourcePicker
8336
+ ? sourcePickerSelectedTokens
8337
+ : fromTokens;
8338
+ const isSameSelection = (a, b) => {
8339
+ if (a.isUnified || b.isUnified) {
8340
+ return Boolean(a.isUnified &&
8341
+ b.isUnified &&
8342
+ a.unifiedSymbol === b.unifiedSymbol);
7639
8343
  }
7640
- invalidateExactOutQuoteForRefresh();
7641
- setSourceSelectionRevision((current) => current + 1);
7642
- }
7643
- else {
7644
- clearPendingSwapIntent();
7645
- }
7646
- setFromTokens((prev) => {
7647
- const isSameSelection = (a, b) => {
7648
- if (a.isUnified || b.isUnified) {
7649
- return Boolean(a.isUnified &&
7650
- b.isUnified &&
7651
- a.unifiedSymbol === b.unifiedSymbol);
7652
- }
7653
- return (a.contractAddress.toLowerCase() ===
7654
- b.contractAddress.toLowerCase() &&
7655
- a.chainId === b.chainId);
7656
- };
7657
- const isDepositOrSendSourcePicker = activeMode === "deposit" || activeMode === "send";
7658
- const sourceTokens = token.sourceTokens ?? [];
7659
- const isSameUnifiedGroup = (item) => Boolean(item.isUnified &&
7660
- token.isUnified &&
7661
- item.unifiedSymbol === token.unifiedSymbol);
7662
- const withDefaultAmount = (item) => ({
7663
- ...item,
7664
- userAmount: activeMode === "swap" && prev.length === 0
7665
- ? amount
7666
- : "",
7667
- });
8344
+ return (a.contractAddress.toLowerCase() ===
8345
+ b.contractAddress.toLowerCase() && a.chainId === b.chainId);
8346
+ };
8347
+ const sourceTokens = token.sourceTokens ?? [];
8348
+ const isSameUnifiedGroup = (item) => Boolean(item.isUnified &&
8349
+ token.isUnified &&
8350
+ item.unifiedSymbol === token.unifiedSymbol);
8351
+ const withDefaultAmount = (item) => ({
8352
+ ...item,
8353
+ userAmount: activeMode === "swap" && prevTokens.length === 0
8354
+ ? amount
8355
+ : "",
8356
+ });
8357
+ const getNextTokens = () => {
7668
8358
  if (isDepositOrSendSourcePicker &&
7669
8359
  token.isUnified &&
7670
8360
  sourceTokens.length > 0) {
7671
- const hasUnifiedSelection = prev.some(isSameUnifiedGroup);
7672
- const areAllChildrenSelected = sourceTokens.every((source) => prev.some((item) => isSameSelection(item, source)));
7673
- const withoutGroup = prev.filter((item) => !isSameUnifiedGroup(item) &&
8361
+ const hasUnifiedSelection = prevTokens.some(isSameUnifiedGroup);
8362
+ const areAllChildrenSelected = sourceTokens.every((source) => prevTokens.some((item) => isSameSelection(item, source)));
8363
+ const withoutGroup = prevTokens.filter((item) => !isSameUnifiedGroup(item) &&
7674
8364
  !sourceTokens.some((source) => isSameSelection(item, source)));
7675
8365
  if (hasUnifiedSelection || areAllChildrenSelected) {
7676
8366
  return withoutGroup;
@@ -7681,10 +8371,10 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7681
8371
  ];
7682
8372
  }
7683
8373
  if (isDepositOrSendSourcePicker && !token.isUnified) {
7684
- const unifiedSelection = prev.find((item) => item.isUnified &&
8374
+ const unifiedSelection = prevTokens.find((item) => item.isUnified &&
7685
8375
  item.sourceTokens?.some((source) => isSameSelection(source, token)));
7686
8376
  if (unifiedSelection?.sourceTokens?.length) {
7687
- const withoutUnified = prev.filter((item) => !isSameSelection(item, unifiedSelection));
8377
+ const withoutUnified = prevTokens.filter((item) => !isSameSelection(item, unifiedSelection));
7688
8378
  return [
7689
8379
  ...withoutUnified,
7690
8380
  ...unifiedSelection.sourceTokens
@@ -7693,12 +8383,12 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7693
8383
  ];
7694
8384
  }
7695
8385
  }
7696
- const exists = prev.find((item) => isSameSelection(item, token));
8386
+ const exists = prevTokens.find((item) => isSameSelection(item, token));
7697
8387
  if (exists) {
7698
- return prev.filter((item) => !isSameSelection(item, token));
8388
+ return prevTokens.filter((item) => !isSameSelection(item, token));
7699
8389
  }
7700
8390
  const tokenSourceKeys = new Set((token.sourceTokens ?? []).map((source) => `${source.chainId}-${source.contractAddress.toLowerCase()}`));
7701
- const next = prev.filter((existing) => {
8391
+ const next = prevTokens.filter((existing) => {
7702
8392
  if (token.isUnified &&
7703
8393
  tokenSourceKeys.has(`${existing.chainId}-${existing.contractAddress.toLowerCase()}`)) {
7704
8394
  return false;
@@ -7712,12 +8402,16 @@ function NexusWidgetInner({ config: rawConfig, embed = true, className, connecte
7712
8402
  return true;
7713
8403
  });
7714
8404
  return [...next, withDefaultAmount(token)];
7715
- });
7716
- }, preserveSelectedBelowMinimum: false, requiredUsd: activeMode === "deposit"
7717
- ? depositUsdDisplay
7718
- : activeMode === "send" && sendAmountUsd > 0
7719
- ? sendAmountUsd.toFixed(2)
7720
- : undefined, selectedTokens: fromTokens, swapBalance: swapBalance, swapSupportedChains: swapSupportedChainsAndTokens, title: activeMode === "deposit" || activeMode === "send"
8405
+ };
8406
+ const nextTokens = getNextTokens();
8407
+ if (isDepositOrSendSourcePicker) {
8408
+ handleSourcePickerDraftSelectionChange(nextTokens);
8409
+ }
8410
+ else {
8411
+ clearPendingSwapIntent();
8412
+ setFromTokens(nextTokens);
8413
+ }
8414
+ }, preserveSelectedBelowMinimum: false, requiredUsd: assetSelectorRequiredUsdDisplay, selectedTokens: sourcePickerSelectedTokens, showBelowMinimumInline: activeMode === "swap" && swapType === "exactIn", swapBalance: swapBalance, swapSupportedChains: swapSupportedChainsAndTokens, title: activeMode === "deposit" || activeMode === "send"
7721
8415
  ? "Choose Assets to Pay with"
7722
8416
  : "Choose assets to send" }) })] })), (activeMode === "swap" ||
7723
8417
  activeMode === "send" ||