@trustware/sdk-staging 1.1.10-staging.0 → 1.1.11-staging.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 (38) hide show
  1. package/README.md +54 -21
  2. package/dist/constants.cjs +1 -1
  3. package/dist/constants.mjs +1 -1
  4. package/dist/{core-BPLiD1UG.d.ts → core-B1WCJfDI.d.ts} +15 -2
  5. package/dist/{core-B7Ez-5-r.d.cts → core-CeNTuqPz.d.cts} +15 -2
  6. package/dist/core.cjs +365 -279
  7. package/dist/core.cjs.map +1 -1
  8. package/dist/core.d.cts +2 -2
  9. package/dist/core.d.ts +2 -2
  10. package/dist/core.mjs +363 -278
  11. package/dist/core.mjs.map +1 -1
  12. package/dist/index.cjs +375 -170
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +11 -5
  15. package/dist/index.d.ts +11 -5
  16. package/dist/index.mjs +373 -169
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/{manager-B6NNV-E_.d.ts → manager-Bw8b1wSD.d.ts} +136 -21
  19. package/dist/{manager-BwjyFjjz.d.cts → manager-DNGk1YkR.d.cts} +136 -21
  20. package/dist/{metadata-BiTMMvlY.d.ts → metadata-C__KMeuz.d.ts} +1 -1
  21. package/dist/{metadata-HCflXcsF.d.cts → metadata-Dk4R8_-T.d.cts} +1 -1
  22. package/dist/smart-account.cjs +49 -44
  23. package/dist/smart-account.cjs.map +1 -1
  24. package/dist/smart-account.d.cts +17 -12
  25. package/dist/smart-account.d.ts +17 -12
  26. package/dist/smart-account.mjs +49 -44
  27. package/dist/smart-account.mjs.map +1 -1
  28. package/dist/wallet.cjs +54 -39
  29. package/dist/wallet.cjs.map +1 -1
  30. package/dist/wallet.d.cts +3 -3
  31. package/dist/wallet.d.ts +3 -3
  32. package/dist/wallet.mjs +54 -39
  33. package/dist/wallet.mjs.map +1 -1
  34. package/dist/widget.cjs +369 -170
  35. package/dist/widget.cjs.map +1 -1
  36. package/dist/widget.mjs +369 -169
  37. package/dist/widget.mjs.map +1 -1
  38. package/package.json +73 -5
package/dist/index.mjs CHANGED
@@ -25,18 +25,16 @@ var init_defaults = __esm({
25
25
  });
26
26
 
27
27
  // src/types/config.ts
28
- var DEFAULT_SLIPPAGE2, DEFAULT_AUTO_DETECT_PROVIDER2, DEFAULT_RETRY_CONFIG, DEFAULT_FEATURE_FLAGS;
28
+ var DEFAULT_SLIPPAGE2, DEFAULT_AUTO_DETECT_PROVIDER2, DEFAULT_RETRY_CONFIG, RATE_LIMIT_WAIT_BUDGET_MS, DEFAULT_FEATURE_FLAGS;
29
29
  var init_config = __esm({
30
30
  "src/types/config.ts"() {
31
31
  "use strict";
32
32
  DEFAULT_SLIPPAGE2 = 1;
33
33
  DEFAULT_AUTO_DETECT_PROVIDER2 = false;
34
34
  DEFAULT_RETRY_CONFIG = {
35
- autoRetry: true,
36
- maxRetries: 3,
37
- baseDelayMs: 1e3,
38
35
  approachingThreshold: 5
39
36
  };
37
+ RATE_LIMIT_WAIT_BUDGET_MS = 1e4;
40
38
  DEFAULT_FEATURE_FLAGS = {
41
39
  tokensPagination: true,
42
40
  balanceStreaming: false,
@@ -173,9 +171,6 @@ function resolveConfig(input) {
173
171
  const theme = input.theme ?? "system";
174
172
  const messages = deepMerge(DEFAULT_MESSAGES, input.messages);
175
173
  const retry = {
176
- autoRetry: input.retry?.autoRetry ?? DEFAULT_RETRY_CONFIG.autoRetry,
177
- maxRetries: input.retry?.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
178
- baseDelayMs: input.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,
179
174
  approachingThreshold: input.retry?.approachingThreshold ?? DEFAULT_RETRY_CONFIG.approachingThreshold,
180
175
  onRateLimitInfo: input.retry?.onRateLimitInfo,
181
176
  onRateLimited: input.retry?.onRateLimited,
@@ -442,6 +437,20 @@ var init_eipWallets = __esm({
442
437
  "src/wallets/eipWallets.ts"() {
443
438
  "use strict";
444
439
  CHAIN_PARAMS = {
440
+ 1: {
441
+ chainIdHex: "0x1",
442
+ chainName: "Ethereum",
443
+ rpcUrls: ["https://eth.llamarpc.com"],
444
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
445
+ blockExplorerUrls: ["https://etherscan.io"]
446
+ },
447
+ 137: {
448
+ chainIdHex: "0x89",
449
+ chainName: "Polygon",
450
+ rpcUrls: ["https://polygon-rpc.com"],
451
+ nativeCurrency: { name: "POL", symbol: "POL", decimals: 18 },
452
+ blockExplorerUrls: ["https://polygonscan.com"]
453
+ },
445
454
  8453: {
446
455
  chainIdHex: "0x2105",
447
456
  chainName: "Base",
@@ -473,7 +482,7 @@ var init_constants = __esm({
473
482
  "src/constants.ts"() {
474
483
  "use strict";
475
484
  SDK_NAME = "@trustware/sdk";
476
- SDK_VERSION = "1.1.10-staging.0";
485
+ SDK_VERSION = "1.1.11-staging.2";
477
486
  API_ROOT = "https://bv-staging-api.trustware.io";
478
487
  GTM_ID = "GTM-TZDGNCXB";
479
488
  API_PREFIX = "/api";
@@ -517,6 +526,9 @@ async function assertOK(r) {
517
526
  }
518
527
  throw new Error(`HTTP ${r.status}: ${msg}`);
519
528
  }
529
+ function isNotFoundError(err) {
530
+ return err instanceof Error && err.message.startsWith("HTTP 404");
531
+ }
520
532
  async function validateSdkAccess() {
521
533
  const r = await fetch(`${apiBase()}/sdk/validate`, {
522
534
  method: "GET",
@@ -557,50 +569,54 @@ function notifyRateLimitCallbacks(info, isRateLimited, retryCount) {
557
569
  retry.onRateLimitApproaching(info, retry.approachingThreshold);
558
570
  }
559
571
  }
560
- function calculateBackoffDelay(baseDelayMs, retryCount, retryAfter) {
561
- if (retryAfter && retryAfter > 0) {
562
- return retryAfter * 1e3;
572
+ function serverDirectedWaitMs(info) {
573
+ if (!info) return null;
574
+ if (typeof info.retryAfter === "number" && info.retryAfter > 0) {
575
+ return info.retryAfter * 1e3;
563
576
  }
564
- return baseDelayMs * Math.pow(2, retryCount);
577
+ if (typeof info.reset === "number" && info.reset > 0) {
578
+ const untilReset = info.reset * 1e3 - Date.now();
579
+ if (untilReset > 0) return untilReset;
580
+ }
581
+ return null;
565
582
  }
566
583
  function sleep(ms) {
567
584
  return new Promise((resolve) => setTimeout(resolve, ms));
568
585
  }
569
- async function rateLimitedFetch(url, options = {}) {
570
- const { skipRateLimit, ...fetchOptions } = options;
571
- const cfg = TrustwareConfigStore.get();
572
- if (!cfg.retry.autoRetry || skipRateLimit) {
573
- return fetch(url, fetchOptions);
574
- }
575
- const { maxRetries, baseDelayMs } = cfg.retry;
586
+ async function withRateLimitRetry(attempt) {
587
+ let waitedMs = 0;
576
588
  let retryCount = 0;
577
589
  while (true) {
578
- const response = await fetch(url, fetchOptions);
590
+ const response = await attempt();
579
591
  const rateLimitInfo = parseRateLimitHeaders(response);
580
- if (response.status === 429) {
592
+ if (response.status !== 429) {
581
593
  if (rateLimitInfo) {
582
- notifyRateLimitCallbacks(rateLimitInfo, true, retryCount);
594
+ notifyRateLimitCallbacks(rateLimitInfo, false, 0);
583
595
  }
584
- if (retryCount >= maxRetries) {
585
- throw new RateLimitError(
586
- rateLimitInfo || { limit: 0, remaining: 0, reset: 0 },
587
- true
588
- );
589
- }
590
- const delay = calculateBackoffDelay(
591
- baseDelayMs,
592
- retryCount,
593
- rateLimitInfo?.retryAfter
594
- );
595
- await sleep(delay);
596
- retryCount++;
597
- continue;
596
+ return response;
598
597
  }
599
598
  if (rateLimitInfo) {
600
- notifyRateLimitCallbacks(rateLimitInfo, false, 0);
599
+ notifyRateLimitCallbacks(rateLimitInfo, true, retryCount);
601
600
  }
602
- return response;
601
+ const waitMs = serverDirectedWaitMs(rateLimitInfo);
602
+ const info = rateLimitInfo || { limit: 0, remaining: 0, reset: 0 };
603
+ if (waitMs === null) {
604
+ throw new RateLimitError(info, true);
605
+ }
606
+ if (waitedMs + waitMs > RATE_LIMIT_WAIT_BUDGET_MS) {
607
+ throw new RateLimitError(info, false);
608
+ }
609
+ await sleep(waitMs);
610
+ waitedMs += waitMs;
611
+ retryCount++;
612
+ }
613
+ }
614
+ async function rateLimitedFetch(url, options = {}) {
615
+ const { skipRateLimit, ...fetchOptions } = options;
616
+ if (skipRateLimit) {
617
+ return fetch(url, fetchOptions);
603
618
  }
619
+ return withRateLimitRetry(() => fetch(url, fetchOptions));
604
620
  }
605
621
  var RateLimitError;
606
622
  var init_http = __esm({
@@ -608,9 +624,11 @@ var init_http = __esm({
608
624
  "use strict";
609
625
  init_constants();
610
626
  init_config2();
627
+ init_config();
611
628
  RateLimitError = class extends Error {
612
629
  constructor(info, retriesExhausted) {
613
- const message = retriesExhausted ? `Rate limit exceeded after max retries. Try again in ${info.retryAfter ?? Math.ceil((info.reset * 1e3 - Date.now()) / 1e3)} seconds.` : `Rate limit exceeded. Try again in ${info.retryAfter} seconds.`;
630
+ const seconds = info.retryAfter ?? (info.reset > 0 ? Math.max(0, Math.ceil((info.reset * 1e3 - Date.now()) / 1e3)) : null);
631
+ const message = seconds === null ? "Rate limit exceeded. The server did not say when to retry." : `Rate limit exceeded. Try again in ${seconds} seconds.`;
614
632
  super(message);
615
633
  this.name = "RateLimitError";
616
634
  this.rateLimitInfo = info;
@@ -4514,6 +4532,19 @@ async function submitReceipt(intentId, txHash, sponsorshipRequestId, eoaAddress)
4514
4532
  const j = await r.json();
4515
4533
  return j.data;
4516
4534
  }
4535
+ async function submitStepReceipt(intentId, stepIndex, txHash) {
4536
+ const r = await rateLimitedFetch(
4537
+ `${apiBase()}/v1/route-intent/${intentId}/steps/${stepIndex}/receipt`,
4538
+ {
4539
+ method: "POST",
4540
+ headers: jsonHeaders({ "Idempotency-Key": txHash }),
4541
+ body: JSON.stringify({ txHash })
4542
+ }
4543
+ );
4544
+ await assertOK(r);
4545
+ const j = await r.json();
4546
+ return j.data;
4547
+ }
4517
4548
  async function getStatus(intentId) {
4518
4549
  const r = await rateLimitedFetch(
4519
4550
  `${apiBase()}/v1/route-intent/${intentId}/status`,
@@ -4523,7 +4554,18 @@ async function getStatus(intentId) {
4523
4554
  );
4524
4555
  await assertOK(r);
4525
4556
  const j = await r.json();
4526
- return j.data;
4557
+ return normalizeStatusPayload(j.data);
4558
+ }
4559
+ function normalizeStatusPayload(raw) {
4560
+ if (!raw || typeof raw !== "object") return raw;
4561
+ const wire = raw;
4562
+ const out = { ...wire };
4563
+ for (const [snake, camel] of Object.entries(STATUS_WIRE_TO_CAMEL)) {
4564
+ if (out[camel] !== void 0) continue;
4565
+ if (!(snake in wire)) continue;
4566
+ out[camel] = wire[snake];
4567
+ }
4568
+ return out;
4527
4569
  }
4528
4570
  async function pollStatus(intentId, { intervalMs = 2e3, timeoutMs = 5 * 6e4 } = {}) {
4529
4571
  const t0 = Date.now();
@@ -4534,12 +4576,39 @@ async function pollStatus(intentId, { intervalMs = 2e3, timeoutMs = 5 * 6e4 } =
4534
4576
  await new Promise((r) => setTimeout(r, intervalMs));
4535
4577
  }
4536
4578
  }
4579
+ var STATUS_WIRE_TO_CAMEL;
4537
4580
  var init_routes = __esm({
4538
4581
  "src/core/routes.ts"() {
4539
4582
  "use strict";
4540
4583
  init_http();
4541
4584
  init_store();
4542
4585
  init_address();
4586
+ STATUS_WIRE_TO_CAMEL = {
4587
+ intent_id: "intentId",
4588
+ from_address: "fromAddress",
4589
+ to_address: "toAddress",
4590
+ from_chain_id: "fromChainId",
4591
+ to_chain_id: "toChainId",
4592
+ source_tx_hash: "sourceTxHash",
4593
+ dest_tx_hash: "destTxHash",
4594
+ request_id: "requestId",
4595
+ provider_request_id: "providerRequestId",
4596
+ transaction_request: "transactionRequest",
4597
+ status_raw: "statusRaw",
4598
+ route_path: "routePath",
4599
+ route_status: "routeStatus",
4600
+ to_amount_wei: "toAmountWei",
4601
+ from_chain_block: "fromChainBlock",
4602
+ to_chain_block: "toChainBlock",
4603
+ from_chain_tx_url: "fromChainTxUrl",
4604
+ to_chain_tx_url: "toChainTxUrl",
4605
+ gas_status: "gasStatus",
4606
+ is_gmp_transaction: "isGMPTransaction",
4607
+ axelar_transaction_url: "axelarTransactionUrl",
4608
+ create_date: "createdDate",
4609
+ update_date: "updatedDate",
4610
+ time_spent_ms: "timeSpentMs"
4611
+ };
4543
4612
  }
4544
4613
  });
4545
4614
 
@@ -5088,7 +5157,7 @@ async function getBalances(chainRef, address, opts) {
5088
5157
  if (cached) return cached;
5089
5158
  }
5090
5159
  const url = `${apiBase()}/v1/data/wallets/${encodeURIComponent(chainKey)}/${trimmedAddress}/balances`;
5091
- const response = await fetch(url, {
5160
+ const response = await rateLimitedFetch(url, {
5092
5161
  method: "GET",
5093
5162
  credentials: "omit",
5094
5163
  headers: jsonHeaders()
@@ -5252,18 +5321,27 @@ async function waitForTxConfirmation(chainId, txHash) {
5252
5321
  }
5253
5322
  throw new Error("Timed out waiting for approval confirmation");
5254
5323
  }
5255
- async function ensureApprovals(w, approvals, fallbackChainId) {
5324
+ function requiredApprovalAmount(planAmountWei, routeAmountWei) {
5325
+ if (planAmountWei >= UNLIMITED_APPROVAL_FLOOR && routeAmountWei !== void 0 && routeAmountWei > 0n) {
5326
+ return routeAmountWei;
5327
+ }
5328
+ return planAmountWei;
5329
+ }
5330
+ function approvalSatisfied(currentAllowance, requiredWei) {
5331
+ if (requiredWei === 0n) return currentAllowance === 0n;
5332
+ return currentAllowance >= requiredWei;
5333
+ }
5334
+ async function ensureApprovals(w, approvals, fallbackChainId, intentId, routeAmountWei) {
5256
5335
  if (!approvals || approvals.length === 0) return;
5257
5336
  const owner = await w.getAddress();
5258
- for (const approval of approvals) {
5337
+ for (const [stepIndex, approval] of approvals.entries()) {
5259
5338
  const spender = approval.spender;
5260
5339
  const tokenAddress = approval.tokenAddress;
5261
5340
  const amount = approval.amount;
5262
5341
  if (!spender || !tokenAddress || !amount) continue;
5263
5342
  const chainId = approval.chainId || String(fallbackChainId ?? "");
5264
5343
  if (!chainId) continue;
5265
- const amountWei = BigInt(amount);
5266
- if (amountWei === 0n) continue;
5344
+ const amountWei = requiredApprovalAmount(BigInt(amount), routeAmountWei);
5267
5345
  let currentAllowance = 0n;
5268
5346
  try {
5269
5347
  const { allowance } = await getEVMAllowance({
@@ -5275,7 +5353,7 @@ async function ensureApprovals(w, approvals, fallbackChainId) {
5275
5353
  currentAllowance = BigInt(allowance || "0");
5276
5354
  } catch {
5277
5355
  }
5278
- if (currentAllowance >= amountWei) continue;
5356
+ if (approvalSatisfied(currentAllowance, amountWei)) continue;
5279
5357
  const data = encodeFunctionData({
5280
5358
  abi: erc20ApproveAbi,
5281
5359
  functionName: "approve",
@@ -5287,6 +5365,10 @@ async function ensureApprovals(w, approvals, fallbackChainId) {
5287
5365
  value: 0n,
5288
5366
  chainId: Number.isFinite(Number(chainId)) ? Number(chainId) : void 0
5289
5367
  });
5368
+ if (intentId) {
5369
+ void submitStepReceipt(intentId, stepIndex, hash).catch(() => {
5370
+ });
5371
+ }
5290
5372
  await waitForTxConfirmation(chainId, hash);
5291
5373
  }
5292
5374
  }
@@ -5296,7 +5378,7 @@ function isUserRejected(e2) {
5296
5378
  const msg = String(e2?.message || e2)?.toLowerCase?.() || "";
5297
5379
  return msg.includes("user rejected") || msg.includes("user denied");
5298
5380
  }
5299
- async function sendRouteTransaction(b, fallbackChainId) {
5381
+ async function sendRouteTransaction(b, fallbackChainId, options) {
5300
5382
  const w = walletManager.wallet;
5301
5383
  if (!w) throw new Error("Trustware.wallet not configured");
5302
5384
  const txReq = b.txReq;
@@ -5323,11 +5405,13 @@ async function sendRouteTransaction(b, fallbackChainId) {
5323
5405
  }
5324
5406
  }
5325
5407
  }
5326
- if (!validatedSponsorship) {
5408
+ if (!validatedSponsorship && !options?.approvalsEnsured) {
5327
5409
  await ensureApprovals(
5328
5410
  w,
5329
5411
  b.route?.execution?.approvals,
5330
- Number.isFinite(target) ? target : void 0
5412
+ Number.isFinite(target) ? target : void 0,
5413
+ b.intentId,
5414
+ BigInt(b.route?.estimate?.fromAmount ?? "0")
5331
5415
  );
5332
5416
  }
5333
5417
  if (w.type === "eip1193") {
@@ -5426,7 +5510,7 @@ async function runTopUp(params) {
5426
5510
  }
5427
5511
  }
5428
5512
  }
5429
- var erc20ApproveAbi;
5513
+ var erc20ApproveAbi, UNLIMITED_APPROVAL_FLOOR;
5430
5514
  var init_tx = __esm({
5431
5515
  "src/core/tx.ts"() {
5432
5516
  "use strict";
@@ -5436,6 +5520,7 @@ var init_tx = __esm({
5436
5520
  erc20ApproveAbi = parseAbi([
5437
5521
  "function approve(address spender, uint256 amount) returns (bool)"
5438
5522
  ]);
5523
+ UNLIMITED_APPROVAL_FLOOR = 1n << 255n;
5439
5524
  }
5440
5525
  });
5441
5526
 
@@ -7575,6 +7660,7 @@ var init_core = __esm({
7575
7660
  init_store();
7576
7661
  init_wallets();
7577
7662
  init_routes();
7663
+ init_routes();
7578
7664
  init_balances();
7579
7665
  init_tx();
7580
7666
  init_http();
@@ -7709,6 +7795,7 @@ var init_core = __esm({
7709
7795
  buildRoute,
7710
7796
  buildDepositAddress,
7711
7797
  submitReceipt,
7798
+ submitStepReceipt,
7712
7799
  getStatus,
7713
7800
  pollStatus,
7714
7801
  getBalances,
@@ -8182,12 +8269,9 @@ function useTrustwareConfig() {
8182
8269
  title: "Trustware SDK",
8183
8270
  description: "Seamlessly bridge assets across chains with Trustware."
8184
8271
  },
8185
- retry: {
8186
- autoRetry: true,
8187
- maxRetries: 3,
8188
- baseDelayMs: 1e3,
8189
- approachingThreshold: 5
8190
- },
8272
+ // Shared with the resolver rather than restated, so this placeholder
8273
+ // can't drift from the real defaults.
8274
+ retry: { ...DEFAULT_RETRY_CONFIG },
8191
8275
  features: {
8192
8276
  tokensPagination: true,
8193
8277
  balanceStreaming: false,
@@ -8212,6 +8296,7 @@ var init_useTrustwareConfig = __esm({
8212
8296
  "src/hooks/useTrustwareConfig.ts"() {
8213
8297
  "use strict";
8214
8298
  "use client";
8299
+ init_config();
8215
8300
  init_store();
8216
8301
  }
8217
8302
  });
@@ -8939,75 +9024,17 @@ var init_useTransactionSubmit = __esm({
8939
9024
 
8940
9025
  // src/hooks/useGTM.ts
8941
9026
  import { useEffect as useEffect11, useRef as useRef3, useCallback as useCallback8 } from "react";
8942
- function useGTM(gtmId) {
8943
- const isInitialized = useRef3(false);
8944
- const scriptRef = useRef3(null);
8945
- const isGA4Allowed = useCallback8(() => {
8946
- try {
8947
- return Trustware.getConfig().features.shouldAllowGA4;
8948
- } catch {
8949
- return false;
8950
- }
8951
- }, []);
8952
- useEffect11(() => {
8953
- if (!isGA4Allowed()) {
8954
- console.warn(
8955
- "useGTM: GA4 tracking is disabled or Trustware config not initialized."
8956
- );
8957
- return;
8958
- }
8959
- if (!gtmId || typeof gtmId !== "string") {
8960
- console.error(
8961
- "useGTM: A valid GTM Container ID is required (format: GTM-XXXXX)."
8962
- );
8963
- return;
8964
- }
8965
- if (isInitialized.current) return;
8966
- const alreadyLoaded = document.querySelector(
8967
- `script[src*="googletagmanager.com/gtm.js?id=${gtmId}"]`
8968
- );
8969
- if (alreadyLoaded) {
8970
- window.dataLayer = window.dataLayer || [];
8971
- isInitialized.current = true;
8972
- return;
8973
- }
8974
- window.dataLayer = window.dataLayer || [];
8975
- window.dataLayer.push({
8976
- "gtm.start": (/* @__PURE__ */ new Date()).getTime(),
8977
- event: "gtm.js"
8978
- });
8979
- const firstScript = document.getElementsByTagName("script")[0];
8980
- const script = document.createElement("script");
8981
- script.async = true;
8982
- script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
8983
- firstScript.parentNode?.insertBefore(script, firstScript);
8984
- scriptRef.current = script;
8985
- isInitialized.current = true;
8986
- return () => {
8987
- if (scriptRef.current) {
8988
- scriptRef.current.parentNode?.removeChild(scriptRef.current);
8989
- scriptRef.current = null;
8990
- }
8991
- isInitialized.current = false;
8992
- };
8993
- }, [gtmId, isGA4Allowed]);
8994
- const addNoscriptIframe = useCallback8(() => {
8995
- if (!isGA4Allowed()) return;
8996
- if (document.querySelector('iframe[src*="googletagmanager.com/ns.html"]'))
8997
- return;
8998
- const noscript = document.createElement("noscript");
8999
- const iframe = document.createElement("iframe");
9000
- iframe.src = `https://www.googletagmanager.com/ns.html?id=${gtmId}`;
9001
- iframe.height = "0";
9002
- iframe.width = "0";
9003
- iframe.style.display = "none";
9004
- iframe.style.visibility = "hidden";
9005
- noscript.appendChild(iframe);
9006
- document.body.insertBefore(noscript, document.body.firstChild);
9007
- }, [gtmId, isGA4Allowed]);
9027
+ function isGA4Allowed() {
9028
+ try {
9029
+ return Trustware.getConfig().features.shouldAllowGA4;
9030
+ } catch {
9031
+ return false;
9032
+ }
9033
+ }
9034
+ function useGTMTracker() {
9008
9035
  const trackEvent = useCallback8(
9009
9036
  (eventName, eventParams = {}) => {
9010
- if (!isInitialized.current) {
9037
+ if (!isContainerInitialized) {
9011
9038
  console.warn(
9012
9039
  "useGTM: Not initialized. Ensure a valid GTM Container ID was provided."
9013
9040
  );
@@ -9034,7 +9061,7 @@ function useGTM(gtmId) {
9034
9061
  );
9035
9062
  const setUserProperty = useCallback8(
9036
9063
  (propertyName, value) => {
9037
- if (!isInitialized.current) {
9064
+ if (!isContainerInitialized) {
9038
9065
  console.warn(
9039
9066
  "useGTM: Not initialized. Ensure a valid GTM Container ID was provided."
9040
9067
  );
@@ -9060,20 +9087,77 @@ function useGTM(gtmId) {
9060
9087
  }
9061
9088
  window.gtag(command, ...args);
9062
9089
  },
9063
- [isGA4Allowed]
9090
+ []
9064
9091
  );
9065
- return {
9066
- trackEvent,
9067
- trackPageView,
9068
- setUserProperty,
9069
- directGtag,
9070
- addNoscriptIframe
9071
- };
9092
+ return { trackEvent, trackPageView, setUserProperty, directGtag };
9093
+ }
9094
+ function useGTM(gtmId) {
9095
+ const scriptRef = useRef3(null);
9096
+ const tracker = useGTMTracker();
9097
+ useEffect11(() => {
9098
+ if (!isGA4Allowed()) {
9099
+ console.warn(
9100
+ "useGTM: GA4 tracking is disabled or Trustware config not initialized."
9101
+ );
9102
+ return;
9103
+ }
9104
+ if (!gtmId || typeof gtmId !== "string") {
9105
+ console.error(
9106
+ "useGTM: A valid GTM Container ID is required (format: GTM-XXXXX)."
9107
+ );
9108
+ return;
9109
+ }
9110
+ if (isContainerInitialized) return;
9111
+ const alreadyLoaded = document.querySelector(
9112
+ `script[src*="googletagmanager.com/gtm.js?id=${gtmId}"]`
9113
+ );
9114
+ if (alreadyLoaded) {
9115
+ window.dataLayer = window.dataLayer || [];
9116
+ isContainerInitialized = true;
9117
+ return;
9118
+ }
9119
+ window.dataLayer = window.dataLayer || [];
9120
+ window.dataLayer.push({
9121
+ "gtm.start": (/* @__PURE__ */ new Date()).getTime(),
9122
+ event: "gtm.js"
9123
+ });
9124
+ const firstScript = document.getElementsByTagName("script")[0];
9125
+ const script = document.createElement("script");
9126
+ script.async = true;
9127
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;
9128
+ firstScript.parentNode?.insertBefore(script, firstScript);
9129
+ scriptRef.current = script;
9130
+ isContainerInitialized = true;
9131
+ return () => {
9132
+ if (scriptRef.current) {
9133
+ scriptRef.current.parentNode?.removeChild(scriptRef.current);
9134
+ scriptRef.current = null;
9135
+ }
9136
+ isContainerInitialized = false;
9137
+ };
9138
+ }, [gtmId]);
9139
+ const addNoscriptIframe = useCallback8(() => {
9140
+ if (!isGA4Allowed()) return;
9141
+ if (document.querySelector('iframe[src*="googletagmanager.com/ns.html"]'))
9142
+ return;
9143
+ const noscript = document.createElement("noscript");
9144
+ const iframe = document.createElement("iframe");
9145
+ iframe.src = `https://www.googletagmanager.com/ns.html?id=${gtmId}`;
9146
+ iframe.height = "0";
9147
+ iframe.width = "0";
9148
+ iframe.style.display = "none";
9149
+ iframe.style.visibility = "hidden";
9150
+ noscript.appendChild(iframe);
9151
+ document.body.insertBefore(noscript, document.body.firstChild);
9152
+ }, [gtmId]);
9153
+ return { ...tracker, addNoscriptIframe };
9072
9154
  }
9155
+ var isContainerInitialized;
9073
9156
  var init_useGTM = __esm({
9074
9157
  "src/hooks/useGTM.ts"() {
9075
9158
  "use strict";
9076
9159
  init_core2();
9160
+ isContainerInitialized = false;
9077
9161
  }
9078
9162
  });
9079
9163
 
@@ -9100,7 +9184,7 @@ function useTransactionPolling() {
9100
9184
  return void 0;
9101
9185
  }
9102
9186
  })();
9103
- const { trackEvent } = useGTM(GTM_ID);
9187
+ const { trackEvent } = useGTMTracker();
9104
9188
  const [state, setState] = useState13({
9105
9189
  isPolling: false,
9106
9190
  apiStatus: null,
@@ -9200,8 +9284,21 @@ function useTransactionPolling() {
9200
9284
  pollCountRef.current += 1;
9201
9285
  const pollInterval = pollCountRef.current <= 10 ? FAST_POLL_INTERVAL_MS : NORMAL_POLL_INTERVAL_MS;
9202
9286
  pollingRef.current = setTimeout(poll, pollInterval);
9203
- } catch {
9287
+ } catch (err) {
9204
9288
  if (abortRef.current) return;
9289
+ if (isNotFoundError(err)) {
9290
+ clearPolling();
9291
+ const notFoundError = "Transaction session expired. Please try again.";
9292
+ setState((prev) => ({
9293
+ ...prev,
9294
+ isPolling: false,
9295
+ error: notFoundError
9296
+ }));
9297
+ setErrorMessage(notFoundError);
9298
+ setTransactionStatus("error");
9299
+ setCurrentStep("error");
9300
+ return;
9301
+ }
9205
9302
  const elapsed = Date.now() - startTimeRef.current;
9206
9303
  if (elapsed > POLL_TIMEOUT_MS) {
9207
9304
  clearPolling();
@@ -9337,10 +9434,10 @@ var init_useTransactionPolling = __esm({
9337
9434
  "use strict";
9338
9435
  "use client";
9339
9436
  init_routes();
9437
+ init_http();
9340
9438
  init_DepositContext();
9341
9439
  init_provider();
9342
9440
  init_core2();
9343
- init_constants();
9344
9441
  init_hooks();
9345
9442
  FAST_POLL_INTERVAL_MS = 1500;
9346
9443
  NORMAL_POLL_INTERVAL_MS = 2500;
@@ -30710,7 +30807,7 @@ import {
30710
30807
  keccak256 as keccak2566,
30711
30808
  stringToHex
30712
30809
  } from "viem";
30713
- async function fetchWithTimeout(url, init) {
30810
+ async function fetchOnceWithTimeout(url, init) {
30714
30811
  const controller = new AbortController();
30715
30812
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
30716
30813
  try {
@@ -30731,6 +30828,9 @@ async function fetchWithTimeout(url, init) {
30731
30828
  clearTimeout(timer);
30732
30829
  }
30733
30830
  }
30831
+ async function fetchWithTimeout(url, init) {
30832
+ return withRateLimitRetry(() => fetchOnceWithTimeout(url, init));
30833
+ }
30734
30834
  function extractRevertHex(err) {
30735
30835
  if (typeof err.data === "string" && err.data.startsWith("0x"))
30736
30836
  return err.data;
@@ -31230,7 +31330,7 @@ async function estimateRelayFeeInToken(valueWei, fromAmountWei, fromAmountUSD, c
31230
31330
  tokenUnitsPerDollar = Number(fromAmountWei) / fromUSD;
31231
31331
  } else if (fromToken && fromDecimals !== void 0) {
31232
31332
  try {
31233
- const resp = await fetch(
31333
+ const resp = await rateLimitedFetch(
31234
31334
  `${apiBase()}/v1/price/token?chainId=${cid}&address=${fromToken}`,
31235
31335
  { headers: jsonHeaders() }
31236
31336
  );
@@ -31246,9 +31346,10 @@ async function estimateRelayFeeInToken(valueWei, fromAmountWei, fromAmountUSD, c
31246
31346
  return 0n;
31247
31347
  }
31248
31348
  try {
31249
- const resp = await fetch(`${apiBase()}/v1/price/native?chainId=${cid}`, {
31250
- headers: jsonHeaders()
31251
- });
31349
+ const resp = await rateLimitedFetch(
31350
+ `${apiBase()}/v1/price/native?chainId=${cid}`,
31351
+ { headers: jsonHeaders() }
31352
+ );
31252
31353
  if (!resp.ok) throw new Error(`native price HTTP ${resp.status}`);
31253
31354
  const json = await resp.json();
31254
31355
  const ethPriceUSD = parseFloat(json.data?.priceUSD ?? "0");
@@ -31756,7 +31857,7 @@ function useTransactionActionModel({
31756
31857
  }) {
31757
31858
  const feeDataCacheRef = useRef13({});
31758
31859
  const { isSubmitting, submitTransaction } = useTransactionSubmit();
31759
- const { trackEvent } = useGTM(GTM_ID);
31860
+ const { trackEvent } = useGTMTracker();
31760
31861
  const destinationConfig = (() => {
31761
31862
  try {
31762
31863
  return Trustware.getConfig();
@@ -32189,7 +32290,6 @@ var init_useTransactionActionModel = __esm({
32189
32290
  init_chainHelpers();
32190
32291
  init_utils();
32191
32292
  init_hooks();
32192
- init_constants();
32193
32293
  }
32194
32294
  });
32195
32295
 
@@ -33541,6 +33641,16 @@ async function waitForApprovalConfirmation(chainId, txHash) {
33541
33641
  }
33542
33642
  throw new Error("Timed out waiting for approval confirmation");
33543
33643
  }
33644
+ async function requireActiveAddress(wallet, plannedFor) {
33645
+ if (!wallet) throw new Error("Wallet not connected. Please reconnect.");
33646
+ const active = await wallet.getAddress();
33647
+ if (active?.toLowerCase() !== plannedFor?.toLowerCase()) {
33648
+ throw new Error(
33649
+ "Wallet account changed. Please review and confirm the swap again."
33650
+ );
33651
+ }
33652
+ return active;
33653
+ }
33544
33654
  function useSwapExecution(fromChain) {
33545
33655
  const [state, setState] = useState29({
33546
33656
  txStatus: "idle",
@@ -33597,7 +33707,15 @@ function useSwapExecution(fromChain) {
33597
33707
  if (tx.status === "bridging") {
33598
33708
  setState((p) => ({ ...p, txStatus: "bridging" }));
33599
33709
  }
33600
- } catch {
33710
+ } catch (err) {
33711
+ if (abortRef.current) return;
33712
+ if (isNotFoundError(err)) {
33713
+ clearPolling();
33714
+ const msg = "Transaction session expired. Please try again.";
33715
+ setState((p) => ({ ...p, txStatus: "error", errorMessage: msg }));
33716
+ onError(msg);
33717
+ return;
33718
+ }
33601
33719
  }
33602
33720
  if (abortRef.current) return;
33603
33721
  pollCountRef.current += 1;
@@ -33623,24 +33741,55 @@ function useSwapExecution(fromChain) {
33623
33741
  }
33624
33742
  const txReq = routeResult.txReq;
33625
33743
  const chainIdStr = String(txReq?.chainId ?? fromChain?.chainId ?? "");
33626
- const spender = txReq?.to ?? txReq?.target;
33627
33744
  const amountWei = BigInt(routeResult.route?.estimate?.fromAmount ?? "0");
33628
- if (!spender || !chainIdStr || amountWei === 0n) {
33745
+ const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
33746
+ const toCheck = plannedApprovals.length ? plannedApprovals.flatMap((a) => {
33747
+ const token = a.tokenAddress ?? fromTokenAddress;
33748
+ const required = requiredApprovalAmount(
33749
+ BigInt(a.amount || "0"),
33750
+ amountWei
33751
+ );
33752
+ if (!token || !a.spender) return [];
33753
+ return [
33754
+ {
33755
+ chainId: a.chainId || chainIdStr,
33756
+ token,
33757
+ spender: a.spender,
33758
+ required
33759
+ }
33760
+ ];
33761
+ }) : (() => {
33762
+ const spender = txReq?.to ?? txReq?.target;
33763
+ if (!spender) return [];
33764
+ return [
33765
+ {
33766
+ chainId: chainIdStr,
33767
+ token: fromTokenAddress,
33768
+ spender,
33769
+ required: amountWei
33770
+ }
33771
+ ];
33772
+ })();
33773
+ if (toCheck.length === 0 || !chainIdStr || amountWei === 0n) {
33629
33774
  setState((p) => ({ ...p, allowanceStatus: "unknown" }));
33630
33775
  return;
33631
33776
  }
33632
33777
  setState((p) => ({ ...p, allowanceStatus: "checking" }));
33633
33778
  try {
33634
- const { allowance } = await getEVMAllowance({
33635
- chainId: chainIdStr,
33636
- tokenAddress: fromTokenAddress,
33637
- ownerAddress: walletAddress,
33638
- spenderAddress: spender
33639
- });
33640
- const allowanceWei = BigInt(allowance || "0");
33779
+ const results = await Promise.all(
33780
+ toCheck.map(async (c) => {
33781
+ const { allowance } = await getEVMAllowance({
33782
+ chainId: c.chainId,
33783
+ tokenAddress: c.token,
33784
+ ownerAddress: walletAddress,
33785
+ spenderAddress: c.spender
33786
+ });
33787
+ return approvalSatisfied(BigInt(allowance || "0"), c.required);
33788
+ })
33789
+ );
33641
33790
  setState((p) => ({
33642
33791
  ...p,
33643
- allowanceStatus: allowanceWei >= amountWei ? "sufficient" : "needed"
33792
+ allowanceStatus: results.every(Boolean) ? "sufficient" : "needed"
33644
33793
  }));
33645
33794
  } catch {
33646
33795
  setState((p) => ({ ...p, allowanceStatus: "needed" }));
@@ -33744,19 +33893,49 @@ function useSwapExecution(fromChain) {
33744
33893
  }
33745
33894
  }
33746
33895
  try {
33747
- if (!isNative && walletAddress && spender && chainIdStr && fromTokenAddress) {
33896
+ const plannedApprovals = routeResult.route?.execution?.approvals ?? [];
33897
+ const requiredApprovals = plannedApprovals.length ? plannedApprovals.flatMap((a, index) => {
33898
+ const token = a.tokenAddress ?? fromTokenAddress;
33899
+ const approvalSpender = a.spender;
33900
+ if (!token || !approvalSpender) return [];
33901
+ const amountWei = requiredApprovalAmount(
33902
+ BigInt(a.amount || "0"),
33903
+ fromAmountWei
33904
+ );
33905
+ return [
33906
+ {
33907
+ stepIndex: index,
33908
+ token,
33909
+ spender: approvalSpender,
33910
+ amountWei,
33911
+ chainId: a.chainId || chainIdStr
33912
+ }
33913
+ ];
33914
+ }) : spender && fromTokenAddress ? [
33915
+ {
33916
+ stepIndex: null,
33917
+ token: fromTokenAddress,
33918
+ spender,
33919
+ amountWei: fromAmountWei,
33920
+ chainId: chainIdStr
33921
+ }
33922
+ ] : [];
33923
+ const ownsApprovals = !isNative && !!walletAddress && !!chainIdStr;
33924
+ const activeAddress = ownsApprovals ? await requireActiveAddress(wallet, walletAddress) : void 0;
33925
+ for (const required of requiredApprovals) {
33926
+ if (!ownsApprovals) break;
33748
33927
  let allowanceWei = 0n;
33749
33928
  try {
33750
33929
  const { allowance } = await getEVMAllowance({
33751
- chainId: chainIdStr,
33752
- tokenAddress: fromTokenAddress,
33753
- ownerAddress: walletAddress,
33754
- spenderAddress: spender
33930
+ chainId: required.chainId,
33931
+ tokenAddress: required.token,
33932
+ ownerAddress: activeAddress,
33933
+ spenderAddress: required.spender
33755
33934
  });
33756
33935
  allowanceWei = BigInt(allowance || "0");
33757
33936
  } catch {
33758
33937
  }
33759
- if (allowanceWei < fromAmountWei) {
33938
+ if (!approvalSatisfied(allowanceWei, required.amountWei)) {
33760
33939
  setState((p) => ({ ...p, txStatus: "approving" }));
33761
33940
  if (!wallet || wallet.ecosystem !== "evm") {
33762
33941
  throw new Error("EVM wallet required for token approval");
@@ -33764,7 +33943,7 @@ function useSwapExecution(fromChain) {
33764
33943
  if (!chainIdStr || chainIdStr === "0") {
33765
33944
  throw new Error("Invalid chain ID for token approval");
33766
33945
  }
33767
- const targetChain = Number(txReq.chainId ?? fromChain?.chainId);
33946
+ const targetChain = Number(required.chainId ?? txReq.chainId);
33768
33947
  if (Number.isFinite(targetChain)) {
33769
33948
  const current = await wallet.getChainId();
33770
33949
  if (current !== targetChain) {
@@ -33776,18 +33955,18 @@ function useSwapExecution(fromChain) {
33776
33955
  }
33777
33956
  const approveAmount = maxApproval ? BigInt(
33778
33957
  "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
33779
- ) : fromAmountWei;
33958
+ ) : required.amountWei;
33780
33959
  const data = encodeFunctionData10({
33781
33960
  abi: erc20Abi2,
33782
33961
  functionName: "approve",
33783
- args: [spender, approveAmount]
33962
+ args: [required.spender, approveAmount]
33784
33963
  });
33785
33964
  let approvalHash;
33786
33965
  if (wallet.type === "eip1193") {
33787
33966
  const from = await wallet.getAddress();
33788
33967
  const params = {
33789
33968
  from,
33790
- to: fromTokenAddress,
33969
+ to: required.token,
33791
33970
  data,
33792
33971
  value: "0x0"
33793
33972
  };
@@ -33800,14 +33979,22 @@ function useSwapExecution(fromChain) {
33800
33979
  });
33801
33980
  } else {
33802
33981
  const response = await wallet.sendTransaction({
33803
- to: fromTokenAddress,
33982
+ to: required.token,
33804
33983
  data,
33805
33984
  value: 0n,
33806
33985
  chainId: Number.isFinite(targetChain) ? targetChain : void 0
33807
33986
  });
33808
33987
  approvalHash = response.hash;
33809
33988
  }
33810
- await waitForApprovalConfirmation(chainIdStr, approvalHash);
33989
+ if (required.stepIndex !== null) {
33990
+ void submitStepReceipt(
33991
+ routeResult.intentId,
33992
+ required.stepIndex,
33993
+ approvalHash
33994
+ ).catch(() => {
33995
+ });
33996
+ }
33997
+ await waitForApprovalConfirmation(required.chainId, approvalHash);
33811
33998
  setState((p) => ({
33812
33999
  ...p,
33813
34000
  txStatus: "confirming",
@@ -33815,9 +34002,13 @@ function useSwapExecution(fromChain) {
33815
34002
  }));
33816
34003
  }
33817
34004
  }
34005
+ if (ownsApprovals) {
34006
+ await requireActiveAddress(wallet, walletAddress);
34007
+ }
33818
34008
  const hash = await Trustware.sendRouteTransaction(
33819
34009
  routeResult,
33820
- numericChainId
34010
+ numericChainId,
34011
+ { approvalsEnsured: ownsApprovals }
33821
34012
  );
33822
34013
  try {
33823
34014
  await submitReceipt(routeResult.intentId, hash);
@@ -33885,6 +34076,8 @@ var init_useSwapExecution = __esm({
33885
34076
  "use client";
33886
34077
  init_core2();
33887
34078
  init_routes();
34079
+ init_http();
34080
+ init_tx();
33888
34081
  init_sdkRpc();
33889
34082
  init_chainHelpers();
33890
34083
  FAST_POLL_MS = 1500;
@@ -33896,9 +34089,14 @@ var init_useSwapExecution = __esm({
33896
34089
 
33897
34090
  // src/core/forex.ts
33898
34091
  async function fetchForexRates(base2 = "USD") {
33899
- const r = await fetch(`${apiBase()}/v1/forex?base=${base2}`, {
33900
- headers: jsonHeaders()
33901
- });
34092
+ let r;
34093
+ try {
34094
+ r = await rateLimitedFetch(`${apiBase()}/v1/forex?base=${base2}`, {
34095
+ headers: jsonHeaders()
34096
+ });
34097
+ } catch {
34098
+ return {};
34099
+ }
33902
34100
  if (!r.ok) return {};
33903
34101
  const j = await r.json();
33904
34102
  const rates = j?.data?.rates ?? j?.rates ?? j?.data ?? {};
@@ -39849,6 +40047,7 @@ function WidgetInner({
39849
40047
  const { resolvedTheme } = useDepositUi();
39850
40048
  const { status, revalidate, errors } = useTrustware();
39851
40049
  const [showConfirmDialog, setShowConfirmDialog] = useState35(false);
40050
+ useGTM(GTM_ID);
39852
40051
  const handleCloseRequest = useCallback23(() => {
39853
40052
  if (ACTIVE_TRANSACTION_STATUSES.includes(transactionStatus)) {
39854
40053
  setShowConfirmDialog(true);
@@ -39922,6 +40121,8 @@ var init_TrustwareWidgetV2 = __esm({
39922
40121
  init_provider();
39923
40122
  init_manager();
39924
40123
  init_useTrustwareConfig();
40124
+ init_hooks();
40125
+ init_constants();
39925
40126
  init_swap();
39926
40127
  widgetContentContainerStyle = {
39927
40128
  position: "relative",
@@ -40059,6 +40260,7 @@ var init_index = __esm({
40059
40260
  "src/index.ts"() {
40060
40261
  init_core2();
40061
40262
  init_http();
40263
+ init_core2();
40062
40264
  init_useWalletTokenState();
40063
40265
  init_wallets();
40064
40266
  init_widget();
@@ -40083,6 +40285,7 @@ export {
40083
40285
  GTM_ID,
40084
40286
  IdentityStore,
40085
40287
  POPULAR_ORDER,
40288
+ RATE_LIMIT_WAIT_BUDGET_MS,
40086
40289
  RateLimitError,
40087
40290
  SDK_NAME,
40088
40291
  SDK_VERSION,
@@ -40092,6 +40295,7 @@ export {
40092
40295
  TrustwareWidgetV2 as TrustwareWidget,
40093
40296
  WALLETCONNECT_PROJECT_ID,
40094
40297
  WALLETS,
40298
+ assertValidPostHook,
40095
40299
  buildWalletIdentityAddress,
40096
40300
  connectDetectedWallet,
40097
40301
  createWalletIdentity,