@trustware/sdk-staging 1.1.17-staging.6 → 1.1.18-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/widget.mjs CHANGED
@@ -620,7 +620,7 @@ var init_constants = __esm({
620
620
  "src/constants.ts"() {
621
621
  "use strict";
622
622
  SDK_NAME = "@trustware/sdk";
623
- SDK_VERSION = "1.1.17-staging.6";
623
+ SDK_VERSION = "1.1.18-staging.1";
624
624
  API_ROOT = "https://bv-staging-api.trustware.io";
625
625
  GTM_ID = "GTM-TZDGNCXB";
626
626
  API_PREFIX = "/api";
@@ -2881,6 +2881,32 @@ var init_routeError = __esm({
2881
2881
  }
2882
2882
  });
2883
2883
 
2884
+ // src/core/routeApprovals.ts
2885
+ function approvalAmountWei(approval, stepIndex) {
2886
+ const { amount } = approval;
2887
+ if (amount === void 0 || amount === "") {
2888
+ throw new Error(
2889
+ `Route approval ${stepIndex} (spender ${String(approval.spender)}) has no amount.`
2890
+ );
2891
+ }
2892
+ if (!DECIMAL_INTEGER.test(amount)) {
2893
+ throw new Error(
2894
+ `Route approval ${stepIndex} (spender ${String(approval.spender)}) has an invalid amount "${amount}".`
2895
+ );
2896
+ }
2897
+ return BigInt(amount);
2898
+ }
2899
+ function assertValidRouteApprovals(approvals) {
2900
+ (approvals ?? []).forEach(approvalAmountWei);
2901
+ }
2902
+ var DECIMAL_INTEGER;
2903
+ var init_routeApprovals = __esm({
2904
+ "src/core/routeApprovals.ts"() {
2905
+ "use strict";
2906
+ DECIMAL_INTEGER = /^\d+$/;
2907
+ }
2908
+ });
2909
+
2884
2910
  // src/core/routeValue.ts
2885
2911
  function parseUsd(value) {
2886
2912
  if (value == null) return null;
@@ -3041,6 +3067,7 @@ async function buildRoute(body, signal) {
3041
3067
  if (!txReq?.data) {
3042
3068
  throw new Error("Invalid route: missing transaction data");
3043
3069
  }
3070
+ assertValidRouteApprovals(route?.execution?.approvals);
3044
3071
  assertRouteDeliversValue(route);
3045
3072
  return { intentId, txReq, actions, finalExchangeRate, route, sponsorship };
3046
3073
  }
@@ -3161,6 +3188,7 @@ var init_routes = __esm({
3161
3188
  "use strict";
3162
3189
  init_http();
3163
3190
  init_routeError();
3191
+ init_routeApprovals();
3164
3192
  init_routeValue();
3165
3193
  init_store();
3166
3194
  init_address();
@@ -6523,11 +6551,13 @@ async function ensureApprovals(w, approvals, fallbackChainId, intentId, routeAmo
6523
6551
  for (const [stepIndex, approval] of approvals.entries()) {
6524
6552
  const spender = approval.spender;
6525
6553
  const tokenAddress = approval.tokenAddress;
6526
- const amount = approval.amount;
6527
- if (!spender || !tokenAddress || !amount) continue;
6554
+ if (!spender || !tokenAddress) continue;
6528
6555
  const chainId = approval.chainId || String(fallbackChainId ?? "");
6529
6556
  if (!chainId) continue;
6530
- const amountWei = requiredApprovalAmount(BigInt(amount), routeAmountWei);
6557
+ const amountWei = requiredApprovalAmount(
6558
+ approvalAmountWei(approval, stepIndex),
6559
+ routeAmountWei
6560
+ );
6531
6561
  let currentAllowance = 0n;
6532
6562
  try {
6533
6563
  const { allowance } = await getEVMAllowance({
@@ -6613,7 +6643,6 @@ async function sendRouteTransactionReporting(b, fallbackChainId, options, onAcce
6613
6643
  )}).`
6614
6644
  );
6615
6645
  }
6616
- await ensureWalletOnChain(w, target);
6617
6646
  if (!validatedSponsorship && !options?.approvalsEnsured) {
6618
6647
  await ensureApprovals(
6619
6648
  w,
@@ -6623,6 +6652,7 @@ async function sendRouteTransactionReporting(b, fallbackChainId, options, onAcce
6623
6652
  BigInt(b.route?.estimate?.fromAmount ?? "0")
6624
6653
  );
6625
6654
  }
6655
+ await ensureWalletOnChain(w, target);
6626
6656
  if (w.type === "eip1193") {
6627
6657
  const from = await w.getAddress();
6628
6658
  const hexValue = value ? `0x${value.toString(16)}` : "0x0";
@@ -6789,6 +6819,7 @@ var erc20ApproveAbi, NOT_FOUND_GRACE_MS, UNLIMITED_APPROVAL_FLOOR;
6789
6819
  var init_tx = __esm({
6790
6820
  "src/core/tx.ts"() {
6791
6821
  "use strict";
6822
+ init_routeApprovals();
6792
6823
  init_routeValue();
6793
6824
  init_wallets();
6794
6825
  init_routes();
@@ -7039,11 +7070,7 @@ function mapRouteFacts(facts) {
7039
7070
  return INVALID_ADDRESS;
7040
7071
  }
7041
7072
  if (!facts.allDeclined) {
7042
- return {
7043
- category: "route_error",
7044
- title: "Route Unavailable",
7045
- message: "A routing provider did not respond. Please try again in a moment."
7046
- };
7073
+ return ROUTING_BUSY;
7047
7074
  }
7048
7075
  const has = (code) => facts.codes.includes(code);
7049
7076
  if (has(RouteDeclineCode.FeesExceedOutput)) {
@@ -7063,12 +7090,8 @@ function mapRouteFacts(facts) {
7063
7090
  message: minimum ? `This route needs at least ${minimum}. Try a larger amount.` : "This amount is below the minimum for this route. Try a larger amount."
7064
7091
  };
7065
7092
  }
7066
- if (lowLiquidity && !tooLow) {
7067
- return {
7068
- category: "route_error",
7069
- title: "Insufficient Liquidity",
7070
- message: "Not enough liquidity for this swap. Try a smaller amount or different token."
7071
- };
7093
+ if (facts.codes.every((code) => code === RouteDeclineCode.InsufficientLiquidity)) {
7094
+ return INSUFFICIENT_LIQUIDITY;
7072
7095
  }
7073
7096
  if (facts.codes.every((code) => code === RouteDeclineCode.DestinationCallFailed)) {
7074
7097
  return DESTINATION_CALL_FAILED;
@@ -7120,11 +7143,7 @@ function classifyError(raw, msg) {
7120
7143
  };
7121
7144
  }
7122
7145
  if (bm.includes("liquidity") || bm.includes("slippage") || bm.includes("price impact")) {
7123
- return {
7124
- category: "route_error",
7125
- title: "Insufficient Liquidity",
7126
- message: "Not enough liquidity for this swap. Try a smaller amount or different token."
7127
- };
7146
+ return INSUFFICIENT_LIQUIDITY;
7128
7147
  }
7129
7148
  if (bm.includes("insufficient funds") || bm.includes("insufficient balance")) {
7130
7149
  return {
@@ -7160,12 +7179,8 @@ function classifyError(raw, msg) {
7160
7179
  message: "Could not get a quote right now. Please try again."
7161
7180
  };
7162
7181
  }
7163
- if (lower.includes("not enough liquidity") || lower.includes("insufficient liquidity") || lower.includes("price impact too high")) {
7164
- return {
7165
- category: "route_error",
7166
- title: "Insufficient Liquidity",
7167
- message: "Not enough liquidity for this swap. Try a smaller amount or different token."
7168
- };
7182
+ if (lower.includes("enough liquidity") || lower.includes("insufficient liquidity") || lower.includes("price impact too high")) {
7183
+ return INSUFFICIENT_LIQUIDITY;
7169
7184
  }
7170
7185
  if (lower.includes("destination contract call")) {
7171
7186
  return DESTINATION_CALL_FAILED;
@@ -7173,6 +7188,9 @@ function classifyError(raw, msg) {
7173
7188
  if (lower.includes("invalid address") || lower.includes("address is not valid")) {
7174
7189
  return INVALID_ADDRESS;
7175
7190
  }
7191
+ if (lower.includes("routing is busy")) {
7192
+ return ROUTING_BUSY;
7193
+ }
7176
7194
  if (lower.includes("did not respond") || lower.includes("routing provider")) {
7177
7195
  return {
7178
7196
  category: "route_error",
@@ -7251,7 +7269,7 @@ function classifyError(raw, msg) {
7251
7269
  message: "An unexpected error occurred. Please try again."
7252
7270
  };
7253
7271
  }
7254
- var SELF_MAPPED, INVALID_ADDRESS, DESTINATION_CALL_FAILED;
7272
+ var SELF_MAPPED, INVALID_ADDRESS, DESTINATION_CALL_FAILED, INSUFFICIENT_LIQUIDITY, ROUTING_BUSY;
7255
7273
  var init_mapError = __esm({
7256
7274
  "src/widget/lib/mapError.ts"() {
7257
7275
  "use strict";
@@ -7272,6 +7290,16 @@ var init_mapError = __esm({
7272
7290
  title: "Destination Call Failed",
7273
7291
  message: "The destination contract call could not be simulated. Check the call data and target."
7274
7292
  };
7293
+ INSUFFICIENT_LIQUIDITY = {
7294
+ category: "route_error",
7295
+ title: "Insufficient Liquidity",
7296
+ message: "There isn't enough liquidity to route this swap right now. Try a different token or network, or a smaller amount."
7297
+ };
7298
+ ROUTING_BUSY = {
7299
+ category: "route_error",
7300
+ title: "Route Unavailable",
7301
+ message: "Routing is busy right now. Please wait a moment and try again."
7302
+ };
7275
7303
  }
7276
7304
  });
7277
7305
 
@@ -19878,6 +19906,37 @@ var init_SuccessSummaryCard = __esm({
19878
19906
  }
19879
19907
  });
19880
19908
 
19909
+ // src/widget/helpers/plannedApprovals.ts
19910
+ function plannedErc20Approvals(approvals, route) {
19911
+ return (approvals ?? []).flatMap((approval, stepIndex) => {
19912
+ const token = approval.tokenAddress ?? route.fromTokenAddress;
19913
+ const spender = approval.spender;
19914
+ if (!isEvmAddress2(token) || !isEvmAddress2(spender)) return [];
19915
+ return [
19916
+ {
19917
+ stepIndex,
19918
+ chainId: approval.chainId || route.chainId,
19919
+ token,
19920
+ spender,
19921
+ // A planned 0 is the reset-to-zero step USDT-style tokens need
19922
+ // before an existing allowance can change; it is kept as 0.
19923
+ amountWei: requiredApprovalAmount(
19924
+ approvalAmountWei(approval, stepIndex),
19925
+ route.amountWei
19926
+ )
19927
+ }
19928
+ ];
19929
+ });
19930
+ }
19931
+ var init_plannedApprovals = __esm({
19932
+ "src/widget/helpers/plannedApprovals.ts"() {
19933
+ "use strict";
19934
+ init_routeApprovals();
19935
+ init_tx();
19936
+ init_chainHelpers();
19937
+ }
19938
+ });
19939
+
19881
19940
  // src/widget/lib/routeChange.ts
19882
19941
  function describeRouteChange(change) {
19883
19942
  switch (change.kind) {
@@ -33177,9 +33236,6 @@ var init_smart_account2 = __esm({
33177
33236
  // src/widget/features/transaction/hooks/useTransactionActionModel.ts
33178
33237
  import { useCallback as useCallback20, useEffect as useEffect26, useMemo as useMemo22, useRef as useRef12, useState as useState26 } from "react";
33179
33238
  import { encodeFunctionData as encodeFunctionData9, erc20Abi } from "viem";
33180
- function sleep3(ms) {
33181
- return new Promise((resolve) => setTimeout(resolve, ms));
33182
- }
33183
33239
  function useTransactionActionModel({
33184
33240
  actionErrorMessage,
33185
33241
  amountWei,
@@ -33217,92 +33273,72 @@ function useTransactionActionModel({
33217
33273
  selectedChain?.id,
33218
33274
  selectedChain?.networkIdentifier
33219
33275
  ]);
33220
- const selectedTokenOnBackendChain = useMemo22(() => {
33221
- if (!backendChainId2) return false;
33222
- const target = normalizeChainKey(backendChainId2);
33223
- return [
33224
- selectedChain?.chainId,
33225
- selectedChain?.id,
33226
- selectedChain?.networkIdentifier
33227
- ].some((c) => {
33228
- const k = normalizeChainKey(c ?? null);
33229
- return k !== "" && k === target;
33230
- });
33231
- }, [
33232
- backendChainId2,
33233
- selectedChain?.chainId,
33234
- selectedChain?.id,
33235
- selectedChain?.networkIdentifier
33236
- ]);
33237
33276
  const isNativeSelected = useMemo22(() => {
33238
33277
  const address = selectedToken?.address;
33239
33278
  return !address || isNativeTokenAddress(address, chainType);
33240
33279
  }, [chainType, selectedToken?.address]);
33241
- const spender = useMemo22(() => {
33242
- const txReq = routeResult?.txReq;
33243
- const addr = txReq?.to ?? txReq?.target;
33244
- return addr ?? null;
33245
- }, [routeResult?.txReq]);
33246
- const [allowanceWei, setAllowanceWei] = useState26(0n);
33280
+ const requiredApprovals = useMemo22(
33281
+ () => isEvm && !isNativeSelected && !routeResult?.sponsorship ? plannedErc20Approvals(routeResult?.route?.execution?.approvals, {
33282
+ fromTokenAddress: selectedToken?.address,
33283
+ chainId: backendChainId2 ?? "",
33284
+ amountWei
33285
+ }) : [],
33286
+ [
33287
+ amountWei,
33288
+ backendChainId2,
33289
+ isEvm,
33290
+ isNativeSelected,
33291
+ routeResult?.route?.execution?.approvals,
33292
+ routeResult?.sponsorship,
33293
+ selectedToken?.address
33294
+ ]
33295
+ );
33296
+ const [unmetApprovals, setUnmetApprovals] = useState26(
33297
+ []
33298
+ );
33247
33299
  const [isReadingAllowance, setIsReadingAllowance] = useState26(false);
33248
33300
  const [isApproving, setIsApproving] = useState26(false);
33249
33301
  const [gasReservationWei, setGasReservationWei] = useState26(0n);
33302
+ const findUnmetApprovals = useCallback20(async () => {
33303
+ if (!walletAddress) return [];
33304
+ const met = await Promise.all(
33305
+ requiredApprovals.map(async (approval) => {
33306
+ try {
33307
+ const { allowance } = await getEVMAllowance({
33308
+ chainId: approval.chainId,
33309
+ tokenAddress: approval.token,
33310
+ ownerAddress: walletAddress,
33311
+ spenderAddress: approval.spender
33312
+ });
33313
+ return approvalSatisfied(
33314
+ BigInt(allowance || "0"),
33315
+ approval.amountWei
33316
+ );
33317
+ } catch {
33318
+ return false;
33319
+ }
33320
+ })
33321
+ );
33322
+ return requiredApprovals.filter((_, index) => !met[index]);
33323
+ }, [requiredApprovals, walletAddress]);
33250
33324
  const readAllowance = useCallback20(async () => {
33251
- if (!isEvm || isNativeSelected || !!routeResult?.sponsorship || // sponsored routes: SA approves bridge inside the UO batch
33252
- !backendChainId2 || !selectedTokenOnBackendChain || !walletAddress || !spender || !selectedToken?.address) {
33253
- setAllowanceWei(0n);
33325
+ if (requiredApprovals.length === 0) {
33326
+ setUnmetApprovals([]);
33254
33327
  return;
33255
33328
  }
33329
+ setIsReadingAllowance(true);
33256
33330
  try {
33257
- setIsReadingAllowance(true);
33258
- const result = await getEVMAllowance({
33259
- chainId: backendChainId2,
33260
- tokenAddress: selectedToken.address,
33261
- ownerAddress: walletAddress,
33262
- spenderAddress: spender
33263
- });
33264
- setAllowanceWei(BigInt(result.allowance || "0"));
33265
- } catch {
33266
- setAllowanceWei(0n);
33331
+ setUnmetApprovals(await findUnmetApprovals());
33267
33332
  } finally {
33268
33333
  setIsReadingAllowance(false);
33269
33334
  }
33270
- }, [
33271
- backendChainId2,
33272
- isEvm,
33273
- isNativeSelected,
33274
- routeResult?.sponsorship,
33275
- selectedTokenOnBackendChain,
33276
- selectedToken?.address,
33277
- spender,
33278
- walletAddress
33279
- ]);
33335
+ }, [findUnmetApprovals, requiredApprovals.length]);
33280
33336
  useEffect26(() => {
33281
33337
  void readAllowance();
33282
33338
  }, [readAllowance]);
33283
- const needsApproval = isEvm && !isNativeSelected && !routeResult?.sponsorship && // sponsored routes use Permit2 + SA internal batch — no EOA bridge approval
33284
- !!walletAddress && !!spender && amountWei > 0n && allowanceWei < amountWei;
33285
- const waitForApprovalConfirmation2 = useCallback20(
33286
- async (chainId, txHash) => {
33287
- const timeoutMs = 12e4;
33288
- const intervalMs = 2e3;
33289
- const started = Date.now();
33290
- while (Date.now() - started < timeoutMs) {
33291
- const status = await getEVMTxStatus({ chainId, txHash });
33292
- if (status.status === "success") {
33293
- return;
33294
- }
33295
- if (status.status === "reverted") {
33296
- throw new Error("Approval transaction reverted");
33297
- }
33298
- await sleep3(intervalMs);
33299
- }
33300
- throw new Error("Timed out waiting for approval confirmation");
33301
- },
33302
- []
33303
- );
33339
+ const needsApproval = !!walletAddress && amountWei > 0n && unmetApprovals.length > 0;
33304
33340
  const handleApproveExact = useCallback20(async () => {
33305
- if (isApproving || amountWei <= 0n || !walletAddress || !spender || !selectedToken?.address) {
33341
+ if (isApproving || amountWei <= 0n || !walletAddress || unmetApprovals.length === 0) {
33306
33342
  return;
33307
33343
  }
33308
33344
  const wallet = Trustware.getWallet();
@@ -33318,80 +33354,57 @@ function useTransactionActionModel({
33318
33354
  });
33319
33355
  setIsApproving(true);
33320
33356
  try {
33321
- const targetChain = Number(
33322
- routeResult?.txReq?.chainId ?? selectedChain?.chainId ?? selectedChain?.id
33323
- );
33324
- console.log("[approve] targetChain:", targetChain, {
33325
- txReqChainId: routeResult?.txReq?.chainId,
33326
- selectedChainId: selectedChain?.chainId,
33327
- selectedChainIdAlt: selectedChain?.id
33328
- });
33329
- if (Number.isFinite(targetChain)) {
33330
- const current = await wallet.getChainId();
33331
- if (current !== targetChain) {
33332
- try {
33333
- await wallet.switchChain(targetChain);
33334
- } catch {
33335
- }
33336
- }
33337
- }
33338
- const data = encodeFunctionData9({
33339
- abi: erc20Abi,
33340
- functionName: "approve",
33341
- args: [spender, amountWei]
33342
- });
33343
- let hash;
33344
- if (wallet.type === "eip1193") {
33345
- const from = await wallet.getAddress();
33346
- const params = {
33347
- from,
33348
- to: selectedToken.address,
33349
- data,
33350
- value: "0x0"
33351
- };
33352
- if (Number.isFinite(targetChain)) {
33353
- params.chainId = `0x${targetChain.toString(16)}`;
33354
- }
33355
- hash = await wallet.request({
33356
- method: "eth_sendTransaction",
33357
- params: [params]
33357
+ for (const approval of unmetApprovals) {
33358
+ const approvalChain = Number(approval.chainId);
33359
+ await ensureWalletOnChain(wallet, approvalChain);
33360
+ const data = encodeFunctionData9({
33361
+ abi: erc20Abi,
33362
+ functionName: "approve",
33363
+ args: [approval.spender, approval.amountWei]
33358
33364
  });
33359
- } else {
33360
- const response = await wallet.sendTransaction({
33361
- to: selectedToken.address,
33362
- data,
33363
- value: 0n,
33364
- chainId: Number.isFinite(targetChain) ? targetChain : void 0
33365
- });
33366
- hash = response.hash;
33367
- }
33368
- if (backendChainId2) {
33369
- await waitForApprovalConfirmation2(backendChainId2, hash);
33370
- await readAllowance();
33371
- } else {
33372
- setAllowanceWei(amountWei);
33365
+ let hash;
33366
+ if (wallet.type === "eip1193") {
33367
+ const from = await wallet.getAddress();
33368
+ hash = await wallet.request({
33369
+ method: "eth_sendTransaction",
33370
+ params: [
33371
+ {
33372
+ from,
33373
+ to: approval.token,
33374
+ data,
33375
+ value: "0x0",
33376
+ chainId: `0x${approvalChain.toString(16)}`
33377
+ }
33378
+ ]
33379
+ });
33380
+ } else {
33381
+ const response = await wallet.sendTransaction({
33382
+ to: approval.token,
33383
+ data,
33384
+ value: 0n,
33385
+ chainId: approvalChain
33386
+ });
33387
+ hash = response.hash;
33388
+ }
33389
+ await waitForApprovalConfirmation(approval.chainId, hash);
33373
33390
  }
33391
+ await readAllowance();
33374
33392
  } catch {
33375
33393
  } finally {
33376
33394
  setIsApproving(false);
33377
33395
  }
33378
33396
  }, [
33379
33397
  amountWei,
33380
- backendChainId2,
33381
33398
  destinationConfig?.routes.toChain,
33382
33399
  destinationConfig?.routes.toToken,
33383
33400
  isApproving,
33384
33401
  readAllowance,
33385
- routeResult?.txReq?.chainId,
33386
33402
  selectedChain?.axelarChainName,
33387
33403
  selectedChain?.chainId,
33388
- selectedChain?.id,
33389
33404
  selectedChain?.networkName,
33390
- selectedToken?.address,
33391
33405
  selectedToken?.symbol,
33392
- spender,
33393
33406
  trackEvent,
33394
- waitForApprovalConfirmation2,
33407
+ unmetApprovals,
33395
33408
  walletAddress
33396
33409
  ]);
33397
33410
  const getCachedFeeData = useCallback20(async () => {
@@ -33578,32 +33591,13 @@ function useTransactionActionModel({
33578
33591
  const handleSwipeConfirm = useCallback20(async () => {
33579
33592
  if (needsApproval) {
33580
33593
  await handleApproveExact();
33581
- if (!backendChainId2 || !selectedTokenOnBackendChain || !selectedToken?.address || !walletAddress || !spender) {
33582
- return;
33583
- }
33584
- const freshAllowance = await getEVMAllowance({
33585
- chainId: backendChainId2,
33586
- tokenAddress: selectedToken.address,
33587
- ownerAddress: walletAddress,
33588
- spenderAddress: spender
33589
- }).then((r) => BigInt(r.allowance || "0")).catch(() => 0n);
33590
- if (freshAllowance >= amountWei) {
33594
+ if ((await findUnmetApprovals()).length === 0) {
33591
33595
  await handleConfirm();
33592
33596
  }
33593
33597
  return;
33594
33598
  }
33595
33599
  await handleConfirm();
33596
- }, [
33597
- amountWei,
33598
- backendChainId2,
33599
- handleApproveExact,
33600
- handleConfirm,
33601
- needsApproval,
33602
- selectedTokenOnBackendChain,
33603
- selectedToken?.address,
33604
- spender,
33605
- walletAddress
33606
- ]);
33600
+ }, [findUnmetApprovals, handleApproveExact, handleConfirm, needsApproval]);
33607
33601
  const isWalletConnected = walletStatus === "connected";
33608
33602
  const canSwipe = parsedAmount > 0 && !!selectedToken && isWalletConnected && !isLoadingRoute && !isSubmitting && !!routeResult && !actionErrorMessage && !isApproving && !isReadingAllowance;
33609
33603
  const swipeResetKey = useMemo22(() => {
@@ -33647,9 +33641,11 @@ var init_useTransactionActionModel = __esm({
33647
33641
  "use strict";
33648
33642
  init_core2();
33649
33643
  init_sdkRpc();
33644
+ init_tx();
33650
33645
  init_hooks2();
33651
33646
  init_chains();
33652
33647
  init_utils2();
33648
+ init_plannedApprovals();
33653
33649
  init_hooks();
33654
33650
  init_routeChange();
33655
33651
  }
@@ -35088,33 +35084,15 @@ function useSwapExecution(fromChain) {
35088
35084
  const chainIdStr = String(txReq?.chainId ?? fromChain?.chainId ?? "");
35089
35085
  const amountWei = BigInt(routeResult.route?.estimate?.fromAmount ?? "0");
35090
35086
  const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
35091
- const toCheck = plannedApprovals.length ? plannedApprovals.flatMap((a) => {
35092
- const token = a.tokenAddress ?? fromTokenAddress;
35093
- const required = requiredApprovalAmount(
35094
- BigInt(a.amount || "0"),
35095
- amountWei
35096
- );
35097
- if (!isEvmAddress2(token) || !isEvmAddress2(a.spender)) return [];
35098
- return [
35099
- {
35100
- chainId: a.chainId || chainIdStr,
35101
- token,
35102
- spender: a.spender,
35103
- required
35104
- }
35105
- ];
35106
- }) : (() => {
35107
- const spender = txReq?.to ?? txReq?.target;
35108
- if (!spender) return [];
35109
- return [
35110
- {
35111
- chainId: chainIdStr,
35112
- token: fromTokenAddress,
35113
- spender,
35114
- required: amountWei
35115
- }
35116
- ];
35117
- })();
35087
+ if (plannedApprovals.length === 0) {
35088
+ setState((p) => ({ ...p, allowanceStatus: "sufficient" }));
35089
+ return;
35090
+ }
35091
+ const toCheck = plannedErc20Approvals(plannedApprovals, {
35092
+ fromTokenAddress,
35093
+ chainId: chainIdStr,
35094
+ amountWei
35095
+ });
35118
35096
  if (toCheck.length === 0 || !chainIdStr || amountWei === 0n) {
35119
35097
  setState((p) => ({ ...p, allowanceStatus: "unknown" }));
35120
35098
  return;
@@ -35129,7 +35107,7 @@ function useSwapExecution(fromChain) {
35129
35107
  ownerAddress: walletAddress,
35130
35108
  spenderAddress: c.spender
35131
35109
  });
35132
- return approvalSatisfied(BigInt(allowance || "0"), c.required);
35110
+ return approvalSatisfied(BigInt(allowance || "0"), c.amountWei);
35133
35111
  })
35134
35112
  );
35135
35113
  setState((p) => ({
@@ -35184,7 +35162,6 @@ function useSwapExecution(fromChain) {
35184
35162
  const txReq = routeResult.txReq;
35185
35163
  const numericChainId = Number(txReq.chainId ?? fromChain?.chainId) || void 0;
35186
35164
  const chainIdStr = String(txReq.chainId ?? fromChain?.chainId ?? "");
35187
- const spender = txReq.to ?? txReq.target;
35188
35165
  const chainType = normalizeChainType(fromChain);
35189
35166
  const isNative = !fromTokenAddress || isNativeTokenAddress(fromTokenAddress, chainType);
35190
35167
  const fromAmountWei = BigInt(
@@ -35264,34 +35241,12 @@ function useSwapExecution(fromChain) {
35264
35241
  const acceptance = { tracking: false };
35265
35242
  try {
35266
35243
  const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
35267
- const requiredApprovals = plannedApprovals.length ? plannedApprovals.flatMap((a, index) => {
35268
- const token = a.tokenAddress ?? fromTokenAddress;
35269
- const approvalSpender = a.spender;
35270
- if (!isEvmAddress2(token) || !isEvmAddress2(approvalSpender))
35271
- return [];
35272
- const amountWei = requiredApprovalAmount(
35273
- BigInt(a.amount || "0"),
35274
- fromAmountWei
35275
- );
35276
- return [
35277
- {
35278
- stepIndex: index,
35279
- token,
35280
- spender: approvalSpender,
35281
- amountWei,
35282
- chainId: a.chainId || chainIdStr
35283
- }
35284
- ];
35285
- }) : spender && fromTokenAddress ? [
35286
- {
35287
- stepIndex: null,
35288
- token: fromTokenAddress,
35289
- spender,
35290
- amountWei: fromAmountWei,
35291
- chainId: chainIdStr
35292
- }
35293
- ] : [];
35294
- const coversEveryPlannedApproval = plannedApprovals.length === 0 || requiredApprovals.length === plannedApprovals.length;
35244
+ const requiredApprovals = plannedErc20Approvals(plannedApprovals, {
35245
+ fromTokenAddress,
35246
+ chainId: chainIdStr,
35247
+ amountWei: fromAmountWei
35248
+ });
35249
+ const coversEveryPlannedApproval = requiredApprovals.length === plannedApprovals.length;
35295
35250
  const ownsApprovals = coversEveryPlannedApproval && needsErc20Approval(fromTokenAddress, chainType) && !!walletAddress && !!chainIdStr;
35296
35251
  const activeAddress = ownsApprovals ? await requireActiveAddress(wallet, walletAddress) : void 0;
35297
35252
  for (const required of requiredApprovals) {
@@ -35353,14 +35308,12 @@ function useSwapExecution(fromChain) {
35353
35308
  });
35354
35309
  approvalHash = response.hash;
35355
35310
  }
35356
- if (required.stepIndex !== null) {
35357
- void submitStepReceipt(
35358
- routeResult.intentId,
35359
- required.stepIndex,
35360
- approvalHash
35361
- ).catch(() => {
35362
- });
35363
- }
35311
+ void submitStepReceipt(
35312
+ routeResult.intentId,
35313
+ required.stepIndex,
35314
+ approvalHash
35315
+ ).catch(() => {
35316
+ });
35364
35317
  await waitForApprovalConfirmation(required.chainId, approvalHash);
35365
35318
  setState((p) => ({
35366
35319
  ...p,
@@ -35477,6 +35430,7 @@ var init_useSwapExecution = __esm({
35477
35430
  init_tx();
35478
35431
  init_sdkRpc();
35479
35432
  init_chainHelpers();
35433
+ init_plannedApprovals();
35480
35434
  init_chains();
35481
35435
  SA_COOLDOWN_MS = 3e4;
35482
35436
  }
@@ -37728,6 +37682,26 @@ var init_currency = __esm({
37728
37682
  }
37729
37683
  });
37730
37684
 
37685
+ // src/modes/swap/amountFormat.ts
37686
+ function fmtAmount(n, maxFractionDigits = 6) {
37687
+ if (!isFinite(n) || n === 0) return "0";
37688
+ if (Math.abs(n) < 10 ** -maxFractionDigits) {
37689
+ return n.toLocaleString(void 0, {
37690
+ maximumSignificantDigits: SMALL_AMOUNT_SIGNIFICANT_DIGITS
37691
+ });
37692
+ }
37693
+ return n.toLocaleString(void 0, {
37694
+ maximumFractionDigits: maxFractionDigits
37695
+ });
37696
+ }
37697
+ var SMALL_AMOUNT_SIGNIFICANT_DIGITS;
37698
+ var init_amountFormat = __esm({
37699
+ "src/modes/swap/amountFormat.ts"() {
37700
+ "use strict";
37701
+ SMALL_AMOUNT_SIGNIFICANT_DIGITS = 2;
37702
+ }
37703
+ });
37704
+
37731
37705
  // src/widget/lib/quoteCountdown.ts
37732
37706
  function quoteCountdown(elapsedMs, intervalMs) {
37733
37707
  if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
@@ -39057,10 +39031,6 @@ import {
39057
39031
  } from "react";
39058
39032
  import ReactDOM from "react-dom";
39059
39033
  import { Fragment as Fragment15, jsx as jsx57, jsxs as jsxs47 } from "react/jsx-runtime";
39060
- function fmtAmount(n, max = 6) {
39061
- if (!isFinite(n) || n === 0) return "0";
39062
- return n.toLocaleString(void 0, { maximumFractionDigits: max });
39063
- }
39064
39034
  function buildExplorerUrl(hash, chain, backendUrl) {
39065
39035
  if (backendUrl) return backendUrl;
39066
39036
  const base2 = chain?.blockExplorerUrls?.[0];
@@ -40797,249 +40767,34 @@ function SwapMode({
40797
40767
  }
40798
40768
  ),
40799
40769
  completedStr && /* @__PURE__ */ jsx57(SuccessReceiptRow, { label: "Completed", value: completedStr }),
40800
- txHash && (() => {
40801
- const srcUrl = buildExplorerUrl(
40802
- txHash,
40803
- fromChain,
40804
- fromChainTxUrl
40805
- );
40806
- const isCopied = copiedHash === txHash;
40807
- return /* @__PURE__ */ jsxs47(
40808
- "div",
40809
- {
40810
- style: {
40811
- display: "flex",
40812
- alignItems: "center",
40813
- justifyContent: "space-between"
40814
- },
40815
- children: [
40816
- /* @__PURE__ */ jsx57("span", { style: { color: colors.mutedForeground }, children: destTxHash ? "Source tx" : "Transaction" }),
40817
- /* @__PURE__ */ jsxs47(
40818
- "div",
40819
- {
40820
- style: {
40821
- display: "inline-flex",
40822
- alignItems: "center",
40823
- gap: spacing[2]
40824
- },
40825
- children: [
40826
- srcUrl && /* @__PURE__ */ jsxs47(
40827
- "a",
40828
- {
40829
- href: srcUrl,
40830
- target: "_blank",
40831
- rel: "noopener noreferrer",
40832
- style: {
40833
- display: "inline-flex",
40834
- alignItems: "center",
40835
- gap: "0.25rem",
40836
- color: colors.foreground,
40837
- fontWeight: fontWeight.medium,
40838
- textDecorationLine: "underline",
40839
- textDecorationStyle: "dotted",
40840
- textUnderlineOffset: "4px"
40841
- },
40842
- children: [
40843
- txHash.slice(0, 6),
40844
- "\u2026",
40845
- txHash.slice(-4),
40846
- /* @__PURE__ */ jsxs47(
40847
- "svg",
40848
- {
40849
- style: { width: "0.75rem", height: "0.75rem" },
40850
- viewBox: "0 0 24 24",
40851
- fill: "none",
40852
- stroke: "currentColor",
40853
- strokeWidth: 2,
40854
- strokeLinecap: "round",
40855
- strokeLinejoin: "round",
40856
- children: [
40857
- /* @__PURE__ */ jsx57("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
40858
- /* @__PURE__ */ jsx57("polyline", { points: "15 3 21 3 21 9" }),
40859
- /* @__PURE__ */ jsx57("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
40860
- ]
40861
- }
40862
- )
40863
- ]
40864
- }
40865
- ),
40866
- /* @__PURE__ */ jsx57(
40867
- "button",
40868
- {
40869
- onClick: () => handleCopyHash(txHash),
40870
- title: "Copy full hash",
40871
- style: {
40872
- display: "inline-flex",
40873
- alignItems: "center",
40874
- gap: "0.2rem",
40875
- fontSize: fontSize.xs,
40876
- color: isCopied ? colors.green[500] : colors.mutedForeground,
40877
- backgroundColor: "transparent",
40878
- border: 0,
40879
- cursor: "pointer",
40880
- fontFamily: "inherit",
40881
- padding: 0,
40882
- transition: "color 0.15s"
40883
- },
40884
- children: isCopied ? "Copied!" : /* @__PURE__ */ jsxs47(
40885
- "svg",
40886
- {
40887
- style: {
40888
- width: "0.875rem",
40889
- height: "0.875rem"
40890
- },
40891
- viewBox: "0 0 24 24",
40892
- fill: "none",
40893
- stroke: "currentColor",
40894
- strokeWidth: 2,
40895
- strokeLinecap: "round",
40896
- strokeLinejoin: "round",
40897
- children: [
40898
- /* @__PURE__ */ jsx57(
40899
- "rect",
40900
- {
40901
- x: "9",
40902
- y: "9",
40903
- width: "13",
40904
- height: "13",
40905
- rx: "2",
40906
- ry: "2"
40907
- }
40908
- ),
40909
- /* @__PURE__ */ jsx57("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
40910
- ]
40911
- }
40912
- )
40913
- }
40914
- )
40915
- ]
40916
- }
40917
- )
40918
- ]
40919
- }
40920
- );
40921
- })(),
40922
- destTxHash && (() => {
40923
- const destUrl = buildExplorerUrl(
40924
- destTxHash,
40925
- toChain,
40926
- toChainTxUrl
40927
- );
40928
- const isCopied = copiedHash === destTxHash;
40929
- return /* @__PURE__ */ jsxs47(
40930
- "div",
40931
- {
40932
- style: {
40933
- display: "flex",
40934
- alignItems: "center",
40935
- justifyContent: "space-between"
40936
- },
40937
- children: [
40938
- /* @__PURE__ */ jsx57("span", { style: { color: colors.mutedForeground }, children: "Destination tx" }),
40939
- /* @__PURE__ */ jsxs47(
40940
- "div",
40941
- {
40942
- style: {
40943
- display: "inline-flex",
40944
- alignItems: "center",
40945
- gap: spacing[2]
40946
- },
40947
- children: [
40948
- destUrl && /* @__PURE__ */ jsxs47(
40949
- "a",
40950
- {
40951
- href: destUrl,
40952
- target: "_blank",
40953
- rel: "noopener noreferrer",
40954
- style: {
40955
- display: "inline-flex",
40956
- alignItems: "center",
40957
- gap: "0.25rem",
40958
- color: colors.foreground,
40959
- fontWeight: fontWeight.medium,
40960
- textDecorationLine: "underline",
40961
- textDecorationStyle: "dotted",
40962
- textUnderlineOffset: "4px"
40963
- },
40964
- children: [
40965
- destTxHash.slice(0, 6),
40966
- "\u2026",
40967
- destTxHash.slice(-4),
40968
- /* @__PURE__ */ jsxs47(
40969
- "svg",
40970
- {
40971
- style: { width: "0.75rem", height: "0.75rem" },
40972
- viewBox: "0 0 24 24",
40973
- fill: "none",
40974
- stroke: "currentColor",
40975
- strokeWidth: 2,
40976
- strokeLinecap: "round",
40977
- strokeLinejoin: "round",
40978
- children: [
40979
- /* @__PURE__ */ jsx57("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
40980
- /* @__PURE__ */ jsx57("polyline", { points: "15 3 21 3 21 9" }),
40981
- /* @__PURE__ */ jsx57("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
40982
- ]
40983
- }
40984
- )
40985
- ]
40986
- }
40987
- ),
40988
- /* @__PURE__ */ jsx57(
40989
- "button",
40990
- {
40991
- onClick: () => handleCopyHash(destTxHash),
40992
- title: "Copy full hash",
40993
- style: {
40994
- display: "inline-flex",
40995
- alignItems: "center",
40996
- fontSize: fontSize.xs,
40997
- color: isCopied ? colors.green[500] : colors.mutedForeground,
40998
- backgroundColor: "transparent",
40999
- border: 0,
41000
- cursor: "pointer",
41001
- fontFamily: "inherit",
41002
- padding: 0,
41003
- transition: "color 0.15s"
41004
- },
41005
- children: isCopied ? "Copied!" : /* @__PURE__ */ jsxs47(
41006
- "svg",
41007
- {
41008
- style: {
41009
- width: "0.875rem",
41010
- height: "0.875rem"
41011
- },
41012
- viewBox: "0 0 24 24",
41013
- fill: "none",
41014
- stroke: "currentColor",
41015
- strokeWidth: 2,
41016
- strokeLinecap: "round",
41017
- strokeLinejoin: "round",
41018
- children: [
41019
- /* @__PURE__ */ jsx57(
41020
- "rect",
41021
- {
41022
- x: "9",
41023
- y: "9",
41024
- width: "13",
41025
- height: "13",
41026
- rx: "2",
41027
- ry: "2"
41028
- }
41029
- ),
41030
- /* @__PURE__ */ jsx57("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
41031
- ]
41032
- }
41033
- )
41034
- }
41035
- )
41036
- ]
41037
- }
41038
- )
41039
- ]
41040
- }
41041
- );
41042
- })()
40770
+ txHash && /* @__PURE__ */ jsx57(
40771
+ SuccessTxHashRow,
40772
+ {
40773
+ label: destTxHash ? "Source tx" : "Transaction",
40774
+ hash: txHash,
40775
+ explorerUrl: buildExplorerUrl(
40776
+ txHash,
40777
+ fromChain,
40778
+ fromChainTxUrl
40779
+ ),
40780
+ isCopied: copiedHash === txHash,
40781
+ onCopy: handleCopyHash
40782
+ }
40783
+ ),
40784
+ destTxHash && /* @__PURE__ */ jsx57(
40785
+ SuccessTxHashRow,
40786
+ {
40787
+ label: "Destination tx",
40788
+ hash: destTxHash,
40789
+ explorerUrl: buildExplorerUrl(
40790
+ destTxHash,
40791
+ toChain,
40792
+ toChainTxUrl
40793
+ ),
40794
+ isCopied: copiedHash === destTxHash,
40795
+ onCopy: handleCopyHash
40796
+ }
40797
+ )
41043
40798
  ]
41044
40799
  }
41045
40800
  )
@@ -43388,6 +43143,119 @@ function SuccessReceiptRow({
43388
43143
  }
43389
43144
  );
43390
43145
  }
43146
+ function SuccessTxHashRow({
43147
+ label,
43148
+ hash,
43149
+ explorerUrl,
43150
+ isCopied,
43151
+ onCopy
43152
+ }) {
43153
+ const shortHash = `${hash.slice(0, 6)}\u2026${hash.slice(-4)}`;
43154
+ return /* @__PURE__ */ jsxs47(
43155
+ "div",
43156
+ {
43157
+ style: {
43158
+ display: "flex",
43159
+ alignItems: "center",
43160
+ justifyContent: "space-between"
43161
+ },
43162
+ children: [
43163
+ /* @__PURE__ */ jsx57("span", { style: { color: colors.mutedForeground }, children: label }),
43164
+ /* @__PURE__ */ jsxs47(
43165
+ "div",
43166
+ {
43167
+ style: {
43168
+ display: "inline-flex",
43169
+ alignItems: "center",
43170
+ gap: spacing[2]
43171
+ },
43172
+ children: [
43173
+ explorerUrl ? /* @__PURE__ */ jsxs47(
43174
+ "a",
43175
+ {
43176
+ href: explorerUrl,
43177
+ target: "_blank",
43178
+ rel: "noopener noreferrer",
43179
+ style: {
43180
+ display: "inline-flex",
43181
+ alignItems: "center",
43182
+ gap: "0.25rem",
43183
+ color: colors.foreground,
43184
+ fontWeight: fontWeight.medium,
43185
+ textDecorationLine: "underline",
43186
+ textDecorationStyle: "dotted",
43187
+ textUnderlineOffset: "4px"
43188
+ },
43189
+ children: [
43190
+ shortHash,
43191
+ /* @__PURE__ */ jsxs47(
43192
+ "svg",
43193
+ {
43194
+ style: { width: "0.75rem", height: "0.75rem" },
43195
+ viewBox: "0 0 24 24",
43196
+ fill: "none",
43197
+ stroke: "currentColor",
43198
+ strokeWidth: 2,
43199
+ strokeLinecap: "round",
43200
+ strokeLinejoin: "round",
43201
+ children: [
43202
+ /* @__PURE__ */ jsx57("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
43203
+ /* @__PURE__ */ jsx57("polyline", { points: "15 3 21 3 21 9" }),
43204
+ /* @__PURE__ */ jsx57("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
43205
+ ]
43206
+ }
43207
+ )
43208
+ ]
43209
+ }
43210
+ ) : /* @__PURE__ */ jsx57(
43211
+ "span",
43212
+ {
43213
+ style: { color: colors.foreground, fontWeight: fontWeight.medium },
43214
+ children: shortHash
43215
+ }
43216
+ ),
43217
+ /* @__PURE__ */ jsx57(
43218
+ "button",
43219
+ {
43220
+ onClick: () => onCopy(hash),
43221
+ title: "Copy full hash",
43222
+ style: {
43223
+ display: "inline-flex",
43224
+ alignItems: "center",
43225
+ fontSize: fontSize.xs,
43226
+ color: isCopied ? colors.green[500] : colors.mutedForeground,
43227
+ backgroundColor: "transparent",
43228
+ border: 0,
43229
+ cursor: "pointer",
43230
+ fontFamily: "inherit",
43231
+ padding: 0,
43232
+ transition: "color 0.15s"
43233
+ },
43234
+ children: isCopied ? "Copied!" : /* @__PURE__ */ jsxs47(
43235
+ "svg",
43236
+ {
43237
+ style: { width: "0.875rem", height: "0.875rem" },
43238
+ viewBox: "0 0 24 24",
43239
+ fill: "none",
43240
+ stroke: "currentColor",
43241
+ strokeWidth: 2,
43242
+ strokeLinecap: "round",
43243
+ strokeLinejoin: "round",
43244
+ children: [
43245
+ /* @__PURE__ */ jsx57("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
43246
+ /* @__PURE__ */ jsx57("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
43247
+ ]
43248
+ }
43249
+ )
43250
+ }
43251
+ )
43252
+ ]
43253
+ }
43254
+ )
43255
+ ]
43256
+ }
43257
+ );
43258
+ }
43391
43259
  function ProcessingStepNode({
43392
43260
  active,
43393
43261
  done,
@@ -43542,6 +43410,7 @@ var init_SwapMode = __esm({
43542
43410
  init_SwapTokenSelect();
43543
43411
  init_VaultDiscovery();
43544
43412
  init_currency();
43413
+ init_amountFormat();
43545
43414
  init_quoteCountdown();
43546
43415
  init_components2();
43547
43416
  ConfettiEffect3 = lazy2(