@trustware/sdk-staging 1.1.9-staging.7 → 1.1.11-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.cjs CHANGED
@@ -585,7 +585,7 @@ var init_constants = __esm({
585
585
  "src/constants.ts"() {
586
586
  "use strict";
587
587
  SDK_NAME = "@trustware/sdk";
588
- SDK_VERSION = "1.1.9-staging.7";
588
+ SDK_VERSION = "1.1.11-staging.1";
589
589
  API_ROOT = "https://bv-staging-api.trustware.io";
590
590
  GTM_ID = "GTM-TZDGNCXB";
591
591
  API_PREFIX = "/api";
@@ -629,6 +629,9 @@ async function assertOK(r) {
629
629
  }
630
630
  throw new Error(`HTTP ${r.status}: ${msg}`);
631
631
  }
632
+ function isNotFoundError(err) {
633
+ return err instanceof Error && err.message.startsWith("HTTP 404");
634
+ }
632
635
  async function validateSdkAccess() {
633
636
  const r = await fetch(`${apiBase()}/sdk/validate`, {
634
637
  method: "GET",
@@ -2464,6 +2467,27 @@ var init_balances = __esm({
2464
2467
  });
2465
2468
 
2466
2469
  // src/core/routes.ts
2470
+ function assertValidPostHook(hooks) {
2471
+ const postHook = hooks?.postHook;
2472
+ if (!postHook) return;
2473
+ if (!postHook.target?.trim()) {
2474
+ throw new Error("hooks.postHook.target is required.");
2475
+ }
2476
+ if (!postHook.callData?.trim()) {
2477
+ throw new Error("hooks.postHook.callData is required.");
2478
+ }
2479
+ if (postHook.fullAmount) {
2480
+ if (postHook.amountInputPos === void 0) {
2481
+ throw new Error(
2482
+ "hooks.postHook.amountInputPos is required when fullAmount is true."
2483
+ );
2484
+ }
2485
+ } else if (!postHook.fundAmount?.trim()) {
2486
+ throw new Error(
2487
+ "hooks.postHook.fundAmount is required unless fullAmount is set."
2488
+ );
2489
+ }
2490
+ }
2467
2491
  function isEvmTxRequest(txReq) {
2468
2492
  return Boolean(txReq?.data && (txReq.to || txReq.target));
2469
2493
  }
@@ -2482,6 +2506,7 @@ async function buildRoute(body, signal) {
2482
2506
  if (!addressValidation.isValid) {
2483
2507
  throw new Error(addressValidation.error || "Invalid route addresses.");
2484
2508
  }
2509
+ assertValidPostHook(body.hooks);
2485
2510
  const cfg = TrustwareConfigStore.get();
2486
2511
  const url = `${apiBase()}/v1/routes/route`;
2487
2512
  const payload = {
@@ -2520,6 +2545,7 @@ async function buildRoute(body, signal) {
2520
2545
  return { intentId, txReq, actions, finalExchangeRate, route, sponsorship };
2521
2546
  }
2522
2547
  async function buildDepositAddress(body, signal) {
2548
+ assertValidPostHook(body.hooks);
2523
2549
  const cfg = TrustwareConfigStore.get();
2524
2550
  const url = `${apiBase()}/v1/routes/deposit-address`;
2525
2551
  const payload = {
@@ -2578,6 +2604,19 @@ async function submitReceipt(intentId, txHash, sponsorshipRequestId, eoaAddress)
2578
2604
  const j = await r.json();
2579
2605
  return j.data;
2580
2606
  }
2607
+ async function submitStepReceipt(intentId, stepIndex, txHash) {
2608
+ const r = await rateLimitedFetch(
2609
+ `${apiBase()}/v1/route-intent/${intentId}/steps/${stepIndex}/receipt`,
2610
+ {
2611
+ method: "POST",
2612
+ headers: jsonHeaders({ "Idempotency-Key": txHash }),
2613
+ body: JSON.stringify({ txHash })
2614
+ }
2615
+ );
2616
+ await assertOK(r);
2617
+ const j = await r.json();
2618
+ return j.data;
2619
+ }
2581
2620
  async function getStatus(intentId) {
2582
2621
  const r = await rateLimitedFetch(
2583
2622
  `${apiBase()}/v1/route-intent/${intentId}/status`,
@@ -5302,75 +5341,17 @@ var init_useTransactionSubmit = __esm({
5302
5341
  });
5303
5342
 
5304
5343
  // src/hooks/useGTM.ts
5305
- function useGTM(gtmId) {
5306
- const isInitialized = (0, import_react7.useRef)(false);
5307
- const scriptRef = (0, import_react7.useRef)(null);
5308
- const isGA4Allowed = (0, import_react7.useCallback)(() => {
5309
- try {
5310
- return Trustware.getConfig().features.shouldAllowGA4;
5311
- } catch {
5312
- return false;
5313
- }
5314
- }, []);
5315
- (0, import_react7.useEffect)(() => {
5316
- if (!isGA4Allowed()) {
5317
- console.warn(
5318
- "useGTM: GA4 tracking is disabled or Trustware config not initialized."
5319
- );
5320
- return;
5321
- }
5322
- if (!gtmId || typeof gtmId !== "string") {
5323
- console.error(
5324
- "useGTM: A valid GTM Container ID is required (format: GTM-XXXXX)."
5325
- );
5326
- return;
5327
- }
5328
- if (isInitialized.current) return;
5329
- const alreadyLoaded = document.querySelector(
5330
- `script[src*="googletagmanager.com/gtm.js?id=${gtmId}"]`
5331
- );
5332
- if (alreadyLoaded) {
5333
- window.dataLayer = window.dataLayer || [];
5334
- isInitialized.current = true;
5335
- return;
5336
- }
5337
- window.dataLayer = window.dataLayer || [];
5338
- window.dataLayer.push({
5339
- "gtm.start": (/* @__PURE__ */ new Date()).getTime(),
5340
- event: "gtm.js"
5341
- });
5342
- const firstScript = document.getElementsByTagName("script")[0];
5343
- const script = document.createElement("script");
5344
- script.async = true;
5345
- script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
5346
- firstScript.parentNode?.insertBefore(script, firstScript);
5347
- scriptRef.current = script;
5348
- isInitialized.current = true;
5349
- return () => {
5350
- if (scriptRef.current) {
5351
- scriptRef.current.parentNode?.removeChild(scriptRef.current);
5352
- scriptRef.current = null;
5353
- }
5354
- isInitialized.current = false;
5355
- };
5356
- }, [gtmId, isGA4Allowed]);
5357
- const addNoscriptIframe = (0, import_react7.useCallback)(() => {
5358
- if (!isGA4Allowed()) return;
5359
- if (document.querySelector('iframe[src*="googletagmanager.com/ns.html"]'))
5360
- return;
5361
- const noscript = document.createElement("noscript");
5362
- const iframe = document.createElement("iframe");
5363
- iframe.src = `https://www.googletagmanager.com/ns.html?id=${gtmId}`;
5364
- iframe.height = "0";
5365
- iframe.width = "0";
5366
- iframe.style.display = "none";
5367
- iframe.style.visibility = "hidden";
5368
- noscript.appendChild(iframe);
5369
- document.body.insertBefore(noscript, document.body.firstChild);
5370
- }, [gtmId, isGA4Allowed]);
5344
+ function isGA4Allowed() {
5345
+ try {
5346
+ return Trustware.getConfig().features.shouldAllowGA4;
5347
+ } catch {
5348
+ return false;
5349
+ }
5350
+ }
5351
+ function useGTMTracker() {
5371
5352
  const trackEvent = (0, import_react7.useCallback)(
5372
5353
  (eventName, eventParams = {}) => {
5373
- if (!isInitialized.current) {
5354
+ if (!isContainerInitialized) {
5374
5355
  console.warn(
5375
5356
  "useGTM: Not initialized. Ensure a valid GTM Container ID was provided."
5376
5357
  );
@@ -5397,7 +5378,7 @@ function useGTM(gtmId) {
5397
5378
  );
5398
5379
  const setUserProperty = (0, import_react7.useCallback)(
5399
5380
  (propertyName, value) => {
5400
- if (!isInitialized.current) {
5381
+ if (!isContainerInitialized) {
5401
5382
  console.warn(
5402
5383
  "useGTM: Not initialized. Ensure a valid GTM Container ID was provided."
5403
5384
  );
@@ -5423,22 +5404,78 @@ function useGTM(gtmId) {
5423
5404
  }
5424
5405
  window.gtag(command, ...args);
5425
5406
  },
5426
- [isGA4Allowed]
5407
+ []
5427
5408
  );
5428
- return {
5429
- trackEvent,
5430
- trackPageView,
5431
- setUserProperty,
5432
- directGtag,
5433
- addNoscriptIframe
5434
- };
5409
+ return { trackEvent, trackPageView, setUserProperty, directGtag };
5410
+ }
5411
+ function useGTM(gtmId) {
5412
+ const scriptRef = (0, import_react7.useRef)(null);
5413
+ const tracker = useGTMTracker();
5414
+ (0, import_react7.useEffect)(() => {
5415
+ if (!isGA4Allowed()) {
5416
+ console.warn(
5417
+ "useGTM: GA4 tracking is disabled or Trustware config not initialized."
5418
+ );
5419
+ return;
5420
+ }
5421
+ if (!gtmId || typeof gtmId !== "string") {
5422
+ console.error(
5423
+ "useGTM: A valid GTM Container ID is required (format: GTM-XXXXX)."
5424
+ );
5425
+ return;
5426
+ }
5427
+ if (isContainerInitialized) return;
5428
+ const alreadyLoaded = document.querySelector(
5429
+ `script[src*="googletagmanager.com/gtm.js?id=${gtmId}"]`
5430
+ );
5431
+ if (alreadyLoaded) {
5432
+ window.dataLayer = window.dataLayer || [];
5433
+ isContainerInitialized = true;
5434
+ return;
5435
+ }
5436
+ window.dataLayer = window.dataLayer || [];
5437
+ window.dataLayer.push({
5438
+ "gtm.start": (/* @__PURE__ */ new Date()).getTime(),
5439
+ event: "gtm.js"
5440
+ });
5441
+ const firstScript = document.getElementsByTagName("script")[0];
5442
+ const script = document.createElement("script");
5443
+ script.async = true;
5444
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
5445
+ firstScript.parentNode?.insertBefore(script, firstScript);
5446
+ scriptRef.current = script;
5447
+ isContainerInitialized = true;
5448
+ return () => {
5449
+ if (scriptRef.current) {
5450
+ scriptRef.current.parentNode?.removeChild(scriptRef.current);
5451
+ scriptRef.current = null;
5452
+ }
5453
+ isContainerInitialized = false;
5454
+ };
5455
+ }, [gtmId]);
5456
+ const addNoscriptIframe = (0, import_react7.useCallback)(() => {
5457
+ if (!isGA4Allowed()) return;
5458
+ if (document.querySelector('iframe[src*="googletagmanager.com/ns.html"]'))
5459
+ return;
5460
+ const noscript = document.createElement("noscript");
5461
+ const iframe = document.createElement("iframe");
5462
+ iframe.src = `https://www.googletagmanager.com/ns.html?id=${gtmId}`;
5463
+ iframe.height = "0";
5464
+ iframe.width = "0";
5465
+ iframe.style.display = "none";
5466
+ iframe.style.visibility = "hidden";
5467
+ noscript.appendChild(iframe);
5468
+ document.body.insertBefore(noscript, document.body.firstChild);
5469
+ }, [gtmId]);
5470
+ return { ...tracker, addNoscriptIframe };
5435
5471
  }
5436
- var import_react7;
5472
+ var import_react7, isContainerInitialized;
5437
5473
  var init_useGTM = __esm({
5438
5474
  "src/hooks/useGTM.ts"() {
5439
5475
  "use strict";
5440
5476
  import_react7 = require("react");
5441
5477
  init_core2();
5478
+ isContainerInitialized = false;
5442
5479
  }
5443
5480
  });
5444
5481
 
@@ -5464,7 +5501,7 @@ function useTransactionPolling() {
5464
5501
  return void 0;
5465
5502
  }
5466
5503
  })();
5467
- const { trackEvent } = useGTM(GTM_ID);
5504
+ const { trackEvent } = useGTMTracker();
5468
5505
  const [state, setState] = (0, import_react8.useState)({
5469
5506
  isPolling: false,
5470
5507
  apiStatus: null,
@@ -5564,8 +5601,21 @@ function useTransactionPolling() {
5564
5601
  pollCountRef.current += 1;
5565
5602
  const pollInterval = pollCountRef.current <= 10 ? FAST_POLL_INTERVAL_MS : NORMAL_POLL_INTERVAL_MS;
5566
5603
  pollingRef.current = setTimeout(poll, pollInterval);
5567
- } catch {
5604
+ } catch (err) {
5568
5605
  if (abortRef.current) return;
5606
+ if (isNotFoundError(err)) {
5607
+ clearPolling();
5608
+ const notFoundError = "Transaction session expired. Please try again.";
5609
+ setState((prev) => ({
5610
+ ...prev,
5611
+ isPolling: false,
5612
+ error: notFoundError
5613
+ }));
5614
+ setErrorMessage(notFoundError);
5615
+ setTransactionStatus("error");
5616
+ setCurrentStep("error");
5617
+ return;
5618
+ }
5569
5619
  const elapsed = Date.now() - startTimeRef.current;
5570
5620
  if (elapsed > POLL_TIMEOUT_MS) {
5571
5621
  clearPolling();
@@ -5702,10 +5752,10 @@ var init_useTransactionPolling = __esm({
5702
5752
  "use client";
5703
5753
  import_react8 = require("react");
5704
5754
  init_routes();
5755
+ init_http();
5705
5756
  init_DepositContext();
5706
5757
  init_provider();
5707
5758
  init_core2();
5708
- init_constants();
5709
5759
  init_hooks();
5710
5760
  FAST_POLL_INTERVAL_MS = 1500;
5711
5761
  NORMAL_POLL_INTERVAL_MS = 2500;
@@ -5787,6 +5837,20 @@ var init_eipWallets = __esm({
5787
5837
  "src/wallets/eipWallets.ts"() {
5788
5838
  "use strict";
5789
5839
  CHAIN_PARAMS = {
5840
+ 1: {
5841
+ chainIdHex: "0x1",
5842
+ chainName: "Ethereum",
5843
+ rpcUrls: ["https://eth.llamarpc.com"],
5844
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
5845
+ blockExplorerUrls: ["https://etherscan.io"]
5846
+ },
5847
+ 137: {
5848
+ chainIdHex: "0x89",
5849
+ chainName: "Polygon",
5850
+ rpcUrls: ["https://polygon-rpc.com"],
5851
+ nativeCurrency: { name: "POL", symbol: "POL", decimals: 18 },
5852
+ blockExplorerUrls: ["https://polygonscan.com"]
5853
+ },
5790
5854
  8453: {
5791
5855
  chainIdHex: "0x2105",
5792
5856
  chainName: "Base",
@@ -9024,13 +9088,107 @@ function backendChainId(chain, fallback) {
9024
9088
  const preferred = chain?.networkIdentifier ?? chain?.chainId ?? chain?.id;
9025
9089
  return String(preferred ?? fallback ?? "");
9026
9090
  }
9091
+ function sleep2(ms) {
9092
+ return new Promise((resolve) => setTimeout(resolve, ms));
9093
+ }
9094
+ async function sendEvmTx(w, tx) {
9095
+ if (w.type === "eip1193") {
9096
+ const from = await w.getAddress();
9097
+ const params = {
9098
+ from,
9099
+ to: tx.to,
9100
+ data: tx.data,
9101
+ value: tx.value ? `0x${tx.value.toString(16)}` : "0x0"
9102
+ };
9103
+ if (tx.chainId !== void 0) {
9104
+ params.chainId = `0x${tx.chainId.toString(16)}`;
9105
+ }
9106
+ const hash = await w.request({
9107
+ method: "eth_sendTransaction",
9108
+ params: [params]
9109
+ });
9110
+ return hash;
9111
+ }
9112
+ const response = await w.sendTransaction({
9113
+ to: tx.to,
9114
+ data: tx.data,
9115
+ value: tx.value,
9116
+ chainId: tx.chainId
9117
+ });
9118
+ return response.hash;
9119
+ }
9120
+ async function waitForTxConfirmation(chainId, txHash) {
9121
+ const timeoutMs = 12e4;
9122
+ const intervalMs = 2e3;
9123
+ const started = Date.now();
9124
+ while (Date.now() - started < timeoutMs) {
9125
+ const status = await getEVMTxStatus({ chainId, txHash });
9126
+ if (status.status === "success") return;
9127
+ if (status.status === "reverted") {
9128
+ throw new Error("Approval transaction reverted");
9129
+ }
9130
+ await sleep2(intervalMs);
9131
+ }
9132
+ throw new Error("Timed out waiting for approval confirmation");
9133
+ }
9134
+ function requiredApprovalAmount(planAmountWei, routeAmountWei) {
9135
+ if (planAmountWei >= UNLIMITED_APPROVAL_FLOOR && routeAmountWei !== void 0 && routeAmountWei > 0n) {
9136
+ return routeAmountWei;
9137
+ }
9138
+ return planAmountWei;
9139
+ }
9140
+ function approvalSatisfied(currentAllowance, requiredWei) {
9141
+ if (requiredWei === 0n) return currentAllowance === 0n;
9142
+ return currentAllowance >= requiredWei;
9143
+ }
9144
+ async function ensureApprovals(w, approvals, fallbackChainId, intentId, routeAmountWei) {
9145
+ if (!approvals || approvals.length === 0) return;
9146
+ const owner = await w.getAddress();
9147
+ for (const [stepIndex, approval] of approvals.entries()) {
9148
+ const spender = approval.spender;
9149
+ const tokenAddress = approval.tokenAddress;
9150
+ const amount = approval.amount;
9151
+ if (!spender || !tokenAddress || !amount) continue;
9152
+ const chainId = approval.chainId || String(fallbackChainId ?? "");
9153
+ if (!chainId) continue;
9154
+ const amountWei = requiredApprovalAmount(BigInt(amount), routeAmountWei);
9155
+ let currentAllowance = 0n;
9156
+ try {
9157
+ const { allowance } = await getEVMAllowance({
9158
+ chainId,
9159
+ tokenAddress,
9160
+ ownerAddress: owner,
9161
+ spenderAddress: spender
9162
+ });
9163
+ currentAllowance = BigInt(allowance || "0");
9164
+ } catch {
9165
+ }
9166
+ if (approvalSatisfied(currentAllowance, amountWei)) continue;
9167
+ const data = (0, import_viem2.encodeFunctionData)({
9168
+ abi: erc20ApproveAbi,
9169
+ functionName: "approve",
9170
+ args: [spender, amountWei]
9171
+ });
9172
+ const hash = await sendEvmTx(w, {
9173
+ to: tokenAddress,
9174
+ data,
9175
+ value: 0n,
9176
+ chainId: Number.isFinite(Number(chainId)) ? Number(chainId) : void 0
9177
+ });
9178
+ if (intentId) {
9179
+ void submitStepReceipt(intentId, stepIndex, hash).catch(() => {
9180
+ });
9181
+ }
9182
+ await waitForTxConfirmation(chainId, hash);
9183
+ }
9184
+ }
9027
9185
  function isUserRejected(e2) {
9028
9186
  const code = e2?.code ?? e2?.data?.code;
9029
9187
  if (code === 4001) return true;
9030
9188
  const msg = String(e2?.message || e2)?.toLowerCase?.() || "";
9031
9189
  return msg.includes("user rejected") || msg.includes("user denied");
9032
9190
  }
9033
- async function sendRouteTransaction(b, fallbackChainId) {
9191
+ async function sendRouteTransaction(b, fallbackChainId, options) {
9034
9192
  const w = walletManager.wallet;
9035
9193
  if (!w) throw new Error("Trustware.wallet not configured");
9036
9194
  const txReq = b.txReq;
@@ -9057,6 +9215,15 @@ async function sendRouteTransaction(b, fallbackChainId) {
9057
9215
  }
9058
9216
  }
9059
9217
  }
9218
+ if (!validatedSponsorship && !options?.approvalsEnsured) {
9219
+ await ensureApprovals(
9220
+ w,
9221
+ b.route?.execution?.approvals,
9222
+ Number.isFinite(target) ? target : void 0,
9223
+ b.intentId,
9224
+ BigInt(b.route?.estimate?.fromAmount ?? "0")
9225
+ );
9226
+ }
9060
9227
  if (w.type === "eip1193") {
9061
9228
  const from = await w.getAddress();
9062
9229
  const hexValue = value ? `0x${value.toString(16)}` : "0x0";
@@ -9153,13 +9320,18 @@ async function runTopUp(params) {
9153
9320
  }
9154
9321
  }
9155
9322
  }
9156
- var import_viem2;
9323
+ var import_viem2, erc20ApproveAbi, UNLIMITED_APPROVAL_FLOOR;
9157
9324
  var init_tx = __esm({
9158
9325
  "src/core/tx.ts"() {
9159
9326
  "use strict";
9160
9327
  init_wallets();
9161
9328
  init_routes();
9329
+ init_sdkRpc();
9162
9330
  import_viem2 = require("viem");
9331
+ erc20ApproveAbi = (0, import_viem2.parseAbi)([
9332
+ "function approve(address spender, uint256 amount) returns (bool)"
9333
+ ]);
9334
+ UNLIMITED_APPROVAL_FLOOR = 1n << 255n;
9163
9335
  }
9164
9336
  });
9165
9337
 
@@ -9305,6 +9477,7 @@ var init_core = __esm({
9305
9477
  buildRoute,
9306
9478
  buildDepositAddress,
9307
9479
  submitReceipt,
9480
+ submitStepReceipt,
9308
9481
  getStatus,
9309
9482
  pollStatus,
9310
9483
  getBalances,
@@ -31517,7 +31690,7 @@ function normalizeTokenAddressForCompare(chain, addr) {
31517
31690
  if (chainType === "solana") return value;
31518
31691
  return value.toLowerCase();
31519
31692
  }
31520
- function sleep2(ms) {
31693
+ function sleep3(ms) {
31521
31694
  return new Promise((resolve) => setTimeout(resolve, ms));
31522
31695
  }
31523
31696
  function useTransactionActionModel({
@@ -31533,7 +31706,7 @@ function useTransactionActionModel({
31533
31706
  }) {
31534
31707
  const feeDataCacheRef = (0, import_react37.useRef)({});
31535
31708
  const { isSubmitting, submitTransaction } = useTransactionSubmit();
31536
- const { trackEvent } = useGTM(GTM_ID);
31709
+ const { trackEvent } = useGTMTracker();
31537
31710
  const destinationConfig = (() => {
31538
31711
  try {
31539
31712
  return Trustware.getConfig();
@@ -31635,7 +31808,7 @@ function useTransactionActionModel({
31635
31808
  if (status.status === "reverted") {
31636
31809
  throw new Error("Approval transaction reverted");
31637
31810
  }
31638
- await sleep2(intervalMs);
31811
+ await sleep3(intervalMs);
31639
31812
  }
31640
31813
  throw new Error("Timed out waiting for approval confirmation");
31641
31814
  },
@@ -31969,7 +32142,6 @@ var init_useTransactionActionModel = __esm({
31969
32142
  init_chainHelpers();
31970
32143
  init_utils2();
31971
32144
  init_hooks();
31972
- init_constants();
31973
32145
  }
31974
32146
  });
31975
32147
 
@@ -33300,7 +33472,7 @@ function normalizeTx(raw) {
33300
33472
  toChainTxUrl: r.toChainTxUrl || r.to_chain_tx_url || void 0
33301
33473
  };
33302
33474
  }
33303
- function sleep3(ms) {
33475
+ function sleep4(ms) {
33304
33476
  return new Promise((resolve) => setTimeout(resolve, ms));
33305
33477
  }
33306
33478
  function isUserRejection(err) {
@@ -33318,10 +33490,20 @@ async function waitForApprovalConfirmation(chainId, txHash) {
33318
33490
  if (status.status === "success") return;
33319
33491
  if (status.status === "reverted")
33320
33492
  throw new Error("Approval transaction reverted");
33321
- await sleep3(2e3);
33493
+ await sleep4(2e3);
33322
33494
  }
33323
33495
  throw new Error("Timed out waiting for approval confirmation");
33324
33496
  }
33497
+ async function requireActiveAddress(wallet, plannedFor) {
33498
+ if (!wallet) throw new Error("Wallet not connected. Please reconnect.");
33499
+ const active = await wallet.getAddress();
33500
+ if (active?.toLowerCase() !== plannedFor?.toLowerCase()) {
33501
+ throw new Error(
33502
+ "Wallet account changed. Please review and confirm the swap again."
33503
+ );
33504
+ }
33505
+ return active;
33506
+ }
33325
33507
  function useSwapExecution(fromChain) {
33326
33508
  const [state, setState] = (0, import_react45.useState)({
33327
33509
  txStatus: "idle",
@@ -33378,7 +33560,15 @@ function useSwapExecution(fromChain) {
33378
33560
  if (tx.status === "bridging") {
33379
33561
  setState((p) => ({ ...p, txStatus: "bridging" }));
33380
33562
  }
33381
- } catch {
33563
+ } catch (err) {
33564
+ if (abortRef.current) return;
33565
+ if (isNotFoundError(err)) {
33566
+ clearPolling();
33567
+ const msg = "Transaction session expired. Please try again.";
33568
+ setState((p) => ({ ...p, txStatus: "error", errorMessage: msg }));
33569
+ onError(msg);
33570
+ return;
33571
+ }
33382
33572
  }
33383
33573
  if (abortRef.current) return;
33384
33574
  pollCountRef.current += 1;
@@ -33404,24 +33594,55 @@ function useSwapExecution(fromChain) {
33404
33594
  }
33405
33595
  const txReq = routeResult.txReq;
33406
33596
  const chainIdStr = String(txReq?.chainId ?? fromChain?.chainId ?? "");
33407
- const spender = txReq?.to ?? txReq?.target;
33408
33597
  const amountWei = BigInt(routeResult.route?.estimate?.fromAmount ?? "0");
33409
- if (!spender || !chainIdStr || amountWei === 0n) {
33598
+ const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
33599
+ const toCheck = plannedApprovals.length ? plannedApprovals.flatMap((a) => {
33600
+ const token = a.tokenAddress ?? fromTokenAddress;
33601
+ const required = requiredApprovalAmount(
33602
+ BigInt(a.amount || "0"),
33603
+ amountWei
33604
+ );
33605
+ if (!token || !a.spender) return [];
33606
+ return [
33607
+ {
33608
+ chainId: a.chainId || chainIdStr,
33609
+ token,
33610
+ spender: a.spender,
33611
+ required
33612
+ }
33613
+ ];
33614
+ }) : (() => {
33615
+ const spender = txReq?.to ?? txReq?.target;
33616
+ if (!spender) return [];
33617
+ return [
33618
+ {
33619
+ chainId: chainIdStr,
33620
+ token: fromTokenAddress,
33621
+ spender,
33622
+ required: amountWei
33623
+ }
33624
+ ];
33625
+ })();
33626
+ if (toCheck.length === 0 || !chainIdStr || amountWei === 0n) {
33410
33627
  setState((p) => ({ ...p, allowanceStatus: "unknown" }));
33411
33628
  return;
33412
33629
  }
33413
33630
  setState((p) => ({ ...p, allowanceStatus: "checking" }));
33414
33631
  try {
33415
- const { allowance } = await getEVMAllowance({
33416
- chainId: chainIdStr,
33417
- tokenAddress: fromTokenAddress,
33418
- ownerAddress: walletAddress,
33419
- spenderAddress: spender
33420
- });
33421
- const allowanceWei = BigInt(allowance || "0");
33632
+ const results = await Promise.all(
33633
+ toCheck.map(async (c) => {
33634
+ const { allowance } = await getEVMAllowance({
33635
+ chainId: c.chainId,
33636
+ tokenAddress: c.token,
33637
+ ownerAddress: walletAddress,
33638
+ spenderAddress: c.spender
33639
+ });
33640
+ return approvalSatisfied(BigInt(allowance || "0"), c.required);
33641
+ })
33642
+ );
33422
33643
  setState((p) => ({
33423
33644
  ...p,
33424
- allowanceStatus: allowanceWei >= amountWei ? "sufficient" : "needed"
33645
+ allowanceStatus: results.every(Boolean) ? "sufficient" : "needed"
33425
33646
  }));
33426
33647
  } catch {
33427
33648
  setState((p) => ({ ...p, allowanceStatus: "needed" }));
@@ -33525,19 +33746,49 @@ function useSwapExecution(fromChain) {
33525
33746
  }
33526
33747
  }
33527
33748
  try {
33528
- if (!isNative && walletAddress && spender && chainIdStr && fromTokenAddress) {
33749
+ const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
33750
+ const requiredApprovals = plannedApprovals.length ? plannedApprovals.flatMap((a, index) => {
33751
+ const token = a.tokenAddress ?? fromTokenAddress;
33752
+ const approvalSpender = a.spender;
33753
+ if (!token || !approvalSpender) return [];
33754
+ const amountWei = requiredApprovalAmount(
33755
+ BigInt(a.amount || "0"),
33756
+ fromAmountWei
33757
+ );
33758
+ return [
33759
+ {
33760
+ stepIndex: index,
33761
+ token,
33762
+ spender: approvalSpender,
33763
+ amountWei,
33764
+ chainId: a.chainId || chainIdStr
33765
+ }
33766
+ ];
33767
+ }) : spender && fromTokenAddress ? [
33768
+ {
33769
+ stepIndex: null,
33770
+ token: fromTokenAddress,
33771
+ spender,
33772
+ amountWei: fromAmountWei,
33773
+ chainId: chainIdStr
33774
+ }
33775
+ ] : [];
33776
+ const ownsApprovals = !isNative && !!walletAddress && !!chainIdStr;
33777
+ const activeAddress = ownsApprovals ? await requireActiveAddress(wallet, walletAddress) : void 0;
33778
+ for (const required of requiredApprovals) {
33779
+ if (!ownsApprovals) break;
33529
33780
  let allowanceWei = 0n;
33530
33781
  try {
33531
33782
  const { allowance } = await getEVMAllowance({
33532
- chainId: chainIdStr,
33533
- tokenAddress: fromTokenAddress,
33534
- ownerAddress: walletAddress,
33535
- spenderAddress: spender
33783
+ chainId: required.chainId,
33784
+ tokenAddress: required.token,
33785
+ ownerAddress: activeAddress,
33786
+ spenderAddress: required.spender
33536
33787
  });
33537
33788
  allowanceWei = BigInt(allowance || "0");
33538
33789
  } catch {
33539
33790
  }
33540
- if (allowanceWei < fromAmountWei) {
33791
+ if (!approvalSatisfied(allowanceWei, required.amountWei)) {
33541
33792
  setState((p) => ({ ...p, txStatus: "approving" }));
33542
33793
  if (!wallet || wallet.ecosystem !== "evm") {
33543
33794
  throw new Error("EVM wallet required for token approval");
@@ -33545,7 +33796,7 @@ function useSwapExecution(fromChain) {
33545
33796
  if (!chainIdStr || chainIdStr === "0") {
33546
33797
  throw new Error("Invalid chain ID for token approval");
33547
33798
  }
33548
- const targetChain = Number(txReq.chainId ?? fromChain?.chainId);
33799
+ const targetChain = Number(required.chainId ?? txReq.chainId);
33549
33800
  if (Number.isFinite(targetChain)) {
33550
33801
  const current = await wallet.getChainId();
33551
33802
  if (current !== targetChain) {
@@ -33557,18 +33808,18 @@ function useSwapExecution(fromChain) {
33557
33808
  }
33558
33809
  const approveAmount = maxApproval ? BigInt(
33559
33810
  "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
33560
- ) : fromAmountWei;
33811
+ ) : required.amountWei;
33561
33812
  const data = (0, import_viem43.encodeFunctionData)({
33562
33813
  abi: import_viem43.erc20Abi,
33563
33814
  functionName: "approve",
33564
- args: [spender, approveAmount]
33815
+ args: [required.spender, approveAmount]
33565
33816
  });
33566
33817
  let approvalHash;
33567
33818
  if (wallet.type === "eip1193") {
33568
33819
  const from = await wallet.getAddress();
33569
33820
  const params = {
33570
33821
  from,
33571
- to: fromTokenAddress,
33822
+ to: required.token,
33572
33823
  data,
33573
33824
  value: "0x0"
33574
33825
  };
@@ -33581,14 +33832,22 @@ function useSwapExecution(fromChain) {
33581
33832
  });
33582
33833
  } else {
33583
33834
  const response = await wallet.sendTransaction({
33584
- to: fromTokenAddress,
33835
+ to: required.token,
33585
33836
  data,
33586
33837
  value: 0n,
33587
33838
  chainId: Number.isFinite(targetChain) ? targetChain : void 0
33588
33839
  });
33589
33840
  approvalHash = response.hash;
33590
33841
  }
33591
- await waitForApprovalConfirmation(chainIdStr, approvalHash);
33842
+ if (required.stepIndex !== null) {
33843
+ void submitStepReceipt(
33844
+ routeResult.intentId,
33845
+ required.stepIndex,
33846
+ approvalHash
33847
+ ).catch(() => {
33848
+ });
33849
+ }
33850
+ await waitForApprovalConfirmation(required.chainId, approvalHash);
33592
33851
  setState((p) => ({
33593
33852
  ...p,
33594
33853
  txStatus: "confirming",
@@ -33596,9 +33855,13 @@ function useSwapExecution(fromChain) {
33596
33855
  }));
33597
33856
  }
33598
33857
  }
33858
+ if (ownsApprovals) {
33859
+ await requireActiveAddress(wallet, walletAddress);
33860
+ }
33599
33861
  const hash = await Trustware.sendRouteTransaction(
33600
33862
  routeResult,
33601
- numericChainId
33863
+ numericChainId,
33864
+ { approvalsEnsured: ownsApprovals }
33602
33865
  );
33603
33866
  try {
33604
33867
  await submitReceipt(routeResult.intentId, hash);
@@ -33668,6 +33931,8 @@ var init_useSwapExecution = __esm({
33668
33931
  import_viem43 = require("viem");
33669
33932
  init_core2();
33670
33933
  init_routes();
33934
+ init_http();
33935
+ init_tx();
33671
33936
  init_sdkRpc();
33672
33937
  init_chainHelpers();
33673
33938
  FAST_POLL_MS = 1500;
@@ -39617,6 +39882,7 @@ function WidgetInner({
39617
39882
  const { resolvedTheme } = useDepositUi();
39618
39883
  const { status, revalidate, errors } = useTrustware();
39619
39884
  const [showConfirmDialog, setShowConfirmDialog] = (0, import_react51.useState)(false);
39885
+ useGTM(GTM_ID);
39620
39886
  const handleCloseRequest = (0, import_react51.useCallback)(() => {
39621
39887
  if (ACTIVE_TRANSACTION_STATUSES.includes(transactionStatus)) {
39622
39888
  setShowConfirmDialog(true);
@@ -39691,6 +39957,8 @@ var init_TrustwareWidgetV2 = __esm({
39691
39957
  init_provider();
39692
39958
  init_manager();
39693
39959
  init_useTrustwareConfig();
39960
+ init_hooks();
39961
+ init_constants();
39694
39962
  init_swap();
39695
39963
  import_jsx_runtime60 = require("react/jsx-runtime");
39696
39964
  widgetContentContainerStyle = {