@0xsquid/react-hooks 8.5.0 → 8.5.1-beta-stellar-tempo.0

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 (45) hide show
  1. package/dist/core/constants.d.ts +3 -0
  2. package/dist/core/queries/queries-keys.d.ts +6 -2
  3. package/dist/core/types/config.d.ts +0 -1
  4. package/dist/core/types/index.d.ts +1 -1
  5. package/dist/core/types/stellar.d.ts +52 -0
  6. package/dist/core/types/tokens.d.ts +4 -0
  7. package/dist/hooks/index.d.ts +3 -2
  8. package/dist/hooks/stellar/useStellarTrustLine.d.ts +13 -0
  9. package/dist/hooks/tokens/useSourceChainGasToken.d.ts +10 -0
  10. package/dist/hooks/transaction/send/useEstimateSendTransactionGas.d.ts +10 -2
  11. package/dist/hooks/transaction/useEstimate.d.ts +7 -12
  12. package/dist/hooks/transaction/useTempoFeeCheck.d.ts +11 -0
  13. package/dist/{index-5cyMUZhY.js → index-BSX11dad.js} +1552 -1109
  14. package/dist/index-BSX11dad.js.map +1 -0
  15. package/dist/{index-BGVXRZI6.js → index-XR8ODWxH.js} +1545 -1113
  16. package/dist/index-XR8ODWxH.js.map +1 -0
  17. package/dist/{index.es-BfdAGErV.js → index.es-CczeKjuj.js} +3 -3
  18. package/dist/{index.es-BfdAGErV.js.map → index.es-CczeKjuj.js.map} +1 -1
  19. package/dist/{index.es-CeHwkxPw.js → index.es-CkrP1GZJ.js} +3 -3
  20. package/dist/{index.es-CeHwkxPw.js.map → index.es-CkrP1GZJ.js.map} +1 -1
  21. package/dist/index.esm.js +2 -2
  22. package/dist/index.js +14 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/{secretService-D_d3CFdp.js → secretService-ChCrHmS3.js} +3 -3
  25. package/dist/{secretService-D_d3CFdp.js.map → secretService-ChCrHmS3.js.map} +1 -1
  26. package/dist/{secretService-BMYOBXhv.js → secretService-ScgDU3bX.js} +3 -3
  27. package/dist/{secretService-BMYOBXhv.js.map → secretService-ScgDU3bX.js.map} +1 -1
  28. package/dist/services/external/rpcService.d.ts +1 -1
  29. package/dist/services/external/stellarApiClient.d.ts +4 -0
  30. package/dist/services/external/stellarRpcClient.d.ts +2 -2
  31. package/dist/services/external/xrplRpcClient.d.ts +2 -1
  32. package/dist/services/index.d.ts +1 -0
  33. package/dist/services/internal/assetsService.d.ts +2 -2
  34. package/dist/services/internal/estimateService.d.ts +15 -28
  35. package/dist/services/internal/stellarService.d.ts +9 -0
  36. package/dist/services/internal/tempoService.d.ts +38 -0
  37. package/dist/services/internal/xrplService.d.ts +2 -0
  38. package/dist/{stellarService.client-DOrCdvCd.js → stellarService.client-BaDOSK8x.js} +3 -3
  39. package/dist/{stellarService.client-DOrCdvCd.js.map → stellarService.client-BaDOSK8x.js.map} +1 -1
  40. package/dist/{stellarService.client-CIkvwxPo.js → stellarService.client-D65n-wCV.js} +3 -3
  41. package/dist/{stellarService.client-CIkvwxPo.js.map → stellarService.client-D65n-wCV.js.map} +1 -1
  42. package/package.json +1 -1
  43. package/dist/hooks/user/useUserParams.d.ts +0 -4
  44. package/dist/index-5cyMUZhY.js.map +0 -1
  45. package/dist/index-BGVXRZI6.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { ChainType, ActionType, SquidDataType, BridgeType, BridgeProvider, FeeType } from '@0xsquid/squid-types';
2
- import { zeroAddress as zeroAddress$1, parseUnits, formatUnits, isAddress, SwitchChainError, UserRejectedRequestError, getAddress, defineChain, encodeFunctionData, erc20Abi } from 'viem';
2
+ import { zeroAddress as zeroAddress$1, parseUnits, formatUnits, isAddress, SwitchChainError, UserRejectedRequestError, erc20Abi, getAddress, defineChain, encodeFunctionData } from 'viem';
3
3
  import React, { useMemo, useCallback, useState, useEffect, createContext, useContext, useRef } from 'react';
4
4
  import { useQuery, useMutation, useQueries, useQueryClient, QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
5
  import { fromBech32 } from '@cosmjs/encoding';
@@ -13,22 +13,22 @@ import { isValidXAddress, isValidClassicAddress } from 'ripple-address-codec';
13
13
  import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, createTransferInstruction } from '@solana/spl-token';
14
14
  import { StandardWalletAdapter } from '@solana/wallet-standard-wallet-adapter-base';
15
15
  import { PublicKey, VersionedTransaction, Transaction, SystemProgram, Connection } from '@solana/web3.js';
16
- import { Networks, StrKey, nativeToScVal, Address, rpc, TransactionBuilder, BASE_FEE, Contract, TimeoutInfinite, scValToNative, Operation, Transaction as Transaction$1, xdr } from '@stellar/stellar-sdk';
16
+ import { StrKey, Networks, nativeToScVal, Address, rpc, TransactionBuilder, BASE_FEE, Contract, TimeoutInfinite, scValToNative, Asset, Operation, Transaction as Transaction$1, xdr } from '@stellar/stellar-sdk';
17
17
  import { SUI_TESTNET_CHAIN, SUI_MAINNET_CHAIN } from '@mysten/wallet-standard';
18
- import { CloudflareProvider, BrowserProvider, JsonRpcSigner, JsonRpcProvider, ethers, Interface, Contract as Contract$1, isError } from 'ethers';
18
+ import { CloudflareProvider, JsonRpcProvider, ethers, Interface, BrowserProvider, JsonRpcSigner, Contract as Contract$1, isError } from 'ethers';
19
19
  import BigNumber$1, { BigNumber } from 'bignumber.js';
20
20
  import { countries } from 'countries-list';
21
21
  import getSymbolFromCurrency from 'currency-symbol-map';
22
22
  import Fuse from 'fuse.js';
23
23
  import { create } from 'zustand';
24
24
  import { persist } from 'zustand/middleware';
25
- import { useAccount, useConnectors, useConnect, useDisconnect, useSwitchChain, createConfig, http, useWalletClient, usePublicClient, useBalance, useReadContract, WagmiProvider } from 'wagmi';
25
+ import { useAccount, useConnectors, useConnect, useDisconnect, useSwitchChain, useBalance, useReadContract, createConfig, http, useWalletClient, usePublicClient, WagmiProvider } from 'wagmi';
26
26
  import SafeAppsSDK, { TransactionStatus as TransactionStatus$1 } from '@safe-global/safe-apps-sdk';
27
27
  import { getWallets } from '@wallet-standard/core';
28
28
  import { SlushWallet } from '@mysten/slush-wallet';
29
- import { injected, safe, metaMask, coinbaseWallet, walletConnect } from 'wagmi/connectors';
30
29
  import { StargateClient, SigningStargateClient } from '@cosmjs/stargate';
31
30
  import { SuiClient } from '@mysten/sui/client';
31
+ import { injected, safe, metaMask, coinbaseWallet, walletConnect } from 'wagmi/connectors';
32
32
  import { coin as coin$1 } from '@cosmjs/proto-signing';
33
33
  import { Transaction as Transaction$2 } from '@mysten/sui/transactions';
34
34
  import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
@@ -124,6 +124,9 @@ const CHAIN_IDS = {
124
124
  SONEIUM: "1868",
125
125
  PEAQ: "3338",
126
126
  HEDERA: "295",
127
+ MANTRA: "5888",
128
+ CITREA: "4114",
129
+ TEMPO: "4217",
127
130
  // others
128
131
  BITCOIN: "bitcoin",
129
132
  SOLANA: "solana-mainnet-beta",
@@ -20097,14 +20100,17 @@ function isXrplAddressValid(address) {
20097
20100
  return isValidXAddress(address) || isValidClassicAddress(address);
20098
20101
  }
20099
20102
  function buildXrplTrustSetTx({ amount, sourceAddress, token, }) {
20100
- const [currency, issuer] = token.address.split(".");
20103
+ const asset = parseXrplTokenAddress(token.address);
20104
+ if (!asset) {
20105
+ throw new Error("Invalid asset");
20106
+ }
20101
20107
  return {
20102
20108
  TransactionType: XrplTransactionType.TRUST_SET,
20103
20109
  Flags: XrplTransactionFlag.tfSetNoRipple,
20104
20110
  Account: sourceAddress,
20105
20111
  LimitAmount: {
20106
- currency,
20107
- issuer,
20112
+ currency: asset.code,
20113
+ issuer: asset.issuer,
20108
20114
  value: amount,
20109
20115
  },
20110
20116
  };
@@ -20133,6 +20139,16 @@ function parseXrplPaymentTx(data) {
20133
20139
  throw new Error("Could not parse payment transaction");
20134
20140
  }
20135
20141
  }
20142
+ function parseXrplTokenAddress(address) {
20143
+ const [code, issuer] = address.split(".");
20144
+ if (!code || !issuer) {
20145
+ return null;
20146
+ }
20147
+ return {
20148
+ code,
20149
+ issuer,
20150
+ };
20151
+ }
20136
20152
 
20137
20153
  const chains = [XrplCAIP2ChainId.MAINNET, XrplCAIP2ChainId.TESTNET];
20138
20154
  class XrplWalletConnect {
@@ -21115,8 +21131,10 @@ function isChainflipBridgeTransaction(actions = []) {
21115
21131
  function isOnChainTxData(squidData) {
21116
21132
  return [
21117
21133
  SquidDataType.OnChainExecution,
21118
- SquidDataType.DepositAddressWithSignature,
21119
21134
  SquidDataType.OnChainExecutionWithSignature,
21135
+ SquidDataType.DepositAddressWithSignature,
21136
+ SquidDataType.DepositAddressCalldata,
21137
+ SquidDataType.DepositAddressWithMemo,
21120
21138
  ].includes(squidData.type);
21121
21139
  }
21122
21140
  function getHistoryTransactionId(tx) {
@@ -21393,6 +21411,28 @@ const executeSolanaTransfer = async ({ amount, target, signer, connection, sourc
21393
21411
  return signature;
21394
21412
  };
21395
21413
 
21414
+ var StellarHorizonAssetType;
21415
+ (function (StellarHorizonAssetType) {
21416
+ /**
21417
+ * XLM native token
21418
+ */
21419
+ StellarHorizonAssetType["NATIVE"] = "native";
21420
+ /**
21421
+ * 1-4 char asset code (e.g. USDC)
21422
+ */
21423
+ StellarHorizonAssetType["ALPHANUM4"] = "credit_alphanum4";
21424
+ /**
21425
+ * 5-12 char asset code (e.g. wstETH)
21426
+ */
21427
+ StellarHorizonAssetType["ALPHANUM12"] = "credit_alphanum12";
21428
+ })(StellarHorizonAssetType || (StellarHorizonAssetType = {}));
21429
+ var StellarTokenType;
21430
+ (function (StellarTokenType) {
21431
+ StellarTokenType["NATIVE_TOKEN"] = "nativeToken";
21432
+ StellarTokenType["ISSUER_TOKEN"] = "issuerToken";
21433
+ StellarTokenType["CONTRACT_TOKEN"] = "contractToken";
21434
+ })(StellarTokenType || (StellarTokenType = {}));
21435
+
21396
21436
  function isStellarAddressValid(address) {
21397
21437
  return StrKey.isValidEd25519PublicKey(address);
21398
21438
  }
@@ -21414,6 +21454,78 @@ function stellarAddressToScVal(addressString) {
21414
21454
  type: "address",
21415
21455
  });
21416
21456
  }
21457
+ function getStellarTrustLineAsset(token) {
21458
+ // The address format for issued assets is `CODE-ISSUER`
21459
+ // Example: USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
21460
+ const [code, issuer] = token.address.split("-");
21461
+ if (!code || !issuer) {
21462
+ return null;
21463
+ }
21464
+ return {
21465
+ code,
21466
+ issuer,
21467
+ };
21468
+ }
21469
+ function isStellarToken(token) {
21470
+ try {
21471
+ const stellarToken = token;
21472
+ return (Object.values(StellarTokenType).includes(stellarToken.chainAssetConfig.stellar.assetType) &&
21473
+ typeof stellarToken.chainAssetConfig.stellar.contractAddress === "string");
21474
+ }
21475
+ catch {
21476
+ return false;
21477
+ }
21478
+ }
21479
+ function isStellarIssuedToken(token) {
21480
+ try {
21481
+ return (isStellarToken(token) &&
21482
+ token.chainAssetConfig.stellar.assetType === StellarTokenType.ISSUER_TOKEN);
21483
+ }
21484
+ catch {
21485
+ return false;
21486
+ }
21487
+ }
21488
+ function getStellarHorizonApiUrl(chain) {
21489
+ try {
21490
+ const stellarChain = chain;
21491
+ const [horizonApiUrl] = stellarChain.horizonRpcList;
21492
+ if (typeof horizonApiUrl !== "string") {
21493
+ throw new Error("Invalid Horizon API URL");
21494
+ }
21495
+ return horizonApiUrl;
21496
+ }
21497
+ catch {
21498
+ return null;
21499
+ }
21500
+ }
21501
+ const VALID_ASSET_TYPES = new Set(Object.values(StellarHorizonAssetType));
21502
+ function isValidNativeAsset(asset) {
21503
+ return (typeof asset === "object" &&
21504
+ asset !== null &&
21505
+ "balance" in asset &&
21506
+ typeof asset.balance === "string" &&
21507
+ "asset_type" in asset &&
21508
+ asset.asset_type === StellarHorizonAssetType.NATIVE);
21509
+ }
21510
+ function isValidIssuedAsset(asset) {
21511
+ return (typeof asset === "object" &&
21512
+ asset !== null &&
21513
+ "balance" in asset &&
21514
+ typeof asset.balance === "string" &&
21515
+ "asset_type" in asset &&
21516
+ typeof asset.asset_type === "string" &&
21517
+ VALID_ASSET_TYPES.has(asset.asset_type) &&
21518
+ asset.asset_type !== StellarHorizonAssetType.NATIVE &&
21519
+ "asset_code" in asset &&
21520
+ typeof asset.asset_code === "string" &&
21521
+ "asset_issuer" in asset &&
21522
+ typeof asset.asset_issuer === "string" &&
21523
+ "limit" in asset &&
21524
+ typeof asset.limit === "string");
21525
+ }
21526
+ function isValidHorizonAsset(asset) {
21527
+ return isValidNativeAsset(asset) || isValidIssuedAsset(asset);
21528
+ }
21417
21529
 
21418
21530
  const SUI_FEATURES = ["sui:signTransaction"];
21419
21531
  function isSuiStandardWallet(wallet) {
@@ -22213,6 +22325,8 @@ var QueryKeys;
22213
22325
  QueryKeys["XrplAccountActivatedInfo"] = "xrplAccountActivatedInfo";
22214
22326
  QueryKeys["FiatToCryptoPaymentMethods"] = "fiatToCryptoPaymentMethods";
22215
22327
  QueryKeys["Stellar"] = "stellar";
22328
+ QueryKeys["StellarTrustLine"] = "stellarTrustLine";
22329
+ QueryKeys["IsStellarTrustLineApproved"] = "isStellarTrustLineApproved";
22216
22330
  QueryKeys["StellarAccountActivatedInfo"] = "stellarAccountActivatedInfo";
22217
22331
  QueryKeys["Hedera"] = "hedera";
22218
22332
  QueryKeys["IsHederaTokenAssociated"] = "isHederaTokenAssociated";
@@ -22277,7 +22391,7 @@ const keys = () => ({
22277
22391
  // ============
22278
22392
  // Transactions
22279
22393
  // ============
22280
- transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, price, slippage, getGasOnDestination, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22394
+ transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, price, slippage, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22281
22395
  ...keys().transactions(),
22282
22396
  QueryKeys.Transaction,
22283
22397
  fromChainId,
@@ -22286,7 +22400,6 @@ const keys = () => ({
22286
22400
  fromTokenAddress,
22287
22401
  price,
22288
22402
  slippage,
22289
- getGasOnDestination,
22290
22403
  sourceUserAddress,
22291
22404
  degenMode,
22292
22405
  destinationAddress,
@@ -22320,7 +22433,7 @@ const keys = () => ({
22320
22433
  // ============
22321
22434
  // Approval
22322
22435
  // ============
22323
- routeApproved: (routeData, allowanceInWei) => [
22436
+ routeApproved: (routeData, allowanceInWei, isAllowanceQueryEnabled, hasAllowance) => [
22324
22437
  ...keys().transactions(),
22325
22438
  QueryKeys.RouteApproved,
22326
22439
  routeData?.params.fromAddress,
@@ -22328,6 +22441,8 @@ const keys = () => ({
22328
22441
  routeData?.params.fromToken,
22329
22442
  routeData?.params.fromAmount,
22330
22443
  allowanceInWei?.toString(),
22444
+ isAllowanceQueryEnabled,
22445
+ hasAllowance,
22331
22446
  ],
22332
22447
  sendTransactionGas: (chainId, tokenAddress, from) => [
22333
22448
  QueryKeys.All,
@@ -22400,6 +22515,23 @@ const keys = () => ({
22400
22515
  // ============
22401
22516
  // Stellar
22402
22517
  // ============
22518
+ stellarTrustLine: (tokenAddress, chainId, address) => [
22519
+ ...keys().stellar(),
22520
+ QueryKeys.StellarTrustLine,
22521
+ tokenAddress,
22522
+ chainId,
22523
+ address,
22524
+ ],
22525
+ isStellarTrustLineApproved: (address, chainId, chainType, tokenAddress, trustLineLimit, amountToApprove) => [
22526
+ ...keys().stellar(),
22527
+ QueryKeys.IsStellarTrustLineApproved,
22528
+ address,
22529
+ chainId,
22530
+ chainType,
22531
+ tokenAddress,
22532
+ trustLineLimit,
22533
+ amountToApprove?.toString(),
22534
+ ],
22403
22535
  stellarAccountActivatedInfo: (address, chainId, chainType) => [
22404
22536
  ...keys().stellar(),
22405
22537
  QueryKeys.StellarAccountActivatedInfo,
@@ -22435,6 +22567,8 @@ const getPrefixKey = (key) => {
22435
22567
  return [...keys().transactions(), key];
22436
22568
  case QueryKeys.XrplTrustLine:
22437
22569
  return [...keys().xrpl(), key];
22570
+ case QueryKeys.StellarTrustLine:
22571
+ return [...keys().stellar(), key];
22438
22572
  case QueryKeys.IsHederaTokenAssociated:
22439
22573
  return [...keys().hedera(), key];
22440
22574
  default:
@@ -22447,7 +22581,6 @@ const getConfigWithDefaults = (config) => {
22447
22581
  integratorId: get$2(config, "integratorId", defaultConfigValues.integratorId),
22448
22582
  slippage: get$2(config, "slippage", defaultConfigValues.slippage),
22449
22583
  collectFees: get$2(config, "collectFees", defaultConfigValues.collectFees),
22450
- enableGetGasOnDestination: get$2(config, "enableGetGasOnDestination", defaultConfigValues.enableGetGasOnDestination),
22451
22584
  apiUrl: get$2(config, "apiUrl", defaultConfigValues.apiUrl),
22452
22585
  priceImpactWarnings: get$2(config, "priceImpactWarnings", defaultConfigValues.priceImpactWarnings),
22453
22586
  initialAssets: get$2(config, "initialAssets", defaultConfigValues.initialAssets),
@@ -22937,8 +23070,8 @@ const sortAllTokens = (tokenA, tokenB) => {
22937
23070
  return 0;
22938
23071
  };
22939
23072
  const findToken = (tokens, chainId, address) => tokens.find((t) => t.chainId === chainId && t.address === address);
22940
- const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain?.nativeCurrency.symbol.toUpperCase() &&
22941
- t.chainId == chain?.chainId);
23073
+ const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain.nativeCurrency.symbol.toUpperCase() &&
23074
+ t.chainId == chain.chainId);
22942
23075
  const normalizeIbcAddress = (address) => {
22943
23076
  if (!address.toLowerCase().startsWith("ibc/")) {
22944
23077
  return address;
@@ -23149,7 +23282,7 @@ const filterViewableTokens = (tokens, config, direction) => {
23149
23282
  };
23150
23283
  const getSecretNetworkBalances = async (chainData, cosmosAddress, squidTokens, keplrTypeWallet) => {
23151
23284
  const squidSecretTokens = squidTokens.filter((t) => t.chainId === CHAIN_IDS.SECRET);
23152
- const { fetchAllSecretBalances } = await import('./secretService-BMYOBXhv.js');
23285
+ const { fetchAllSecretBalances } = await import('./secretService-ScgDU3bX.js');
23153
23286
  return fetchAllSecretBalances(chainData, cosmosAddress, squidSecretTokens, keplrTypeWallet);
23154
23287
  };
23155
23288
  function getTokenAssetsKey(token) {
@@ -24840,7 +24973,7 @@ class OnrampService {
24840
24973
  });
24841
24974
  return data;
24842
24975
  }
24843
- async getConfiguration({ chains, tokens, }) {
24976
+ async getConfiguration({ chains: _, tokens, }) {
24844
24977
  const { data } = await axios.get(`${this.baseUrl}/config`);
24845
24978
  // Filter supportedCryptos to only include tokens that match our provided tokens
24846
24979
  const filteredCryptos = data.supportedCryptos.filter((supportedCrypto) => tokens.some((token) => token.address.toLowerCase() ===
@@ -26465,7 +26598,7 @@ function useStellarWallets() {
26465
26598
  try {
26466
26599
  const { allowAllModules: initializeAllModules } = await import('@creit.tech/stellar-wallets-kit');
26467
26600
  const { LedgerModule } = await import('@creit.tech/stellar-wallets-kit/modules/ledger.module');
26468
- const { formatStellarWallet } = await import('./stellarService.client-DOrCdvCd.js');
26601
+ const { formatStellarWallet } = await import('./stellarService.client-BaDOSK8x.js');
26469
26602
  const modules = [...initializeAllModules(), new LedgerModule()];
26470
26603
  const promises = modules.map(async (module) => {
26471
26604
  const isAvailable = await module.isAvailable();
@@ -27772,580 +27905,310 @@ function useTrackSearchEmpty({ searchQuery, resultsLength, context, debounceMs =
27772
27905
  }, [context, debounceMs, resultsLength, searchQuery]);
27773
27906
  }
27774
27907
 
27775
- var hrc20 = [
27776
- {
27777
- inputs: [
27778
- ],
27779
- name: "associate",
27780
- outputs: [
27781
- {
27782
- internalType: "uint256",
27783
- name: "responseCode",
27784
- type: "uint256"
27785
- }
27786
- ],
27787
- stateMutability: "nonpayable",
27788
- type: "function"
27789
- }
27790
- ];
27791
-
27792
- /**
27793
- * Client for interacting with the Hedera Mirrornode API.
27794
- *
27795
- * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
27796
- */
27797
- class HederaApiClient {
27798
- apiUrl;
27799
- constructor(apiUrl) {
27800
- this.apiUrl = apiUrl;
27801
- }
27802
- async isTokenAssociated({ address, token, }) {
27803
- const accountInfo = await this.getAccountInfo(address);
27804
- // Unlimited auto associations
27805
- if (accountInfo.max_automatic_token_associations === -1) {
27806
- return true;
27807
- }
27808
- // If there's no unlimited auto-associations, we need to check if the token is already associated.
27809
- const { tokens: accountTokens } = await this.getAccountTokens(address);
27810
- const tokenId = convertEvmAddressToHederaAccountId(token.address);
27811
- if (accountTokens.some((t) => t.token_id === tokenId)) {
27812
- // Token is already associated
27813
- return true;
27814
- }
27815
- // Finally, if not auto-associated, check if there is an available auto-association slot
27816
- const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
27817
- const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
27818
- autoAssociatedTokens.length;
27819
- return remainingAutoAssociations > 0;
27908
+ class StellarRpcClient {
27909
+ server;
27910
+ constructor(rpcUrl) {
27911
+ this.server = new rpc.Server(rpcUrl);
27820
27912
  }
27821
- async getAccountInfo(address) {
27822
- const data = await this.fetch(`accounts/${address}`);
27823
- if (typeof data.max_automatic_token_associations !== "number") {
27824
- throw new Error("Invalid max_automatic_token_associations type, expected number");
27913
+ /**
27914
+ * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
27915
+ *
27916
+ * With Contract Tokens, we need to call the .balance method on the token contract
27917
+ * and simulate the transaction to get the balance.
27918
+ */
27919
+ async getBalance(userAddress, tokenAddress, chainId) {
27920
+ const account = await this.server.getAccount(userAddress);
27921
+ const network = getStellarNetwork(chainId);
27922
+ if (network == null) {
27923
+ throw new Error(`No Stellar network found for chainId ${chainId}`);
27825
27924
  }
27826
- if (typeof data.balance.balance !== "number") {
27827
- throw new Error("Invalid balance type, expected number");
27925
+ const txBuilder = new TransactionBuilder(account, {
27926
+ fee: (BigInt(BASE_FEE) * BigInt(2)).toString(),
27927
+ networkPassphrase: network,
27928
+ });
27929
+ const contract = new Contract(tokenAddress);
27930
+ const tx = txBuilder
27931
+ .addOperation(contract.call("balance", new Address(userAddress).toScVal()))
27932
+ .setTimeout(TimeoutInfinite)
27933
+ .build();
27934
+ const simulateTxResponse = await this.server.simulateTransaction(tx);
27935
+ if ("error" in simulateTxResponse) {
27936
+ const isNoBalanceError = /trying to get non-existing value for contract instance|trustline entry is missing for account/.test(simulateTxResponse.error);
27937
+ // If the error message indicates that the user has no balance just return 0
27938
+ // We don't want to spam with this error as it's pretty common
27939
+ if (isNoBalanceError) {
27940
+ return "0";
27941
+ }
27942
+ throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
27828
27943
  }
27829
- if (!Array.isArray(data.balance.tokens)) {
27830
- throw new Error("Invalid tokens type, expected array");
27944
+ if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
27945
+ const native = scValToNative(simulateTxResponse.result.retval);
27946
+ return native.toString();
27831
27947
  }
27832
- return data;
27948
+ throw new Error("Failed to fetch balance");
27833
27949
  }
27834
- async getAccountTokens(address) {
27835
- const data = await this.fetch(`accounts/${address}/tokens`);
27836
- if (!Array.isArray(data.tokens)) {
27837
- throw new Error("Invalid tokens type, expected array");
27950
+ async getAllBalances(userAddress, tokens) {
27951
+ const balancePromises = tokens.map((token) => {
27952
+ return this.getBalance(userAddress, token.chainAssetConfig.stellar.contractAddress, token.chainId);
27953
+ });
27954
+ const results = await Promise.allSettled(balancePromises);
27955
+ const balances = results.map((result) => {
27956
+ if (result.status === "fulfilled") {
27957
+ return result.value;
27958
+ }
27959
+ return "0";
27960
+ });
27961
+ return balances.map((balance, index) => {
27962
+ return {
27963
+ ...tokens[index],
27964
+ balance,
27965
+ };
27966
+ });
27967
+ }
27968
+ /**
27969
+ * Resolves when the transaction is confirmed, or fails after a timeout.
27970
+ */
27971
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
27972
+ const startTime = Date.now();
27973
+ while (true) {
27974
+ const result = await this.server.getTransaction(txHash);
27975
+ if (result.status === rpc.Api.GetTransactionStatus.NOT_FOUND) {
27976
+ if (Date.now() - startTime > timeout) {
27977
+ throw new Error(`Transaction ${txHash} not found within timeout`);
27978
+ }
27979
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
27980
+ await new Promise((res) => setTimeout(res, interval));
27981
+ continue;
27982
+ }
27983
+ if (result.status === rpc.Api.GetTransactionStatus.SUCCESS) {
27984
+ return result.status;
27985
+ }
27986
+ throw new Error(`Transaction failed with status: ${result.status}`);
27838
27987
  }
27839
- const firstToken = data.tokens[0];
27840
- if (typeof firstToken?.automatic_association !== "boolean") {
27841
- throw new Error("Invalid automatic_association type, expected boolean");
27988
+ }
27989
+ async isAccountActivated(address) {
27990
+ try {
27991
+ await this.getAccount(address);
27992
+ return true;
27842
27993
  }
27843
- if (typeof firstToken?.token_id !== "string") {
27844
- throw new Error("Invalid token_id type, expected string");
27994
+ catch (error) {
27995
+ if (error?.message && error.message.includes("Account not found")) {
27996
+ return false;
27997
+ }
27998
+ throw error;
27845
27999
  }
27846
- return data;
27847
28000
  }
27848
- async fetch(path) {
27849
- const url = new URL(path, this.apiUrl);
27850
- const response = await fetch(url);
27851
- if (!response.ok) {
27852
- throw new Error(`Hedera API error: ${response.status}`);
28001
+ async getAccount(address) {
28002
+ return this.server.getAccount(address);
28003
+ }
28004
+ async sendTransaction(transaction) {
28005
+ const transactionResponse = await this.server.sendTransaction(transaction);
28006
+ if (transactionResponse.status === "ERROR") {
28007
+ throw new Error("Error sending transaction");
27853
28008
  }
27854
- return response.json();
28009
+ return transactionResponse;
28010
+ }
28011
+ async prepareTransaction(transaction) {
28012
+ return this.server.prepareTransaction(transaction);
27855
28013
  }
27856
28014
  }
27857
28015
 
27858
- hederaWalletConnect.type = "hederaWalletConnect";
27859
- /**
27860
- * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
27861
- *
27862
- * The connector removes the need for a WalletConnect modal, and instead
27863
- * communicates with the provided `extension` by opening the extension popup
27864
- * for user interaction upon request.
27865
- */
27866
- function hederaWalletConnect(parameters) {
27867
- const { extension, ...restParameters } = parameters;
27868
- let provider_;
27869
- let providerPromise;
27870
- let connect;
27871
- let displayUri;
27872
- let sessionDelete;
27873
- let disconnect;
27874
- return createConnector((config) => ({
27875
- id: `hedera-wc-${extension.id}`,
27876
- name: extension.name,
27877
- icon: extension.icon,
27878
- type: hederaWalletConnect.type,
27879
- async setup() {
27880
- const provider = await this.getProvider().catch(() => null);
27881
- if (!provider)
27882
- return;
27883
- if (!connect) {
27884
- connect = this.onConnect.bind(this);
27885
- provider.on("connect", connect);
27886
- }
27887
- if (!sessionDelete) {
27888
- sessionDelete = this.onSessionDelete.bind(this);
27889
- provider.on("session_delete", sessionDelete);
28016
+ class XrplRpcClient {
28017
+ rpcUrl;
28018
+ constructor(rpcUrl) {
28019
+ this.rpcUrl = rpcUrl;
28020
+ }
28021
+ async getBalance(address, tokenAddress) {
28022
+ if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
28023
+ return this.getNativeBalance(address);
28024
+ }
28025
+ return this.getIssuedCurrencyBalance(address, tokenAddress);
28026
+ }
28027
+ async getAllBalances(address) {
28028
+ const [nativeBalance, trustLineBalances] = await Promise.all([
28029
+ this.getNativeBalance(address),
28030
+ this.getTrustLines(address),
28031
+ ]);
28032
+ return [
28033
+ {
28034
+ balance: nativeBalance,
28035
+ address: nativeXrplTokenAddress,
28036
+ },
28037
+ ...trustLineBalances.lines.map((line) => ({
28038
+ balance: line.balance,
28039
+ address: `${line.currency}.${line.account}`,
28040
+ })),
28041
+ ];
28042
+ }
28043
+ async getTrustLines(address, issuer) {
28044
+ return this.call("account_lines", [
28045
+ {
28046
+ account: address,
28047
+ ledger_index: "validated",
28048
+ peer: issuer,
28049
+ },
28050
+ ]);
28051
+ }
28052
+ async getTrustLine(address, asset) {
28053
+ const response = await this.getTrustLines(address, asset.issuer);
28054
+ const trustLine = response.lines.find((line) => line.currency === asset.code);
28055
+ return trustLine ?? null;
28056
+ }
28057
+ async accountActivatedInfo(address) {
28058
+ const serverState = await this.getServerState();
28059
+ const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
28060
+ try {
28061
+ const accountInfo = await this.getAccountInfo(address);
28062
+ const balanceBn = BigInt(accountInfo.account_data.Balance);
28063
+ return {
28064
+ isActivated: balanceBn >= reserveBaseBn,
28065
+ reserveBaseBn,
28066
+ };
28067
+ }
28068
+ catch (error) {
28069
+ if (error.message?.includes("actNotFound")) {
28070
+ return { isActivated: false, reserveBaseBn };
27890
28071
  }
27891
- },
27892
- async connect(params = {}) {
28072
+ throw error;
28073
+ }
28074
+ }
28075
+ /**
28076
+ * Waits for a transaction to be validated and returns its final status.
28077
+ * Resolves to 'success' or throws an error with the failed status.
28078
+ */
28079
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
28080
+ const startTime = Date.now();
28081
+ while (true) {
27893
28082
  try {
27894
- const provider = await this.getProvider();
27895
- if (!provider)
27896
- throw new ProviderNotFoundError();
27897
- if (!displayUri) {
27898
- displayUri = this.onDisplayUri;
27899
- provider.on("display_uri", displayUri);
27900
- }
27901
- if (!provider.session) {
27902
- await provider.connect({
27903
- ...("pairingTopic" in params
27904
- ? { pairingTopic: params.pairingTopic }
27905
- : {}),
27906
- });
27907
- }
27908
- const accounts = (await provider.enable()).map((x) => getAddress(x));
27909
- const currentChainId = await this.getChainId();
27910
- if (displayUri) {
27911
- provider.removeListener("display_uri", displayUri);
27912
- displayUri = undefined;
27913
- }
27914
- if (connect) {
27915
- provider.removeListener("connect", connect);
27916
- connect = undefined;
28083
+ const response = await this.call("tx", [
28084
+ {
28085
+ transaction: txHash,
28086
+ binary: false,
28087
+ },
28088
+ ]);
28089
+ if (!response.validated) {
28090
+ if (Date.now() - startTime > timeout) {
28091
+ throw new Error(`Transaction ${txHash} not validated within timeout`);
28092
+ }
28093
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28094
+ await new Promise((res) => setTimeout(res, interval));
28095
+ continue;
27917
28096
  }
27918
- if (!disconnect) {
27919
- disconnect = this.onDisconnect.bind(this);
27920
- provider.on("disconnect", disconnect);
28097
+ const status = response.meta?.TransactionResult;
28098
+ if (status === XrplTxStatus.SUCCESS) {
28099
+ return status;
27921
28100
  }
27922
- if (!sessionDelete) {
27923
- sessionDelete = this.onSessionDelete.bind(this);
27924
- provider.on("session_delete", sessionDelete);
28101
+ else {
28102
+ throw new Error(`Transaction failed with status: ${status}`);
27925
28103
  }
27926
- return { accounts, chainId: currentChainId };
27927
28104
  }
27928
28105
  catch (error) {
27929
- if (/(user rejected|connection request reset)/i.test(error?.message)) {
27930
- throw new UserRejectedRequestError(error);
28106
+ // txnNotFound = still pending or non-existent
28107
+ if (error?.message?.includes("txnNotFound")) {
28108
+ if (Date.now() - startTime > timeout) {
28109
+ throw new Error(`Transaction ${txHash} not found within timeout`);
28110
+ }
28111
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28112
+ await new Promise((res) => setTimeout(res, interval));
28113
+ continue;
27931
28114
  }
27932
28115
  throw error;
27933
28116
  }
27934
- },
27935
- async disconnect() {
27936
- const provider = await this.getProvider();
27937
- try {
27938
- await provider?.disconnect();
27939
- }
27940
- catch (error) {
27941
- if (!/No matching key/i.test(error.message))
27942
- throw error;
27943
- }
27944
- finally {
27945
- if (disconnect) {
27946
- provider?.removeListener("disconnect", disconnect);
27947
- disconnect = undefined;
27948
- }
27949
- if (!connect) {
27950
- connect = this.onConnect.bind(this);
27951
- provider?.on("connect", connect);
27952
- }
27953
- if (sessionDelete) {
27954
- provider?.removeListener("session_delete", sessionDelete);
27955
- sessionDelete = undefined;
27956
- }
27957
- }
27958
- },
27959
- async getAccounts() {
27960
- const provider = await this.getProvider();
27961
- return provider.accounts.map((x) => getAddress(x));
27962
- },
27963
- async getProvider() {
27964
- async function initProvider() {
27965
- const optionalChains = config.chains.map((x) => x.id);
27966
- if (!optionalChains.length)
27967
- return;
27968
- const { EthereumProvider } = await import('./index.es-BfdAGErV.js');
27969
- const rawProvider = await EthereumProvider.init({
27970
- ...restParameters,
27971
- disableProviderPing: true,
27972
- optionalChains,
27973
- projectId: restParameters.projectId,
27974
- rpcMap: Object.fromEntries(config.chains.map((chain) => {
27975
- const [url] = extractRpcUrls({
27976
- chain,
27977
- transports: config.transports,
27978
- });
27979
- return [chain.id, url];
27980
- })),
27981
- showQrModal: false,
27982
- // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
27983
- // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
27984
- customStoragePrefix: "squid-hedera",
27985
- });
27986
- const proxiedProvider = new Proxy(rawProvider, {
27987
- get(target, prop, receiver) {
27988
- if (prop === "request") {
27989
- return async (args) => {
27990
- const signingMethods = [
27991
- "eth_sendTransaction",
27992
- "eth_signTransaction",
27993
- ];
27994
- if (signingMethods.includes(args.method)) {
27995
- try {
27996
- HederaExtensionHelper.extensionOpen(extension.id);
27997
- }
27998
- catch { }
27999
- }
28000
- // forward request to original provider
28001
- return target.request(args);
28002
- };
28003
- }
28004
- // forward all other properties/methods
28005
- return Reflect.get(target, prop, receiver);
28006
- },
28007
- });
28008
- return proxiedProvider;
28009
- }
28010
- if (!provider_) {
28011
- if (!providerPromise)
28012
- providerPromise = initProvider();
28013
- provider_ = await providerPromise;
28014
- provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
28015
- }
28016
- return provider_;
28017
- },
28018
- async getChainId() {
28019
- const provider = await this.getProvider();
28020
- return provider.chainId;
28021
- },
28022
- async isAuthorized() {
28023
- try {
28024
- const accounts = await this.getAccounts();
28025
- return accounts.length > 0;
28026
- }
28027
- catch {
28028
- return false;
28029
- }
28030
- },
28031
- onAccountsChanged(accounts) {
28032
- if (accounts.length === 0)
28033
- this.onDisconnect();
28034
- else
28035
- config.emitter.emit("change", {
28036
- accounts: accounts.map((x) => getAddress(x)),
28037
- });
28038
- },
28039
- onChainChanged(chain) {
28040
- const chainId = Number(chain);
28041
- config.emitter.emit("change", { chainId });
28042
- },
28043
- async onConnect(connectInfo) {
28044
- const chainId = Number(connectInfo.chainId);
28045
- const accounts = await this.getAccounts();
28046
- config.emitter.emit("connect", { accounts, chainId });
28047
- },
28048
- async onDisconnect() {
28049
- config.emitter.emit("disconnect");
28050
- const provider = await this.getProvider();
28051
- if (disconnect) {
28052
- provider.removeListener("disconnect", disconnect);
28053
- disconnect = undefined;
28054
- }
28055
- if (sessionDelete) {
28056
- provider.removeListener("session_delete", sessionDelete);
28057
- sessionDelete = undefined;
28058
- }
28059
- if (!connect) {
28060
- connect = this.onConnect.bind(this);
28061
- provider.on("connect", connect);
28062
- }
28063
- },
28064
- onDisplayUri(uri) {
28065
- config.emitter.emit("message", { type: "display_uri", data: uri });
28066
- HederaExtensionHelper.extensionConnect(extension.id, uri);
28067
- },
28068
- onSessionDelete() {
28069
- this.onDisconnect();
28070
- },
28071
- }));
28072
- }
28073
-
28074
- const createWagmiConfig = (squidChains, hederaExtensions) => {
28075
- const filteredEvmChains = squidChains.filter((chain) => chain.chainType === ChainType.EVM);
28076
- if (filteredEvmChains.length === 0) {
28077
- throw new Error("At least one chain is required");
28117
+ }
28078
28118
  }
28079
- const wagmiChains = filteredEvmChains.map((chain) => {
28080
- return defineChain({
28081
- id: Number(chain.chainId),
28082
- name: chain.networkName,
28083
- nativeCurrency: {
28084
- name: chain.nativeCurrency.name,
28085
- symbol: chain.nativeCurrency.symbol,
28086
- decimals: chain.nativeCurrency.decimals,
28119
+ async getServerState() {
28120
+ return this.call("server_state", [{}]);
28121
+ }
28122
+ async getAccountInfo(address) {
28123
+ return this.call("account_info", [
28124
+ {
28125
+ account: address,
28126
+ ledger_index: "validated",
28087
28127
  },
28088
- rpcUrls: {
28089
- public: {
28090
- http: [chain.rpc],
28091
- },
28092
- default: {
28093
- http: [chain.rpc],
28094
- },
28128
+ ]);
28129
+ }
28130
+ /**
28131
+ * Returns the balance of the user in the native XRP token
28132
+ * formatted as a string
28133
+ */
28134
+ async getNativeBalance(address) {
28135
+ const [accountInfo, serverState] = await Promise.all([
28136
+ this.getAccountInfo(address),
28137
+ this.getServerState(),
28138
+ ]);
28139
+ const balance = BigInt(accountInfo.account_data.Balance);
28140
+ const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
28141
+ const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
28142
+ const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
28143
+ const reserveBalance = reserveBase + ownerCount * reserveIncrement;
28144
+ const spendableBalance = balance - reserveBalance;
28145
+ return formatBNToReadable(spendableBalance, 6);
28146
+ }
28147
+ /**
28148
+ * Returns the balance of the user in the given issued currency (e.g. RLUSD)
28149
+ * formatted as a string
28150
+ */
28151
+ async getIssuedCurrencyBalance(address, tokenAddress) {
28152
+ const response = await this.getTrustLines(address);
28153
+ const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
28154
+ return tokenBalance?.balance || "0";
28155
+ }
28156
+ async call(method, params) {
28157
+ const response = await fetch(this.rpcUrl, {
28158
+ method: "POST",
28159
+ headers: {
28160
+ "Content-Type": "application/json",
28095
28161
  },
28096
- });
28097
- });
28098
- const wcMetadata = {
28099
- url: SQUID_METADATA.url,
28100
- name: SQUID_METADATA.name,
28101
- icons: [SQUID_METADATA.icon],
28102
- description: SQUID_METADATA.description,
28103
- };
28104
- return createConfig({
28105
- chains: wagmiChains,
28106
- transports: Object.fromEntries(wagmiChains.map((chain) => [
28107
- chain.id,
28108
- http(chain.rpcUrls.public.http[0] ?? ""),
28109
- ])),
28110
- connectors: [
28111
- injected(),
28112
- safe({
28113
- allowedDomains: [/app.safe.global$/],
28114
- }),
28115
- metaMask({
28116
- dappMetadata: {
28117
- name: SQUID_METADATA.name,
28118
- url: SQUID_METADATA.url,
28119
- iconUrl: SQUID_METADATA.icon,
28120
- },
28121
- }),
28122
- coinbaseWallet({
28123
- appName: SQUID_METADATA.name,
28124
- appLogoUrl: SQUID_METADATA.icon,
28125
- }),
28126
- walletConnect({
28127
- projectId: WALLETCONNECT_PROJECT_ID,
28128
- metadata: wcMetadata,
28162
+ body: JSON.stringify({
28163
+ jsonrpc: "2.0",
28164
+ id: 1,
28165
+ method,
28166
+ params,
28129
28167
  }),
28130
- ...hederaExtensions.map((extension) => hederaWalletConnect({
28131
- projectId: WALLETCONNECT_PROJECT_ID,
28132
- metadata: wcMetadata,
28133
- extension,
28134
- })),
28135
- ],
28136
- });
28137
- };
28138
- // Taken from wagmi docs
28139
- // https://wagmi.sh/react/guides/ethers
28140
- function clientToSigner(client) {
28141
- const { account, chain, transport } = client;
28142
- if (!account || !chain || !transport) {
28143
- return undefined;
28168
+ });
28169
+ const data = await response.json();
28170
+ if (!data.result) {
28171
+ throw new Error(`Invalid response from RPC (${method})`);
28172
+ }
28173
+ if ("error" in data.result) {
28174
+ throw new Error(`Error from RPC (${method}): ${data.result.error}`);
28175
+ }
28176
+ return data.result;
28144
28177
  }
28145
- const network = {
28146
- chainId: chain.id,
28147
- name: chain.name,
28148
- ensAddress: chain.contracts?.ensRegistry?.address,
28149
- };
28150
- const provider = new BrowserProvider(transport, network);
28151
- const signer = new JsonRpcSigner(provider, account.address);
28152
- return signer;
28153
28178
  }
28154
28179
 
28155
- function useEvmSigner({ chainId }) {
28156
- const { connector } = useAccount();
28157
- const { data: client } = useWalletClient({ chainId, connector });
28158
- const signer = useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
28159
- return { signer };
28180
+ const clientCache = new Map();
28181
+ async function getClient(chain) {
28182
+ const key = `${chain.chainType}:${chain.chainId}`;
28183
+ if (clientCache.has(key)) {
28184
+ return clientCache.get(key);
28185
+ }
28186
+ const client = await createClient(chain);
28187
+ clientCache.set(key, client);
28188
+ return client;
28160
28189
  }
28161
- const useSigner = ({ chain }) => {
28162
- const evmChainId = chain?.chainType === ChainType.EVM ? Number(chain.chainId) : undefined;
28163
- // EVM and Cosmos need a different signer for each chain
28164
- // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
28165
- const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
28166
- const { signer: cosmosSigner } = useCosmosSigner({ chain });
28167
- const { signer: solanaSigner } = useSolanaContext();
28168
- const { signer: bitcoinSigner } = useBitcoinContext();
28169
- const { signer: suiSigner } = useSuiContext();
28170
- const { signer: xrplSigner } = useXrplContext();
28171
- const { signer: stellarSigner } = useStellarContext();
28172
- const isEvmSignerReady = !!evmSigner;
28173
- const isSolanaSignerReady = !!solanaSigner;
28174
- const isCosmosSignerReady = !!cosmosSigner;
28175
- const isBitcoinSignerReady = !!bitcoinSigner;
28176
- const isSuiSignerReady = !!suiSigner;
28177
- const isXrplSignerReady = !!xrplSigner;
28178
- const isStellarSignerReady = !!stellarSigner;
28179
- const isSignerReady = useMemo(() => {
28180
- if (!chain?.chainType)
28181
- return false;
28182
- switch (chain.chainType) {
28183
- case ChainType.EVM:
28184
- return isEvmSignerReady;
28185
- case ChainType.COSMOS:
28186
- return isCosmosSignerReady;
28187
- case ChainType.BTC:
28188
- return isBitcoinSignerReady;
28189
- case ChainType.SOLANA:
28190
- return isSolanaSignerReady;
28191
- case ChainType.SUI:
28192
- return isSuiSignerReady;
28193
- case ChainType.XRPL:
28194
- return isXrplSignerReady;
28195
- case ChainType.STELLAR:
28196
- return isStellarSignerReady;
28197
- }
28198
- }, [
28199
- chain?.chainType,
28200
- isEvmSignerReady,
28201
- isCosmosSignerReady,
28202
- isBitcoinSignerReady,
28203
- isSolanaSignerReady,
28204
- isSuiSignerReady,
28205
- isXrplSignerReady,
28206
- isStellarSignerReady,
28207
- ]);
28208
- return {
28209
- isSignerReady,
28210
- evmSigner,
28211
- cosmosSigner,
28212
- bitcoinSigner,
28213
- solanaSigner,
28214
- suiSigner,
28215
- xrplSigner,
28216
- stellarSigner,
28217
- };
28218
- };
28219
-
28220
- function useHederaTokenAssociations({ address, chain, token }) {
28221
- const publicClient = usePublicClient({
28222
- chainId: Number(CHAIN_IDS.HEDERA),
28223
- });
28224
- const { evmSigner } = useSigner({ chain });
28225
- const queryClient = useQueryClient();
28226
- /**
28227
- * Creates a token association transaction where the destination account authorizes to receive the given token
28228
- */
28229
- const associateToken = useMutation({
28230
- mutationFn: async () => {
28231
- try {
28232
- if (!evmSigner) {
28233
- throw new Error("EVM signer not found");
28234
- }
28235
- if (chain?.chainId !== CHAIN_IDS.HEDERA ||
28236
- token?.chainId !== CHAIN_IDS.HEDERA) {
28237
- throw new Error("Chain and token to associate must be on Hedera");
28238
- }
28239
- const tokenAddress = parseEvmAddress(token.address);
28240
- if (!tokenAddress) {
28241
- throw new Error("Invalid token address");
28242
- }
28243
- const userAddress = parseEvmAddress(address);
28244
- if (!userAddress) {
28245
- throw new Error("Invalid user address");
28246
- }
28247
- const encodedData = encodeFunctionData({
28248
- abi: hrc20,
28249
- functionName: "associate",
28250
- args: [],
28251
- });
28252
- const txRes = await evmSigner.sendTransaction({
28253
- data: encodedData,
28254
- to: tokenAddress,
28255
- chainId: chain.chainId,
28256
- });
28257
- const receipt = await txRes.wait();
28258
- if (receipt?.status !== 1) {
28259
- throw new Error(`Transaction failed with status: ${receipt?.status}`);
28260
- }
28261
- return true;
28262
- }
28263
- catch (error) {
28264
- console.error("Error associating Hedera token:", error);
28265
- return false;
28266
- }
28267
- },
28268
- async onSuccess() {
28269
- queryClient.refetchQueries({
28270
- queryKey: getPrefixKey(QueryKeys.IsHederaTokenAssociated),
28271
- });
28272
- },
28273
- });
28274
- /**
28275
- * Checks if the destination account has associated the given token.
28276
- *
28277
- * Hedera requires accounts to associate a token before being able to receive it.
28278
- *
28279
- * Accounts which have max. associations set to -1 can receive any token without previous association
28280
- */
28281
- const isTokenAssociated = useQuery({
28282
- queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
28283
- queryFn: async () => {
28284
- if (token?.chainId !== CHAIN_IDS.HEDERA) {
28285
- return true;
28286
- }
28287
- // The native HBAR token doesn't need an association
28288
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
28289
- return true;
28290
- }
28291
- if (!chain || !address || !publicClient) {
28292
- throw new Error("Missing required parameters");
28293
- }
28294
- // TODO: update types
28295
- const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
28296
- if (!apiUrl) {
28297
- throw new Error("Missing Hedera mirror node URL in chain config");
28298
- }
28299
- const hederaApiClient = new HederaApiClient(apiUrl);
28300
- return hederaApiClient.isTokenAssociated({
28301
- address,
28302
- token,
28190
+ async function createClient(chain) {
28191
+ switch (chain.chainType) {
28192
+ case ChainType.EVM:
28193
+ return new JsonRpcProvider(chain.rpc);
28194
+ case ChainType.COSMOS:
28195
+ const rpcUrl = await getWorkingCosmosRpcUrl(chain);
28196
+ return (await StargateClient.connect(rpcUrl));
28197
+ case ChainType.SOLANA:
28198
+ return new Connection(SOLANA_RPC_URL);
28199
+ case ChainType.BTC:
28200
+ return null;
28201
+ case ChainType.SUI:
28202
+ return new SuiClient({
28203
+ url: chain.rpc,
28303
28204
  });
28304
- },
28305
- enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
28306
- });
28307
- return {
28308
- isTokenAssociated,
28309
- associateToken,
28310
- };
28205
+ case ChainType.XRPL:
28206
+ return new XrplRpcClient(chain.rpc);
28207
+ case ChainType.STELLAR:
28208
+ return new StellarRpcClient(chain.rpc);
28209
+ }
28311
28210
  }
28312
28211
 
28313
- const useKeyboardNavigation = ({ onEscape }) => {
28314
- const onKeyDown = useCallback((event) => {
28315
- if (event.key === "Escape") {
28316
- onEscape?.();
28317
- return;
28318
- }
28319
- }, [onEscape]);
28320
- useEffect(() => {
28321
- document.addEventListener("keydown", onKeyDown, false);
28322
- return () => {
28323
- document.removeEventListener("keydown", onKeyDown, false);
28324
- };
28325
- }, [onKeyDown]);
28326
- };
28327
-
28328
- const useSquidQueryClient = () => {
28329
- const queryClient = useQueryClient();
28330
- const invalidateQueries = (key) => {
28331
- const prefixKey = getPrefixKey(key);
28332
- queryClient.invalidateQueries(prefixKey);
28333
- };
28334
- const refetchQueries = (key) => {
28335
- const prefixKey = getPrefixKey(key);
28336
- queryClient.refetchQueries(prefixKey);
28337
- };
28338
- const invalidateAndRefetchQueries = (key) => {
28339
- invalidateQueries(key);
28340
- refetchQueries(key);
28341
- };
28342
- return {
28343
- invalidateQueries,
28344
- refetchQueries,
28345
- invalidateAndRefetchQueries,
28346
- };
28347
- };
28348
-
28349
28212
  /**
28350
28213
  * The default multicall3 address
28351
28214
  * available on most EVM chains
@@ -29328,12 +29191,15 @@ const getAllXrplTokensBalance = async (userAddress, xrplTokens, xrplChains) => {
29328
29191
  };
29329
29192
  const getStellarTokenBalance = async (userAddress, token, chain) => {
29330
29193
  const stellarClient = await getClient(chain);
29331
- const balance = await stellarClient.getBalance(userAddress, token.address, chain.chainId);
29194
+ if (!isStellarToken(token)) {
29195
+ throw new Error("Token must be a Stellar token");
29196
+ }
29197
+ const balance = await stellarClient.getBalance(userAddress, token.chainAssetConfig.stellar.contractAddress, chain.chainId);
29332
29198
  return BigInt(balance);
29333
29199
  };
29334
29200
  const getAllStellarTokensBalance = async (userAddress, stellarTokens, stellarChains) => {
29335
29201
  const getBalancesForChain = async (chain) => {
29336
- const tokensForChain = stellarTokens.filter((t) => t.chainId === chain.chainId);
29202
+ const tokensForChain = stellarTokens.filter((t) => t.chainId === chain.chainId && isStellarToken(t));
29337
29203
  const stellarClient = await getClient(chain);
29338
29204
  const allBalances = await stellarClient.getAllBalances(userAddress, tokensForChain);
29339
29205
  return allBalances.map((token) => {
@@ -29360,372 +29226,6 @@ function timeout(ms, promise) {
29360
29226
  return Promise.race([promise, timeoutPromise]);
29361
29227
  }
29362
29228
 
29363
- class StellarRpcClient {
29364
- server;
29365
- constructor(rpcUrl) {
29366
- this.server = new rpc.Server(rpcUrl);
29367
- }
29368
- /**
29369
- * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
29370
- *
29371
- * With Contract Tokens, we need to call the .balance method on the token contract
29372
- * and simulate the transaction to get the balance.
29373
- */
29374
- async getBalance(userAddress, tokenAddress, chainId) {
29375
- const account = await this.server.getAccount(userAddress);
29376
- const network = getStellarNetwork(chainId);
29377
- if (network == null) {
29378
- throw new Error(`No Stellar network found for chainId ${chainId}`);
29379
- }
29380
- const txBuilder = new TransactionBuilder(account, {
29381
- fee: (BigInt(BASE_FEE) * BigInt(2)).toString(),
29382
- networkPassphrase: network,
29383
- });
29384
- const contract = new Contract(tokenAddress);
29385
- const tx = txBuilder
29386
- .addOperation(contract.call("balance", new Address(userAddress).toScVal()))
29387
- .setTimeout(TimeoutInfinite)
29388
- .build();
29389
- const simulateTxResponse = await this.server.simulateTransaction(tx);
29390
- if ("error" in simulateTxResponse) {
29391
- const isNoBalanceError = simulateTxResponse.error.includes("trying to get non-existing value for contract instance");
29392
- // If the error message indicates that the user has no balance just return 0
29393
- // We don't want to spam with this error as it's pretty common
29394
- if (isNoBalanceError) {
29395
- return "0";
29396
- }
29397
- throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
29398
- }
29399
- if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
29400
- const native = scValToNative(simulateTxResponse.result.retval);
29401
- return native.toString();
29402
- }
29403
- throw new Error("Failed to fetch balance");
29404
- }
29405
- async getAllBalances(userAddress, tokens) {
29406
- const balancePromises = tokens.map((token) => {
29407
- return this.getBalance(userAddress, token.address, token.chainId);
29408
- });
29409
- const results = await Promise.allSettled(balancePromises);
29410
- const balances = results.map((result) => {
29411
- if (result.status === "fulfilled") {
29412
- return result.value;
29413
- }
29414
- return "0";
29415
- });
29416
- return balances.map((balance, index) => {
29417
- return {
29418
- ...tokens[index],
29419
- balance,
29420
- };
29421
- });
29422
- }
29423
- /**
29424
- * Resolves when the transaction is confirmed, or fails after a timeout.
29425
- */
29426
- async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
29427
- const startTime = Date.now();
29428
- while (true) {
29429
- const result = await this.server.getTransaction(txHash);
29430
- if (result.status === rpc.Api.GetTransactionStatus.NOT_FOUND) {
29431
- if (Date.now() - startTime > timeout) {
29432
- throw new Error(`Transaction ${txHash} not found within timeout`);
29433
- }
29434
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29435
- await new Promise((res) => setTimeout(res, interval));
29436
- continue;
29437
- }
29438
- if (result.status === rpc.Api.GetTransactionStatus.SUCCESS) {
29439
- return result.status;
29440
- }
29441
- throw new Error(`Transaction failed with status: ${result.status}`);
29442
- }
29443
- }
29444
- async isAccountActivated(address) {
29445
- try {
29446
- await this.getAccount(address);
29447
- return true;
29448
- }
29449
- catch (error) {
29450
- if (error?.message && error.message.includes("Account not found")) {
29451
- return false;
29452
- }
29453
- throw error;
29454
- }
29455
- }
29456
- async getAccount(address) {
29457
- return this.server.getAccount(address);
29458
- }
29459
- async sendTransaction(transaction) {
29460
- const transactionResponse = await this.server.sendTransaction(transaction);
29461
- if (transactionResponse.status === "ERROR") {
29462
- throw new Error("Error sending transaction");
29463
- }
29464
- return transactionResponse;
29465
- }
29466
- async prepareTransaction(transaction) {
29467
- return this.server.prepareTransaction(transaction);
29468
- }
29469
- }
29470
-
29471
- class XrplRpcClient {
29472
- rpcUrl;
29473
- constructor(rpcUrl) {
29474
- this.rpcUrl = rpcUrl;
29475
- }
29476
- async getBalance(address, tokenAddress) {
29477
- if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
29478
- return this.getNativeBalance(address);
29479
- }
29480
- return this.getIssuedCurrencyBalance(address, tokenAddress);
29481
- }
29482
- async getAllBalances(address) {
29483
- const [nativeBalance, trustLineBalances] = await Promise.all([
29484
- this.getNativeBalance(address),
29485
- this.getTrustLines(address),
29486
- ]);
29487
- return [
29488
- {
29489
- balance: nativeBalance,
29490
- address: nativeXrplTokenAddress,
29491
- },
29492
- ...trustLineBalances.lines.map((line) => ({
29493
- balance: line.balance,
29494
- address: `${line.currency}.${line.account}`,
29495
- })),
29496
- ];
29497
- }
29498
- async getTrustLines(address, issuer) {
29499
- return this.call("account_lines", [
29500
- {
29501
- account: address,
29502
- ledger_index: "validated",
29503
- peer: issuer,
29504
- },
29505
- ]);
29506
- }
29507
- async getTrustLine(address, issuer, currency) {
29508
- const response = await this.getTrustLines(address, issuer);
29509
- const trustLine = response.lines.find((line) => line.currency === currency);
29510
- return trustLine ?? null;
29511
- }
29512
- async accountActivatedInfo(address) {
29513
- const serverState = await this.getServerState();
29514
- const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
29515
- try {
29516
- const accountInfo = await this.getAccountInfo(address);
29517
- const balanceBn = BigInt(accountInfo.account_data.Balance);
29518
- return {
29519
- isActivated: balanceBn >= reserveBaseBn,
29520
- reserveBaseBn,
29521
- };
29522
- }
29523
- catch (error) {
29524
- if (error.message?.includes("actNotFound")) {
29525
- return { isActivated: false, reserveBaseBn };
29526
- }
29527
- throw error;
29528
- }
29529
- }
29530
- /**
29531
- * Waits for a transaction to be validated and returns its final status.
29532
- * Resolves to 'success' or throws an error with the failed status.
29533
- */
29534
- async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
29535
- const startTime = Date.now();
29536
- while (true) {
29537
- try {
29538
- const response = await this.call("tx", [
29539
- {
29540
- transaction: txHash,
29541
- binary: false,
29542
- },
29543
- ]);
29544
- if (!response.validated) {
29545
- if (Date.now() - startTime > timeout) {
29546
- throw new Error(`Transaction ${txHash} not validated within timeout`);
29547
- }
29548
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29549
- await new Promise((res) => setTimeout(res, interval));
29550
- continue;
29551
- }
29552
- const status = response.meta?.TransactionResult;
29553
- if (status === XrplTxStatus.SUCCESS) {
29554
- return status;
29555
- }
29556
- else {
29557
- throw new Error(`Transaction failed with status: ${status}`);
29558
- }
29559
- }
29560
- catch (error) {
29561
- // txnNotFound = still pending or non-existent
29562
- if (error?.message?.includes("txnNotFound")) {
29563
- if (Date.now() - startTime > timeout) {
29564
- throw new Error(`Transaction ${txHash} not found within timeout`);
29565
- }
29566
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29567
- await new Promise((res) => setTimeout(res, interval));
29568
- continue;
29569
- }
29570
- throw error;
29571
- }
29572
- }
29573
- }
29574
- async getServerState() {
29575
- return this.call("server_state", [{}]);
29576
- }
29577
- async getAccountInfo(address) {
29578
- return this.call("account_info", [
29579
- {
29580
- account: address,
29581
- ledger_index: "validated",
29582
- },
29583
- ]);
29584
- }
29585
- /**
29586
- * Returns the balance of the user in the native XRP token
29587
- * formatted as a string
29588
- */
29589
- async getNativeBalance(address) {
29590
- const [accountInfo, serverState] = await Promise.all([
29591
- this.getAccountInfo(address),
29592
- this.getServerState(),
29593
- ]);
29594
- const balance = BigInt(accountInfo.account_data.Balance);
29595
- const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
29596
- const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
29597
- const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
29598
- const reserveBalance = reserveBase + ownerCount * reserveIncrement;
29599
- const spendableBalance = balance - reserveBalance;
29600
- return formatBNToReadable(spendableBalance, 6);
29601
- }
29602
- /**
29603
- * Returns the balance of the user in the given issued currency (e.g. RLUSD)
29604
- * formatted as a string
29605
- */
29606
- async getIssuedCurrencyBalance(address, tokenAddress) {
29607
- const response = await this.getTrustLines(address);
29608
- const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
29609
- return tokenBalance?.balance || "0";
29610
- }
29611
- async call(method, params) {
29612
- const response = await fetch(this.rpcUrl, {
29613
- method: "POST",
29614
- headers: {
29615
- "Content-Type": "application/json",
29616
- },
29617
- body: JSON.stringify({
29618
- jsonrpc: "2.0",
29619
- id: 1,
29620
- method,
29621
- params,
29622
- }),
29623
- });
29624
- const data = await response.json();
29625
- if (!data.result) {
29626
- throw new Error(`Invalid response from RPC (${method})`);
29627
- }
29628
- if ("error" in data.result) {
29629
- throw new Error(`Error from RPC (${method}): ${data.result.error}`);
29630
- }
29631
- return data.result;
29632
- }
29633
- }
29634
-
29635
- const clientCache = new Map();
29636
- async function getClient(chain) {
29637
- const key = `${chain.chainType}:${chain.chainId}`;
29638
- if (clientCache.has(key)) {
29639
- return clientCache.get(key);
29640
- }
29641
- const client = await createClient(chain);
29642
- clientCache.set(key, client);
29643
- return client;
29644
- }
29645
- async function createClient(chain) {
29646
- switch (chain.chainType) {
29647
- case ChainType.EVM:
29648
- return new JsonRpcProvider(chain.rpc);
29649
- case ChainType.COSMOS:
29650
- const rpcUrl = await getWorkingCosmosRpcUrl(chain);
29651
- return (await StargateClient.connect(rpcUrl));
29652
- case ChainType.SOLANA:
29653
- return new Connection(SOLANA_RPC_URL);
29654
- case ChainType.BTC:
29655
- return null;
29656
- case ChainType.SUI:
29657
- return new SuiClient({
29658
- url: chain.rpc,
29659
- });
29660
- case ChainType.XRPL:
29661
- return new XrplRpcClient(chain.rpc);
29662
- case ChainType.STELLAR:
29663
- return new StellarRpcClient(chain.rpc);
29664
- }
29665
- }
29666
-
29667
- class StellarApiClient {
29668
- apiUrl;
29669
- constructor(apiUrl) {
29670
- this.apiUrl = apiUrl;
29671
- }
29672
- async getBaseReserve() {
29673
- const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
29674
- if (!response.ok) {
29675
- throw new Error(`Failed to fetch ledgers: ${response.status}`);
29676
- }
29677
- const ledgers = await response.json();
29678
- const latestLedger = ledgers?._embedded.records?.[0];
29679
- if (latestLedger?.base_reserve_in_stroops == null) {
29680
- throw new Error("Invalid ledger data");
29681
- }
29682
- const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
29683
- return baseReserveBn;
29684
- }
29685
- }
29686
-
29687
- const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
29688
- function useStellarAccountActivation({ address, chain, token, }) {
29689
- /**
29690
- * Checks if the destination account exists on the Stellar network
29691
- * Stellar accounts need to have a minimum balance before they can receive payments
29692
- */
29693
- const accountActivatedInfo = useQuery({
29694
- queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
29695
- queryFn: async () => {
29696
- if (chain?.chainType !== ChainType.STELLAR ||
29697
- token?.type !== ChainType.STELLAR) {
29698
- return null;
29699
- }
29700
- if (!address) {
29701
- throw new Error("Destination address is required");
29702
- }
29703
- // TODO: update types
29704
- const [horizonApiUrl] = chain?.horizonRpcList;
29705
- if (typeof horizonApiUrl !== "string") {
29706
- throw new Error("Invalid Horizon API URL");
29707
- }
29708
- const stellarApiClient = new StellarApiClient(horizonApiUrl);
29709
- const reserveBase = await stellarApiClient.getBaseReserve();
29710
- // Stellar accounts require two base reserves to be activated
29711
- // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
29712
- const accountReserveBase = reserveBase * BigInt(2);
29713
- const stellarRpcClient = await getClient(chain);
29714
- const isActivated = await stellarRpcClient.isAccountActivated(address);
29715
- return {
29716
- isActivated,
29717
- reserveBaseBn: accountReserveBase,
29718
- };
29719
- },
29720
- enabled: !!address && chain?.chainType === ChainType.STELLAR,
29721
- refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
29722
- refetchOnWindowFocus: true,
29723
- });
29724
- return {
29725
- accountActivatedInfo,
29726
- };
29727
- }
29728
-
29729
29229
  const DEFAULT_REFRESH_INTERVAL_MS = 15000;
29730
29230
  const useEvmBalance = ({ chain, token, userAddress, enabled = true, refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS, }) => {
29731
29231
  const { isChainTypeConnected } = useWallet();
@@ -29920,6 +29420,8 @@ function useNativeTokenForChain(chain) {
29920
29420
  }
29921
29421
  };
29922
29422
  const nativeTokenForChainType = useMemo(() => {
29423
+ if (!chain)
29424
+ return undefined;
29923
29425
  return findNativeToken(getTokensForChainType(), chain);
29924
29426
  }, [chain]);
29925
29427
  return { nativeToken: nativeTokenForChainType };
@@ -30166,47 +29668,842 @@ const useNativeBalance = (chain) => {
30166
29668
  return false;
30167
29669
  switch (chain.chainType) {
30168
29670
  case ChainType.EVM:
30169
- return isEvmLoading;
29671
+ return isEvmLoading;
29672
+ case ChainType.COSMOS:
29673
+ return isCosmosLoading;
29674
+ case ChainType.BTC:
29675
+ return isBitcoinLoading;
29676
+ case ChainType.SOLANA:
29677
+ return isSolanaLoading;
29678
+ case ChainType.SUI:
29679
+ return isSuiLoading;
29680
+ case ChainType.XRPL:
29681
+ return isXrpLoading;
29682
+ case ChainType.STELLAR:
29683
+ return isStellarLoading;
29684
+ }
29685
+ }, [
29686
+ chain?.chainType,
29687
+ isEvmLoading,
29688
+ isCosmosLoading,
29689
+ isBitcoinLoading,
29690
+ isSolanaLoading,
29691
+ isSuiLoading,
29692
+ isXrpLoading,
29693
+ isStellarLoading,
29694
+ ]);
29695
+ return { nativeBalance, nativeBalanceFormatted, isLoading };
29696
+ };
29697
+
29698
+ function useHederaAccountActivation({ address, chain, token }) {
29699
+ const { balance: destNativeEvmBalance } = useEvmNativeBalance({
29700
+ chain,
29701
+ address,
29702
+ });
29703
+ const isHederaAccountActivated = useMemo(() => {
29704
+ if (token?.chainId !== CHAIN_IDS.HEDERA)
29705
+ return true;
29706
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
29707
+ return true;
29708
+ return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
29709
+ }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
29710
+ return {
29711
+ isHederaAccountActivated,
29712
+ };
29713
+ }
29714
+
29715
+ var hrc20 = [
29716
+ {
29717
+ inputs: [
29718
+ ],
29719
+ name: "associate",
29720
+ outputs: [
29721
+ {
29722
+ internalType: "uint256",
29723
+ name: "responseCode",
29724
+ type: "uint256"
29725
+ }
29726
+ ],
29727
+ stateMutability: "nonpayable",
29728
+ type: "function"
29729
+ }
29730
+ ];
29731
+
29732
+ /**
29733
+ * Client for interacting with the Hedera Mirrornode API.
29734
+ *
29735
+ * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
29736
+ */
29737
+ class HederaApiClient {
29738
+ apiUrl;
29739
+ constructor(apiUrl) {
29740
+ this.apiUrl = apiUrl;
29741
+ }
29742
+ async isTokenAssociated({ address, token, }) {
29743
+ const accountInfo = await this.getAccountInfo(address);
29744
+ // Unlimited auto associations
29745
+ if (accountInfo.max_automatic_token_associations === -1) {
29746
+ return true;
29747
+ }
29748
+ // If there's no unlimited auto-associations, we need to check if the token is already associated.
29749
+ const { tokens: accountTokens } = await this.getAccountTokens(address);
29750
+ const tokenId = convertEvmAddressToHederaAccountId(token.address);
29751
+ if (accountTokens.some((t) => t.token_id === tokenId)) {
29752
+ // Token is already associated
29753
+ return true;
29754
+ }
29755
+ // Finally, if not auto-associated, check if there is an available auto-association slot
29756
+ const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
29757
+ const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
29758
+ autoAssociatedTokens.length;
29759
+ return remainingAutoAssociations > 0;
29760
+ }
29761
+ async getAccountInfo(address) {
29762
+ const data = await this.fetch(`accounts/${address}`);
29763
+ if (typeof data.max_automatic_token_associations !== "number") {
29764
+ throw new Error("Invalid max_automatic_token_associations type, expected number");
29765
+ }
29766
+ if (typeof data.balance.balance !== "number") {
29767
+ throw new Error("Invalid balance type, expected number");
29768
+ }
29769
+ if (!Array.isArray(data.balance.tokens)) {
29770
+ throw new Error("Invalid tokens type, expected array");
29771
+ }
29772
+ return data;
29773
+ }
29774
+ async getAccountTokens(address) {
29775
+ const data = await this.fetch(`accounts/${address}/tokens`);
29776
+ if (!Array.isArray(data.tokens)) {
29777
+ throw new Error("Invalid tokens type, expected array");
29778
+ }
29779
+ const firstToken = data.tokens[0];
29780
+ if (typeof firstToken?.automatic_association !== "boolean") {
29781
+ throw new Error("Invalid automatic_association type, expected boolean");
29782
+ }
29783
+ if (typeof firstToken?.token_id !== "string") {
29784
+ throw new Error("Invalid token_id type, expected string");
29785
+ }
29786
+ return data;
29787
+ }
29788
+ async fetch(path) {
29789
+ const url = new URL(path, this.apiUrl);
29790
+ const response = await fetch(url);
29791
+ if (!response.ok) {
29792
+ throw new Error(`Hedera API error: ${response.status}`);
29793
+ }
29794
+ return response.json();
29795
+ }
29796
+ }
29797
+
29798
+ hederaWalletConnect.type = "hederaWalletConnect";
29799
+ /**
29800
+ * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
29801
+ *
29802
+ * The connector removes the need for a WalletConnect modal, and instead
29803
+ * communicates with the provided `extension` by opening the extension popup
29804
+ * for user interaction upon request.
29805
+ */
29806
+ function hederaWalletConnect(parameters) {
29807
+ const { extension, ...restParameters } = parameters;
29808
+ let provider_;
29809
+ let providerPromise;
29810
+ let connect;
29811
+ let displayUri;
29812
+ let sessionDelete;
29813
+ let disconnect;
29814
+ return createConnector((config) => ({
29815
+ id: `hedera-wc-${extension.id}`,
29816
+ name: extension.name,
29817
+ icon: extension.icon,
29818
+ type: hederaWalletConnect.type,
29819
+ async setup() {
29820
+ const provider = await this.getProvider().catch(() => null);
29821
+ if (!provider)
29822
+ return;
29823
+ if (!connect) {
29824
+ connect = this.onConnect.bind(this);
29825
+ provider.on("connect", connect);
29826
+ }
29827
+ if (!sessionDelete) {
29828
+ sessionDelete = this.onSessionDelete.bind(this);
29829
+ provider.on("session_delete", sessionDelete);
29830
+ }
29831
+ },
29832
+ async connect(params = {}) {
29833
+ try {
29834
+ const provider = await this.getProvider();
29835
+ if (!provider)
29836
+ throw new ProviderNotFoundError();
29837
+ if (!displayUri) {
29838
+ displayUri = this.onDisplayUri;
29839
+ provider.on("display_uri", displayUri);
29840
+ }
29841
+ if (!provider.session) {
29842
+ await provider.connect({
29843
+ ...("pairingTopic" in params
29844
+ ? { pairingTopic: params.pairingTopic }
29845
+ : {}),
29846
+ });
29847
+ }
29848
+ const accounts = (await provider.enable()).map((x) => getAddress(x));
29849
+ const currentChainId = await this.getChainId();
29850
+ if (displayUri) {
29851
+ provider.removeListener("display_uri", displayUri);
29852
+ displayUri = undefined;
29853
+ }
29854
+ if (connect) {
29855
+ provider.removeListener("connect", connect);
29856
+ connect = undefined;
29857
+ }
29858
+ if (!disconnect) {
29859
+ disconnect = this.onDisconnect.bind(this);
29860
+ provider.on("disconnect", disconnect);
29861
+ }
29862
+ if (!sessionDelete) {
29863
+ sessionDelete = this.onSessionDelete.bind(this);
29864
+ provider.on("session_delete", sessionDelete);
29865
+ }
29866
+ return { accounts, chainId: currentChainId };
29867
+ }
29868
+ catch (error) {
29869
+ if (/(user rejected|connection request reset)/i.test(error?.message)) {
29870
+ throw new UserRejectedRequestError(error);
29871
+ }
29872
+ throw error;
29873
+ }
29874
+ },
29875
+ async disconnect() {
29876
+ const provider = await this.getProvider();
29877
+ try {
29878
+ await provider?.disconnect();
29879
+ }
29880
+ catch (error) {
29881
+ if (!/No matching key/i.test(error.message))
29882
+ throw error;
29883
+ }
29884
+ finally {
29885
+ if (disconnect) {
29886
+ provider?.removeListener("disconnect", disconnect);
29887
+ disconnect = undefined;
29888
+ }
29889
+ if (!connect) {
29890
+ connect = this.onConnect.bind(this);
29891
+ provider?.on("connect", connect);
29892
+ }
29893
+ if (sessionDelete) {
29894
+ provider?.removeListener("session_delete", sessionDelete);
29895
+ sessionDelete = undefined;
29896
+ }
29897
+ }
29898
+ },
29899
+ async getAccounts() {
29900
+ const provider = await this.getProvider();
29901
+ return provider.accounts.map((x) => getAddress(x));
29902
+ },
29903
+ async getProvider() {
29904
+ async function initProvider() {
29905
+ const optionalChains = config.chains.map((x) => x.id);
29906
+ if (!optionalChains.length)
29907
+ return;
29908
+ const { EthereumProvider } = await import('./index.es-CczeKjuj.js');
29909
+ const rawProvider = await EthereumProvider.init({
29910
+ ...restParameters,
29911
+ disableProviderPing: true,
29912
+ optionalChains,
29913
+ projectId: restParameters.projectId,
29914
+ rpcMap: Object.fromEntries(config.chains.map((chain) => {
29915
+ const [url] = extractRpcUrls({
29916
+ chain,
29917
+ transports: config.transports,
29918
+ });
29919
+ return [chain.id, url];
29920
+ })),
29921
+ showQrModal: false,
29922
+ // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
29923
+ // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
29924
+ customStoragePrefix: "squid-hedera",
29925
+ });
29926
+ const proxiedProvider = new Proxy(rawProvider, {
29927
+ get(target, prop, receiver) {
29928
+ if (prop === "request") {
29929
+ return async (args) => {
29930
+ const signingMethods = [
29931
+ "eth_sendTransaction",
29932
+ "eth_signTransaction",
29933
+ ];
29934
+ if (signingMethods.includes(args.method)) {
29935
+ try {
29936
+ HederaExtensionHelper.extensionOpen(extension.id);
29937
+ }
29938
+ catch { }
29939
+ }
29940
+ // forward request to original provider
29941
+ return target.request(args);
29942
+ };
29943
+ }
29944
+ // forward all other properties/methods
29945
+ return Reflect.get(target, prop, receiver);
29946
+ },
29947
+ });
29948
+ return proxiedProvider;
29949
+ }
29950
+ if (!provider_) {
29951
+ if (!providerPromise)
29952
+ providerPromise = initProvider();
29953
+ provider_ = await providerPromise;
29954
+ provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
29955
+ }
29956
+ return provider_;
29957
+ },
29958
+ async getChainId() {
29959
+ const provider = await this.getProvider();
29960
+ return provider.chainId;
29961
+ },
29962
+ async isAuthorized() {
29963
+ try {
29964
+ const accounts = await this.getAccounts();
29965
+ return accounts.length > 0;
29966
+ }
29967
+ catch {
29968
+ return false;
29969
+ }
29970
+ },
29971
+ onAccountsChanged(accounts) {
29972
+ if (accounts.length === 0)
29973
+ this.onDisconnect();
29974
+ else
29975
+ config.emitter.emit("change", {
29976
+ accounts: accounts.map((x) => getAddress(x)),
29977
+ });
29978
+ },
29979
+ onChainChanged(chain) {
29980
+ const chainId = Number(chain);
29981
+ config.emitter.emit("change", { chainId });
29982
+ },
29983
+ async onConnect(connectInfo) {
29984
+ const chainId = Number(connectInfo.chainId);
29985
+ const accounts = await this.getAccounts();
29986
+ config.emitter.emit("connect", { accounts, chainId });
29987
+ },
29988
+ async onDisconnect() {
29989
+ config.emitter.emit("disconnect");
29990
+ const provider = await this.getProvider();
29991
+ if (disconnect) {
29992
+ provider.removeListener("disconnect", disconnect);
29993
+ disconnect = undefined;
29994
+ }
29995
+ if (sessionDelete) {
29996
+ provider.removeListener("session_delete", sessionDelete);
29997
+ sessionDelete = undefined;
29998
+ }
29999
+ if (!connect) {
30000
+ connect = this.onConnect.bind(this);
30001
+ provider.on("connect", connect);
30002
+ }
30003
+ },
30004
+ onDisplayUri(uri) {
30005
+ config.emitter.emit("message", { type: "display_uri", data: uri });
30006
+ HederaExtensionHelper.extensionConnect(extension.id, uri);
30007
+ },
30008
+ onSessionDelete() {
30009
+ this.onDisconnect();
30010
+ },
30011
+ }));
30012
+ }
30013
+
30014
+ const createWagmiConfig = (squidChains, hederaExtensions) => {
30015
+ const filteredEvmChains = squidChains.filter((chain) => chain.chainType === ChainType.EVM);
30016
+ if (filteredEvmChains.length === 0) {
30017
+ throw new Error("At least one chain is required");
30018
+ }
30019
+ const wagmiChains = filteredEvmChains.map((chain) => {
30020
+ return defineChain({
30021
+ id: Number(chain.chainId),
30022
+ name: chain.networkName,
30023
+ nativeCurrency: {
30024
+ name: chain.nativeCurrency.name,
30025
+ symbol: chain.nativeCurrency.symbol,
30026
+ decimals: chain.nativeCurrency.decimals,
30027
+ },
30028
+ rpcUrls: {
30029
+ public: {
30030
+ http: [chain.rpc],
30031
+ },
30032
+ default: {
30033
+ http: [chain.rpc],
30034
+ },
30035
+ },
30036
+ });
30037
+ });
30038
+ const wcMetadata = {
30039
+ url: SQUID_METADATA.url,
30040
+ name: SQUID_METADATA.name,
30041
+ icons: [SQUID_METADATA.icon],
30042
+ description: SQUID_METADATA.description,
30043
+ };
30044
+ return createConfig({
30045
+ chains: wagmiChains,
30046
+ transports: Object.fromEntries(wagmiChains.map((chain) => [
30047
+ chain.id,
30048
+ http(chain.rpcUrls.public.http[0] ?? ""),
30049
+ ])),
30050
+ connectors: [
30051
+ injected(),
30052
+ safe({
30053
+ allowedDomains: [/app.safe.global$/],
30054
+ }),
30055
+ metaMask({
30056
+ dappMetadata: {
30057
+ name: SQUID_METADATA.name,
30058
+ url: SQUID_METADATA.url,
30059
+ iconUrl: SQUID_METADATA.icon,
30060
+ },
30061
+ }),
30062
+ coinbaseWallet({
30063
+ appName: SQUID_METADATA.name,
30064
+ appLogoUrl: SQUID_METADATA.icon,
30065
+ }),
30066
+ walletConnect({
30067
+ projectId: WALLETCONNECT_PROJECT_ID,
30068
+ metadata: wcMetadata,
30069
+ }),
30070
+ ...hederaExtensions.map((extension) => hederaWalletConnect({
30071
+ projectId: WALLETCONNECT_PROJECT_ID,
30072
+ metadata: wcMetadata,
30073
+ extension,
30074
+ })),
30075
+ ],
30076
+ });
30077
+ };
30078
+ // Taken from wagmi docs
30079
+ // https://wagmi.sh/react/guides/ethers
30080
+ function clientToSigner(client) {
30081
+ const { account, chain, transport } = client;
30082
+ if (!account || !chain || !transport) {
30083
+ return undefined;
30084
+ }
30085
+ const network = {
30086
+ chainId: chain.id,
30087
+ name: chain.name,
30088
+ ensAddress: chain.contracts?.ensRegistry?.address,
30089
+ };
30090
+ const provider = new BrowserProvider(transport, network);
30091
+ const signer = new JsonRpcSigner(provider, account.address);
30092
+ return signer;
30093
+ }
30094
+
30095
+ function useEvmSigner({ chainId }) {
30096
+ const { connector } = useAccount();
30097
+ const { data: client } = useWalletClient({ chainId, connector });
30098
+ const signer = useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
30099
+ return { signer };
30100
+ }
30101
+ const useSigner = ({ chain }) => {
30102
+ const evmChainId = chain?.chainType === ChainType.EVM ? Number(chain.chainId) : undefined;
30103
+ // EVM and Cosmos need a different signer for each chain
30104
+ // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
30105
+ const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
30106
+ const { signer: cosmosSigner } = useCosmosSigner({ chain });
30107
+ const { signer: solanaSigner } = useSolanaContext();
30108
+ const { signer: bitcoinSigner } = useBitcoinContext();
30109
+ const { signer: suiSigner } = useSuiContext();
30110
+ const { signer: xrplSigner } = useXrplContext();
30111
+ const { signer: stellarSigner } = useStellarContext();
30112
+ const isEvmSignerReady = !!evmSigner;
30113
+ const isSolanaSignerReady = !!solanaSigner;
30114
+ const isCosmosSignerReady = !!cosmosSigner;
30115
+ const isBitcoinSignerReady = !!bitcoinSigner;
30116
+ const isSuiSignerReady = !!suiSigner;
30117
+ const isXrplSignerReady = !!xrplSigner;
30118
+ const isStellarSignerReady = !!stellarSigner;
30119
+ const isSignerReady = useMemo(() => {
30120
+ if (!chain?.chainType)
30121
+ return false;
30122
+ switch (chain.chainType) {
30123
+ case ChainType.EVM:
30124
+ return isEvmSignerReady;
30170
30125
  case ChainType.COSMOS:
30171
- return isCosmosLoading;
30126
+ return isCosmosSignerReady;
30172
30127
  case ChainType.BTC:
30173
- return isBitcoinLoading;
30128
+ return isBitcoinSignerReady;
30174
30129
  case ChainType.SOLANA:
30175
- return isSolanaLoading;
30130
+ return isSolanaSignerReady;
30176
30131
  case ChainType.SUI:
30177
- return isSuiLoading;
30132
+ return isSuiSignerReady;
30178
30133
  case ChainType.XRPL:
30179
- return isXrpLoading;
30134
+ return isXrplSignerReady;
30180
30135
  case ChainType.STELLAR:
30181
- return isStellarLoading;
30136
+ return isStellarSignerReady;
30182
30137
  }
30183
30138
  }, [
30184
30139
  chain?.chainType,
30185
- isEvmLoading,
30186
- isCosmosLoading,
30187
- isBitcoinLoading,
30188
- isSolanaLoading,
30189
- isSuiLoading,
30190
- isXrpLoading,
30191
- isStellarLoading,
30140
+ isEvmSignerReady,
30141
+ isCosmosSignerReady,
30142
+ isBitcoinSignerReady,
30143
+ isSolanaSignerReady,
30144
+ isSuiSignerReady,
30145
+ isXrplSignerReady,
30146
+ isStellarSignerReady,
30192
30147
  ]);
30193
- return { nativeBalance, nativeBalanceFormatted, isLoading };
30148
+ return {
30149
+ isSignerReady,
30150
+ evmSigner,
30151
+ cosmosSigner,
30152
+ bitcoinSigner,
30153
+ solanaSigner,
30154
+ suiSigner,
30155
+ xrplSigner,
30156
+ stellarSigner,
30157
+ };
30194
30158
  };
30195
30159
 
30196
- function useHederaAccountActivation({ address, chain, token }) {
30197
- const { balance: destNativeEvmBalance } = useEvmNativeBalance({
30198
- chain,
30199
- address,
30160
+ function useHederaTokenAssociations({ address, chain, token }) {
30161
+ const publicClient = usePublicClient({
30162
+ chainId: Number(CHAIN_IDS.HEDERA),
30163
+ });
30164
+ const { evmSigner } = useSigner({ chain });
30165
+ const queryClient = useQueryClient();
30166
+ /**
30167
+ * Creates a token association transaction where the destination account authorizes to receive the given token
30168
+ */
30169
+ const associateToken = useMutation({
30170
+ mutationFn: async () => {
30171
+ try {
30172
+ if (!evmSigner) {
30173
+ throw new Error("EVM signer not found");
30174
+ }
30175
+ if (chain?.chainId !== CHAIN_IDS.HEDERA ||
30176
+ token?.chainId !== CHAIN_IDS.HEDERA) {
30177
+ throw new Error("Chain and token to associate must be on Hedera");
30178
+ }
30179
+ const tokenAddress = parseEvmAddress(token.address);
30180
+ if (!tokenAddress) {
30181
+ throw new Error("Invalid token address");
30182
+ }
30183
+ const userAddress = parseEvmAddress(address);
30184
+ if (!userAddress) {
30185
+ throw new Error("Invalid user address");
30186
+ }
30187
+ const encodedData = encodeFunctionData({
30188
+ abi: hrc20,
30189
+ functionName: "associate",
30190
+ args: [],
30191
+ });
30192
+ const txRes = await evmSigner.sendTransaction({
30193
+ data: encodedData,
30194
+ to: tokenAddress,
30195
+ chainId: chain.chainId,
30196
+ });
30197
+ const receipt = await txRes.wait();
30198
+ if (receipt?.status !== 1) {
30199
+ throw new Error(`Transaction failed with status: ${receipt?.status}`);
30200
+ }
30201
+ return true;
30202
+ }
30203
+ catch (error) {
30204
+ console.error("Error associating Hedera token:", error);
30205
+ return false;
30206
+ }
30207
+ },
30208
+ async onSuccess() {
30209
+ queryClient.refetchQueries({
30210
+ queryKey: getPrefixKey(QueryKeys.IsHederaTokenAssociated),
30211
+ });
30212
+ },
30213
+ });
30214
+ /**
30215
+ * Checks if the destination account has associated the given token.
30216
+ *
30217
+ * Hedera requires accounts to associate a token before being able to receive it.
30218
+ *
30219
+ * Accounts which have max. associations set to -1 can receive any token without previous association
30220
+ */
30221
+ const isTokenAssociated = useQuery({
30222
+ queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
30223
+ queryFn: async () => {
30224
+ if (token?.chainId !== CHAIN_IDS.HEDERA) {
30225
+ return true;
30226
+ }
30227
+ // The native HBAR token doesn't need an association
30228
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
30229
+ return true;
30230
+ }
30231
+ if (!chain || !address || !publicClient) {
30232
+ throw new Error("Missing required parameters");
30233
+ }
30234
+ // TODO: update types
30235
+ const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
30236
+ if (!apiUrl) {
30237
+ throw new Error("Missing Hedera mirror node URL in chain config");
30238
+ }
30239
+ const hederaApiClient = new HederaApiClient(apiUrl);
30240
+ return hederaApiClient.isTokenAssociated({
30241
+ address,
30242
+ token,
30243
+ });
30244
+ },
30245
+ enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
30200
30246
  });
30201
- const isHederaAccountActivated = useMemo(() => {
30202
- if (token?.chainId !== CHAIN_IDS.HEDERA)
30203
- return true;
30204
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
30205
- return true;
30206
- return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
30207
- }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
30208
30247
  return {
30209
- isHederaAccountActivated,
30248
+ isTokenAssociated,
30249
+ associateToken,
30250
+ };
30251
+ }
30252
+
30253
+ const useKeyboardNavigation = ({ onEscape }) => {
30254
+ const onKeyDown = useCallback((event) => {
30255
+ if (event.key === "Escape") {
30256
+ onEscape?.();
30257
+ return;
30258
+ }
30259
+ }, [onEscape]);
30260
+ useEffect(() => {
30261
+ document.addEventListener("keydown", onKeyDown, false);
30262
+ return () => {
30263
+ document.removeEventListener("keydown", onKeyDown, false);
30264
+ };
30265
+ }, [onKeyDown]);
30266
+ };
30267
+
30268
+ const useSquidQueryClient = () => {
30269
+ const queryClient = useQueryClient();
30270
+ const invalidateQueries = (key) => {
30271
+ const prefixKey = getPrefixKey(key);
30272
+ queryClient.invalidateQueries(prefixKey);
30273
+ };
30274
+ const refetchQueries = (key) => {
30275
+ const prefixKey = getPrefixKey(key);
30276
+ queryClient.refetchQueries(prefixKey);
30277
+ };
30278
+ const invalidateAndRefetchQueries = (key) => {
30279
+ invalidateQueries(key);
30280
+ refetchQueries(key);
30281
+ };
30282
+ return {
30283
+ invalidateQueries,
30284
+ refetchQueries,
30285
+ invalidateAndRefetchQueries,
30286
+ };
30287
+ };
30288
+
30289
+ class StellarApiClient {
30290
+ apiUrl;
30291
+ constructor(apiUrl) {
30292
+ this.apiUrl = apiUrl;
30293
+ }
30294
+ async getBaseReserve() {
30295
+ const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
30296
+ if (!response.ok) {
30297
+ throw new Error(`Failed to fetch ledgers: ${response.status}`);
30298
+ }
30299
+ const ledgers = await response.json();
30300
+ const latestLedger = ledgers?._embedded.records?.[0];
30301
+ if (latestLedger?.base_reserve_in_stroops == null) {
30302
+ throw new Error("Invalid ledger data");
30303
+ }
30304
+ const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
30305
+ return baseReserveBn;
30306
+ }
30307
+ async getTrustLines(userAddress) {
30308
+ const response = await fetch(`${this.apiUrl}/accounts/${userAddress}`);
30309
+ if (!response.ok) {
30310
+ throw new Error(`Failed to fetch account data: ${response.statusText}`);
30311
+ }
30312
+ const data = await response.json();
30313
+ if (!Array.isArray(data?.balances)) {
30314
+ throw new Error("Invalid response from Horizon API");
30315
+ }
30316
+ const assets = data.balances.filter(isValidHorizonAsset);
30317
+ return assets.filter(isValidIssuedAsset);
30318
+ }
30319
+ async getTrustLine(address, asset) {
30320
+ const trustLines = await this.getTrustLines(address);
30321
+ const trustLine = trustLines.find((line) => line.asset_code === asset.code && line.asset_issuer === asset.issuer);
30322
+ return trustLine ?? null;
30323
+ }
30324
+ }
30325
+
30326
+ const DEFAULT_REFETCH_INTERVAL$2 = 20_000;
30327
+ function useStellarAccountActivation({ address, chain, token, }) {
30328
+ /**
30329
+ * Checks if the destination account exists on the Stellar network
30330
+ * Stellar accounts need to have a minimum balance before they can receive payments
30331
+ */
30332
+ const accountActivatedInfo = useQuery({
30333
+ queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
30334
+ queryFn: async () => {
30335
+ if (chain?.chainType !== ChainType.STELLAR ||
30336
+ token?.type !== ChainType.STELLAR) {
30337
+ return null;
30338
+ }
30339
+ if (!address) {
30340
+ throw new Error("Destination address is required");
30341
+ }
30342
+ const horizonApiUrl = getStellarHorizonApiUrl(chain);
30343
+ if (!horizonApiUrl) {
30344
+ throw new Error("Invalid Horizon API URL");
30345
+ }
30346
+ const stellarApiClient = new StellarApiClient(horizonApiUrl);
30347
+ const reserveBase = await stellarApiClient.getBaseReserve();
30348
+ // Stellar accounts require two base reserves to be activated
30349
+ // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
30350
+ const accountReserveBase = reserveBase * BigInt(2);
30351
+ const stellarRpcClient = await getClient(chain);
30352
+ const isActivated = await stellarRpcClient.isAccountActivated(address);
30353
+ return {
30354
+ isActivated,
30355
+ reserveBaseBn: accountReserveBase,
30356
+ };
30357
+ },
30358
+ enabled: !!address && chain?.chainType === ChainType.STELLAR,
30359
+ refetchInterval: DEFAULT_REFETCH_INTERVAL$2,
30360
+ refetchOnWindowFocus: true,
30361
+ });
30362
+ return {
30363
+ accountActivatedInfo,
30364
+ };
30365
+ }
30366
+
30367
+ /**
30368
+ * Maximum asset amount on Stellar
30369
+ * @see https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/assets#amount-precision
30370
+ */
30371
+ const MAX_ASSET_AMOUNT = "922337203685.4775807";
30372
+ const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
30373
+ function useStellarTrustLine({ address, chain, token, amount }) {
30374
+ const { stellarSigner } = useSigner({ chain });
30375
+ const queryClient = useQueryClient();
30376
+ /**
30377
+ * Retrieves the destination account's trust line data for the given token
30378
+ */
30379
+ const trustLineQuery = useQuery({
30380
+ queryKey: keys().stellarTrustLine(token?.address, chain?.chainId, address),
30381
+ queryFn: async () => {
30382
+ if (chain?.chainType !== ChainType.STELLAR ||
30383
+ token?.type !== ChainType.STELLAR) {
30384
+ return null;
30385
+ }
30386
+ if (!address || !isStellarAddressValid(address)) {
30387
+ return null;
30388
+ }
30389
+ // Only issued tokens require trust lines
30390
+ // Other token types like contract tokens don't need trust lines
30391
+ if (!isStellarIssuedToken(token)) {
30392
+ return null;
30393
+ }
30394
+ const asset = getStellarTrustLineAsset(token);
30395
+ if (!asset) {
30396
+ throw new Error("Invalid asset");
30397
+ }
30398
+ const horizonApiUrl = getStellarHorizonApiUrl(chain);
30399
+ if (!horizonApiUrl) {
30400
+ throw new Error("Invalid Horizon API URL");
30401
+ }
30402
+ const stellarApiClient = new StellarApiClient(horizonApiUrl);
30403
+ return stellarApiClient.getTrustLine(address, asset);
30404
+ },
30405
+ enabled: !!address &&
30406
+ chain?.chainType === ChainType.STELLAR &&
30407
+ token?.type === ChainType.STELLAR,
30408
+ refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
30409
+ });
30410
+ /**
30411
+ * Creates a trust line where the destination account authorizes to receive the given token
30412
+ */
30413
+ const createTrustLine = useMutation({
30414
+ mutationFn: async () => {
30415
+ try {
30416
+ if (!stellarSigner) {
30417
+ throw new Error("Stellar signer not found");
30418
+ }
30419
+ if (!address) {
30420
+ throw new Error("Destination address is required");
30421
+ }
30422
+ if (chain?.chainType !== ChainType.STELLAR ||
30423
+ token?.type !== ChainType.STELLAR) {
30424
+ throw new Error("Chain and token to approve must be a Stellar token");
30425
+ }
30426
+ if (!isStellarIssuedToken(token)) {
30427
+ throw new Error("Token must be a Stellar issued token");
30428
+ }
30429
+ const assetInfo = getStellarTrustLineAsset(token);
30430
+ if (!assetInfo) {
30431
+ throw new Error("Invalid asset");
30432
+ }
30433
+ const network = getStellarNetwork(chain.chainId);
30434
+ if (network == null) {
30435
+ throw new Error(`Stellar network not found for chain ${chain.chainId}`);
30436
+ }
30437
+ const client = await getClient(chain);
30438
+ const account = await client.getAccount(address);
30439
+ const asset = new Asset(assetInfo.code, assetInfo.issuer);
30440
+ const changeTrustOperation = Operation.changeTrust({
30441
+ asset,
30442
+ limit: MAX_ASSET_AMOUNT,
30443
+ });
30444
+ const builtTransaction = new TransactionBuilder(account, {
30445
+ fee: (BigInt(BASE_FEE) * BigInt(2)).toString(),
30446
+ networkPassphrase: network,
30447
+ })
30448
+ .addOperation(changeTrustOperation)
30449
+ .setTimeout(300)
30450
+ .build();
30451
+ const { signedTxXdr } = await stellarSigner.signTransaction(builtTransaction.toXDR(), {
30452
+ networkPassphrase: network,
30453
+ });
30454
+ const signedTransaction = new Transaction$1(signedTxXdr, network);
30455
+ const sentTransaction = await client.sendTransaction(signedTransaction);
30456
+ const txStatus = await client.waitForTransaction(sentTransaction.hash, {
30457
+ interval: 1_000,
30458
+ });
30459
+ if (txStatus !== rpc.Api.GetTransactionStatus.SUCCESS) {
30460
+ throw new Error(`Transaction failed with status: ${txStatus}`);
30461
+ }
30462
+ return true;
30463
+ }
30464
+ catch (error) {
30465
+ console.error("Error creating trust line:", error);
30466
+ return false;
30467
+ }
30468
+ },
30469
+ async onSuccess() {
30470
+ queryClient.invalidateQueries({
30471
+ queryKey: getPrefixKey(QueryKeys.StellarTrustLine),
30472
+ });
30473
+ },
30474
+ });
30475
+ /**
30476
+ * Checks if the destination account has created a trust line to receive the given token.
30477
+ */
30478
+ const isTrustLineApproved = useQuery({
30479
+ queryKey: keys().isStellarTrustLineApproved(address, token?.chainId, token?.type, token?.address, trustLineQuery.data?.limit, amount),
30480
+ queryFn: async () => {
30481
+ if (token?.type !== ChainType.STELLAR) {
30482
+ return true;
30483
+ }
30484
+ // The native Stellar token doesn't need a trust line
30485
+ if (token.address.toLowerCase() === nativeStellarTokenAddress.toLowerCase()) {
30486
+ return true;
30487
+ }
30488
+ if (!amount) {
30489
+ throw new Error("Amount is required");
30490
+ }
30491
+ const limitBn = BigNumber$1(trustLineQuery.data?.limit || "0");
30492
+ const balanceBn = BigNumber$1(trustLineQuery.data?.balance || "0");
30493
+ const availableAllowanceBn = limitBn.minus(balanceBn);
30494
+ const amountBn = BigNumber$1(formatBNToReadable(amount, token.decimals));
30495
+ return availableAllowanceBn.gte(amountBn);
30496
+ },
30497
+ enabled: !!address &&
30498
+ !!amount &&
30499
+ !trustLineQuery?.isLoading &&
30500
+ trustLineQuery?.isFetched &&
30501
+ token?.type === ChainType.STELLAR,
30502
+ });
30503
+ return {
30504
+ createTrustLine,
30505
+ trustLineQuery,
30506
+ isTrustLineApproved,
30210
30507
  };
30211
30508
  }
30212
30509
 
@@ -30734,6 +31031,143 @@ const useSingleTokenPrice = (tokenData) => {
30734
31031
  return { tokenPrice, getUSDValue };
30735
31032
  };
30736
31033
 
31034
+ const TEMPO_FEE_MANAGER_ADDRESS = "0xfeec000000000000000000000000000000000000";
31035
+ const feeManagerAbi = [
31036
+ {
31037
+ name: "userTokens",
31038
+ type: "function",
31039
+ stateMutability: "view",
31040
+ inputs: [{ type: "address", name: "user" }],
31041
+ outputs: [{ type: "address" }],
31042
+ },
31043
+ ];
31044
+ const isTempoChain = (chainId) => chainId === CHAIN_IDS.TEMPO;
31045
+ /**
31046
+ * Convert a fee amount from virtual USD (18 decimals, attodollars)
31047
+ * to 6-decimal TIP-20 stablecoin units (microdollars).
31048
+ * All TIP-20 tokens on Tempo have 6 decimals.
31049
+ */
31050
+ const convertTempoFeeToStablecoinUnits = (feeIn18Dec) => feeIn18Dec / BigInt(10 ** 12);
31051
+
31052
+ const BALANCE_QUERY_STALE_TIME = 10_000;
31053
+ /**
31054
+ * Returns raw on-chain gas token data for Tempo chains, or null when the source
31055
+ * chain is not Tempo.
31056
+ */
31057
+ const useTempoFeeCheck = ({ fromChain, fromToken, }) => {
31058
+ const { connectedAddresses } = useWallet();
31059
+ const evmAddress = parseEvmAddress(connectedAddresses[ChainType.EVM]);
31060
+ const isTempo = isTempoChain(fromChain?.chainId);
31061
+ const chainId = Number(fromChain?.chainId);
31062
+ // Read account-level gas token preference from FeeManager precompile
31063
+ // See docs: https://docs.tempo.xyz/protocol/fees/spec-fee#account-level
31064
+ const { data: accountGasTokenAddress } = useReadContract({
31065
+ address: TEMPO_FEE_MANAGER_ADDRESS,
31066
+ abi: feeManagerAbi,
31067
+ functionName: "userTokens",
31068
+ args: evmAddress ? [evmAddress] : undefined,
31069
+ chainId,
31070
+ query: {
31071
+ enabled: isTempo && !!evmAddress,
31072
+ staleTime: 30_000,
31073
+ },
31074
+ });
31075
+ const hasAccountFeePreference = !!accountGasTokenAddress &&
31076
+ // By default, the zero address is returned when the user has no preference set
31077
+ zeroAddress$1.toLowerCase() !== accountGasTokenAddress.toLowerCase();
31078
+ // Fetch balance of the account-preferred gas token (if set)
31079
+ const { data: accountGasTokenBalance } = useBalance({
31080
+ address: evmAddress,
31081
+ token: hasAccountFeePreference ? accountGasTokenAddress : undefined,
31082
+ chainId,
31083
+ query: {
31084
+ enabled: isTempo && hasAccountFeePreference && !!evmAddress,
31085
+ staleTime: BALANCE_QUERY_STALE_TIME,
31086
+ },
31087
+ });
31088
+ const fromTokenAddress = parseEvmAddress(fromToken?.address);
31089
+ const { data: fromTokenBalance } = useBalance({
31090
+ address: evmAddress,
31091
+ token: fromTokenAddress,
31092
+ chainId,
31093
+ query: {
31094
+ enabled: isTempo && !!evmAddress,
31095
+ staleTime: BALANCE_QUERY_STALE_TIME,
31096
+ },
31097
+ });
31098
+ if (!isTempo)
31099
+ return null;
31100
+ if (hasAccountFeePreference) {
31101
+ if (!accountGasTokenAddress || !accountGasTokenBalance)
31102
+ return null;
31103
+ return {
31104
+ gasTokenAddress: accountGasTokenAddress,
31105
+ gasBalance: accountGasTokenBalance.value,
31106
+ };
31107
+ }
31108
+ if (!fromTokenAddress || !fromTokenBalance)
31109
+ return null;
31110
+ return {
31111
+ gasTokenAddress: fromTokenAddress,
31112
+ gasBalance: fromTokenBalance.value,
31113
+ };
31114
+ };
31115
+
31116
+ function useSourceChainGasToken({ fromChain, fromToken, }) {
31117
+ const { nativeToken } = useNativeTokenForChain(fromChain);
31118
+ const { evmTokens } = useSquidTokens();
31119
+ const { nativeBalance } = useNativeBalance(fromChain);
31120
+ const tempoFeeData = useTempoFeeCheck({ fromChain, fromToken });
31121
+ const chainFeeParams = useMemo(() => {
31122
+ if (fromToken?.address == null ||
31123
+ fromChain?.chainId == null ||
31124
+ nativeBalance?.value == null) {
31125
+ return null;
31126
+ }
31127
+ if (isTempoChain(fromChain.chainId)) {
31128
+ if (!tempoFeeData)
31129
+ return null;
31130
+ const fromTokenPaysNetworkFee = tempoFeeData.gasTokenAddress.toLowerCase() ===
31131
+ fromToken.address.toLowerCase();
31132
+ return {
31133
+ fromTokenPaysNetworkFee,
31134
+ gasTokenBalanceWei: tempoFeeData.gasBalance,
31135
+ normalizeFee: convertTempoFeeToStablecoinUnits,
31136
+ };
31137
+ }
31138
+ return {
31139
+ fromTokenPaysNetworkFee: fromToken.address.toLowerCase() ===
31140
+ chainTypeToNativeTokenAddressMap[fromToken.type].toLowerCase(),
31141
+ gasTokenBalanceWei: nativeBalance.value,
31142
+ normalizeFee: (fee) => fee,
31143
+ };
31144
+ }, [
31145
+ fromToken?.address,
31146
+ fromToken?.type,
31147
+ fromChain?.chainId,
31148
+ nativeBalance?.value,
31149
+ tempoFeeData,
31150
+ ]);
31151
+ const gasToken = useMemo(() => {
31152
+ if (fromChain?.chainId == null || fromToken?.address == null)
31153
+ return undefined;
31154
+ if (isTempoChain(fromChain.chainId)) {
31155
+ if (!tempoFeeData)
31156
+ return undefined;
31157
+ return evmTokens.find((t) => t.chainId === fromChain.chainId &&
31158
+ t.address.toLowerCase() === tempoFeeData.gasTokenAddress.toLowerCase());
31159
+ }
31160
+ return nativeToken;
31161
+ }, [
31162
+ fromChain?.chainId,
31163
+ fromToken?.address,
31164
+ nativeToken,
31165
+ tempoFeeData,
31166
+ evmTokens,
31167
+ ]);
31168
+ return { gasToken, chainFeeParams };
31169
+ }
31170
+
30737
31171
  const MAX_COINGECKO_QUERY_TOKENS = 100;
30738
31172
  const fetchHistoricalData = async (coingeckoId, timeFrame) => {
30739
31173
  const now = Math.floor(Date.now() / 1000);
@@ -30884,24 +31318,23 @@ const calculateTotalNativeFees = ({ expressFeeCost, firstFeeCost, firstGasCost,
30884
31318
  (sameTokenBetweenFees
30885
31319
  ? BigInt(firstFeeCost?.amount ?? "0") + BigInt(firstGasCost?.amount ?? "0")
30886
31320
  : BigInt(firstGasCost?.amount ?? "0"));
30887
- const isFromBalanceEnoughToSwap = ({ isFromTokenNative, fromAmount, totalFeesInNativeTokenPlusRatio, nativeTokenBalanceFromChainWei, }) => {
30888
- const fromAmountBigInt = BigInt(fromAmount ?? "0");
30889
- const totalFeesInNativeTokenPlusRatioBigInt = totalFeesInNativeTokenPlusRatio;
30890
- const nativeTokenBalanceFromChainWeiBigInt = nativeTokenBalanceFromChainWei;
30891
- return isFromTokenNative
30892
- ? fromAmountBigInt + totalFeesInNativeTokenPlusRatioBigInt <=
30893
- nativeTokenBalanceFromChainWeiBigInt
30894
- : totalFeesInNativeTokenPlusRatioBigInt <=
30895
- nativeTokenBalanceFromChainWeiBigInt;
30896
- };
30897
- const calculateMinAmountValueWarnMsg = ({ isFromTokenNative, nativeTokenBalanceFromChainWei, sourceChainNativeTokenDecimals, totalFeesInNativeTokenPlusRatio, }) => isFromTokenNative
30898
- ? (() => {
30899
- const minAmount = nativeTokenBalanceFromChainWei - totalFeesInNativeTokenPlusRatio;
30900
- return minAmount > BigInt(0)
30901
- ? formatBNToReadable(minAmount, sourceChainNativeTokenDecimals)
30902
- : "0";
30903
- })()
30904
- : undefined;
31321
+ const isGasBalanceEnough = ({ fromAmount, networkFeesWei, chainFeeParams, }) => {
31322
+ if (!chainFeeParams)
31323
+ return false;
31324
+ if (chainFeeParams.fromTokenPaysNetworkFee) {
31325
+ return (BigInt(fromAmount ?? "0") + networkFeesWei <=
31326
+ chainFeeParams.gasTokenBalanceWei);
31327
+ }
31328
+ return networkFeesWei <= chainFeeParams.gasTokenBalanceWei;
31329
+ };
31330
+ const calculateMinAmountValueWarnMsg = ({ networkFeesWei, chainFeeParams, fromTokenDecimals, }) => {
31331
+ if (!chainFeeParams?.fromTokenPaysNetworkFee)
31332
+ return undefined;
31333
+ const minAmount = chainFeeParams.gasTokenBalanceWei - networkFeesWei;
31334
+ if (minAmount <= BigInt(0))
31335
+ return undefined;
31336
+ return formatBNToReadable(minAmount, fromTokenDecimals);
31337
+ };
30905
31338
  /**
30906
31339
  * Calculates the estimated duration of a route
30907
31340
  *
@@ -30940,12 +31373,10 @@ const formatEstimatedRouteDuration = ({ estimatedRouteDuration, isSingleChainRou
30940
31373
  * @returns {Object} An object containing various estimate results and calculations, including token information,
30941
31374
  * amounts, fees, gas costs, and other relevant data for the transaction.
30942
31375
  */
30943
- const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, nativeTokenBalanceFromChainWei, }) => {
31376
+ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, chainFeeParams, gasToken, }) => {
30944
31377
  const fromToken = findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken);
30945
31378
  const fromAmount = squidRoute?.estimate?.fromAmount;
30946
31379
  const fromAmountFormatted = formatAmount(fromAmount, fromToken?.decimals);
30947
- const sourceChainNativeToken = findNativeToken(tokens, fromChain);
30948
- const destChainNativeToken = findNativeToken(tokens, toChain);
30949
31380
  const toAmountUSD = squidRoute?.estimate?.toAmountUSD;
30950
31381
  const exchangeRate = squidRoute?.estimate.exchangeRate ?? "0";
30951
31382
  const toAmountMinUSD = squidRoute?.estimate.toAmountMinUSD ?? "0";
@@ -30964,10 +31395,6 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
30964
31395
  const expectedGasRefundCostUSD = convertTokenAmountToUSD(formatBNToReadable(expectedGasRefundCost, firstFeeCost?.token.decimals ?? 18), firstFeeCost?.token.usdPrice ?? "0");
30965
31396
  const sameTokenBetweenFees = firstFeeCost?.token.address === firstGasCost?.token.address &&
30966
31397
  firstFeeCost?.token.chainId === firstGasCost?.token.chainId;
30967
- const isFromTokenNative =
30968
- // TODO: temporary fix, currently nativeCurrency.symbol is not always in uppercase
30969
- fromToken?.symbol.toUpperCase() ===
30970
- fromChain?.nativeCurrency.symbol.toUpperCase();
30971
31398
  const totalNativeFees = calculateTotalNativeFees({
30972
31399
  expressFeeCost,
30973
31400
  firstFeeCost,
@@ -30975,30 +31402,32 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
30975
31402
  sameTokenBetweenFees,
30976
31403
  });
30977
31404
  const totalFeesInNativeTokenPlusRatio = (totalNativeFees * BigInt(110)) / BigInt(100);
30978
- const fromBalanceEnoughToSwap = isFromBalanceEnoughToSwap({
30979
- isFromTokenNative,
31405
+ const networkFeesWei = chainFeeParams?.normalizeFee(totalFeesInNativeTokenPlusRatio) ?? BigInt(0);
31406
+ const gasBalanceEnough = isGasBalanceEnough({
31407
+ chainFeeParams,
30980
31408
  fromAmount,
30981
- fromTokenDecimals: fromToken?.decimals,
30982
- totalFeesInNativeTokenPlusRatio,
30983
- nativeTokenBalanceFromChainWei,
31409
+ networkFeesWei,
30984
31410
  });
30985
31411
  const minAmountValueWarnMsg = calculateMinAmountValueWarnMsg({
30986
- isFromTokenNative,
30987
- nativeTokenBalanceFromChainWei,
30988
- sourceChainNativeTokenDecimals: sourceChainNativeToken?.decimals,
30989
- totalFeesInNativeTokenPlusRatio,
31412
+ chainFeeParams,
31413
+ networkFeesWei,
31414
+ fromTokenDecimals: fromToken?.decimals,
30990
31415
  });
30991
31416
  const isSingleChainRoute = fromChain?.chainId === toChain?.chainId;
30992
31417
  const estimatedRouteDuration = formatEstimatedRouteDuration({
30993
31418
  estimatedRouteDuration: squidRoute?.estimate?.estimatedRouteDuration,
30994
31419
  isSingleChainRoute,
30995
31420
  });
31421
+ // native fees + fromAmount (if native)
31422
+ const totalGasBalanceNeeded = networkFeesWei +
31423
+ BigInt(chainFeeParams?.fromTokenPaysNetworkFee ? fromAmount ?? 0 : 0);
31424
+ const gasBalanceNeeded = gasToken
31425
+ ? formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals)
31426
+ : undefined;
30996
31427
  return {
30997
31428
  fromToken,
30998
31429
  fromAmount,
30999
31430
  fromAmountFormatted,
31000
- sourceChainNativeToken,
31001
- destChainNativeToken,
31002
31431
  toAmountUSD,
31003
31432
  exchangeRate,
31004
31433
  toAmountMinUSD,
@@ -31013,12 +31442,12 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31013
31442
  expectedGasRefundCost,
31014
31443
  expectedGasRefundCostUSD,
31015
31444
  sameTokenBetweenFees,
31016
- isFromTokenNative,
31445
+ fromTokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31017
31446
  totalNativeFees,
31018
- totalFeesInNativeTokenPlusRatio,
31019
- fromBalanceEnoughToSwap,
31447
+ gasBalanceEnough,
31020
31448
  minAmountValueWarnMsg,
31021
31449
  estimatedRouteDuration,
31450
+ gasBalanceNeeded,
31022
31451
  };
31023
31452
  };
31024
31453
  /**
@@ -31052,28 +31481,6 @@ const calculateTotalWithRefundEstimate = (allFeeCosts, expectedGasRefundCost, ge
31052
31481
  feeToken: firstFeeCost?.token,
31053
31482
  };
31054
31483
  };
31055
- /**
31056
- * Calculates the proposed gas amount for the destination chain.
31057
- *
31058
- * @param destChainNativeToken - The symbol of the native token for the destination chain.
31059
- * @returns An object containing the proposed gas amount value and the currency symbol.
31060
- */
31061
- const getProposedGasDestinationAmount = (destChainNativeToken) => {
31062
- const gasAmounts = {
31063
- GLMR: 5.289,
31064
- ETH: 0.0009,
31065
- AVAX: 0.115,
31066
- BNB: 0.00425,
31067
- FTM: 4.45,
31068
- CELO: 3.052,
31069
- KAVA: 2.339,
31070
- MATIC: 1.795,
31071
- };
31072
- return {
31073
- value: gasAmounts[destChainNativeToken ?? ""] ?? 0,
31074
- currency: destChainNativeToken,
31075
- };
31076
- };
31077
31484
 
31078
31485
  function useSendTransactionGas({ chain, token, from, }) {
31079
31486
  return useQuery({
@@ -31095,7 +31502,11 @@ function useSendTransactionGas({ chain, token, from, }) {
31095
31502
  // Some RPC providers require the sender to have enough balance, otherwise estimation reverts
31096
31503
  // so we'll try to use the user provided address when possible
31097
31504
  const sender = from || dummyAddress;
31098
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
31505
+ // Tempo has no native token, so `value` transfers are rejected by the EVM.
31506
+ // We can simulate an ERC20 transfer instead
31507
+ const supportsNativeTransfers = chain.chainId !== CHAIN_IDS.TEMPO;
31508
+ const isNativeToken = token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase();
31509
+ if (isNativeToken && supportsNativeTransfers) {
31099
31510
  const gas = await client.estimateGas({
31100
31511
  from: sender,
31101
31512
  to: dummyAddress,
@@ -31115,7 +31526,14 @@ function useSendTransactionGas({ chain, token, from, }) {
31115
31526
  data,
31116
31527
  chainId: chain.chainId,
31117
31528
  });
31118
- return gas * maxFeePerGas;
31529
+ const feeWei = gas * maxFeePerGas;
31530
+ // Tempo fees are denominated in virtual USD (18 dec, 1e-18 USD units).
31531
+ // Convert to 6-dec stablecoin units so all callers receive a
31532
+ // consistent unit without needing to know about the chain.
31533
+ if (chain.chainId === CHAIN_IDS.TEMPO) {
31534
+ return convertTempoFeeToStablecoinUnits(feeWei);
31535
+ }
31536
+ return feeWei;
31119
31537
  }
31120
31538
  case ChainType.COSMOS: {
31121
31539
  // TODO: get gas estimation from backend
@@ -31162,19 +31580,33 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31162
31580
  token,
31163
31581
  from,
31164
31582
  });
31165
- const { nativeBalance } = useNativeBalance(chain);
31166
- const isNativeBalanceEnoughToPayGasFees = useMemo(() => {
31167
- if (!nativeBalance?.value || !amount || !token?.decimals) {
31583
+ const { chainFeeParams, gasToken } = useSourceChainGasToken({
31584
+ fromChain: chain,
31585
+ fromToken: token,
31586
+ });
31587
+ const gasBalanceEnough = useMemo(() => {
31588
+ if (!amount || !token?.decimals)
31168
31589
  return false;
31169
- }
31170
- const isTokenNative = token.address.toLowerCase() ===
31171
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31172
- if (isTokenNative) {
31173
- return (parseToBigInt(amount, token.decimals) + estimatedGas <=
31174
- nativeBalance.value);
31175
- }
31176
- return estimatedGas <= nativeBalance.value;
31177
- }, [amount, estimatedGas, nativeBalance?.value, token]);
31590
+ return isGasBalanceEnough({
31591
+ fromAmount: parseToBigInt(amount, token.decimals).toString(),
31592
+ networkFeesWei: estimatedGas,
31593
+ chainFeeParams,
31594
+ });
31595
+ }, [amount, estimatedGas, token, chainFeeParams]);
31596
+ const gasBalanceNeeded = useMemo(() => {
31597
+ if (!amount || !token?.decimals || !gasToken?.decimals)
31598
+ return undefined;
31599
+ // native fees + fromAmount (if native)
31600
+ const totalGasBalanceNeeded = estimatedGas +
31601
+ parseToBigInt(chainFeeParams?.fromTokenPaysNetworkFee ? amount ?? "0" : "0", token.decimals);
31602
+ return formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals);
31603
+ }, [
31604
+ amount,
31605
+ chainFeeParams?.fromTokenPaysNetworkFee,
31606
+ estimatedGas,
31607
+ gasToken?.decimals,
31608
+ token?.decimals,
31609
+ ]);
31178
31610
  const isBalanceEnough = useMemo(() => {
31179
31611
  if (token?.decimals == null || !balance || !amount)
31180
31612
  return false;
@@ -31182,28 +31614,21 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31182
31614
  parseToBigInt(amount, token.decimals));
31183
31615
  }, [amount, balance, token?.decimals]);
31184
31616
  const minAmountValueWarnMsg = useMemo(() => {
31185
- if (!token?.address || !nativeBalance?.value || !estimatedGas)
31617
+ if (!token?.address || !estimatedGas || !chainFeeParams)
31186
31618
  return undefined;
31187
- const isFromTokenNative = token.address.toLowerCase() ===
31188
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31189
31619
  return calculateMinAmountValueWarnMsg({
31190
- isFromTokenNative,
31191
- nativeTokenBalanceFromChainWei: nativeBalance.value,
31192
- sourceChainNativeTokenDecimals: nativeBalance.decimals,
31193
- totalFeesInNativeTokenPlusRatio: estimatedGas,
31620
+ chainFeeParams,
31621
+ networkFeesWei: estimatedGas,
31622
+ fromTokenDecimals: token.decimals,
31194
31623
  });
31195
- }, [
31196
- estimatedGas,
31197
- nativeBalance?.decimals,
31198
- nativeBalance?.value,
31199
- token?.address,
31200
- token?.type,
31201
- ]);
31624
+ }, [estimatedGas, token, chainFeeParams]);
31202
31625
  return {
31203
31626
  estimatedGas,
31627
+ gasBalanceNeeded,
31204
31628
  isBalanceEnough,
31629
+ tokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31205
31630
  isLoading,
31206
- isNativeBalanceEnoughToPayGasFees,
31631
+ isGasBalanceEnough: gasBalanceEnough,
31207
31632
  minAmountValueWarnMsg,
31208
31633
  };
31209
31634
  }
@@ -31373,15 +31798,18 @@ async function sendTransactionXrpl({ amount, signer, to, token, from, }) {
31373
31798
  txHash: hash,
31374
31799
  };
31375
31800
  }
31376
- const [currency, issuer] = token.address.split(".");
31801
+ const asset = parseXrplTokenAddress(token.address);
31802
+ if (!asset) {
31803
+ throw new Error("Invalid asset");
31804
+ }
31377
31805
  const amountFormatted = formatBNToReadable(amount, token.decimals);
31378
31806
  const { hash } = await signer.signAndSubmit({
31379
31807
  network: xrplNetwork,
31380
31808
  tx: {
31381
31809
  ...baseTransaction,
31382
31810
  Amount: {
31383
- currency,
31384
- issuer,
31811
+ currency: asset.code,
31812
+ issuer: asset.issuer,
31385
31813
  value: amountFormatted,
31386
31814
  },
31387
31815
  },
@@ -32119,7 +32547,7 @@ const useApproval = ({ squidRoute, }) => {
32119
32547
  * On Error: Showing the error message if any
32120
32548
  * @returns {boolean} approved
32121
32549
  */
32122
- const routeApproved = useQuery(keys().routeApproved(squidRoute, allowanceInWei), async () => {
32550
+ const routeApproved = useQuery(keys().routeApproved(squidRoute, allowanceInWei, erc20AllowanceQueryEnabled, hasAllowance), async () => {
32123
32551
  if (erc20AllowanceQueryEnabled) {
32124
32552
  return hasAllowance;
32125
32553
  }
@@ -32262,7 +32690,7 @@ const useApproval = ({ squidRoute, }) => {
32262
32690
  // This is to ensure we're using the latest expiry timestamp
32263
32691
  if (squidRoute) {
32264
32692
  queryClient.refetchQueries({
32265
- queryKey: keys().transaction(squidRoute.params.fromChain, squidRoute.params.toChain, squidRoute.params.toToken, squidRoute.params.fromToken, fromPrice, squidRoute.params.slippage, squidRoute.params.receiveGasOnDestination, squidRoute.params.fromAddress, squidRoute.params.bypassGuardrails, squidRoute.params.toAddress, squidRoute.params.fallbackAddresses?.[0]?.address, squidRoute.params.quoteOnly, getChainType(squidRoute.params.fromChain), squidRoute.params.preHook, squidRoute.params.postHook,
32693
+ queryKey: keys().transaction(squidRoute.params.fromChain, squidRoute.params.toChain, squidRoute.params.toToken, squidRoute.params.fromToken, fromPrice, squidRoute.params.slippage, squidRoute.params.fromAddress, squidRoute.params.bypassGuardrails, squidRoute.params.toAddress, squidRoute.params.fallbackAddresses?.[0]?.address, squidRoute.params.quoteOnly, getChainType(squidRoute.params.fromChain), squidRoute.params.preHook, squidRoute.params.postHook,
32266
32694
  // TODO: update types
32267
32695
  squidRoute.params?.overrideGasRefundAddress),
32268
32696
  });
@@ -32281,17 +32709,30 @@ const AXELAR_PROVIDER_IMAGE_URL = "https://raw.githubusercontent.com/0xsquid/ass
32281
32709
  const useEstimate = (squidRoute) => {
32282
32710
  const collectFees = useConfigStore((state) => state.config.collectFees);
32283
32711
  const { tokens } = useSquidTokens();
32284
- const { fromChain, toChain } = useSwap();
32285
- const { nativeBalance } = useNativeBalance(fromChain);
32712
+ const { fromChain, toChain, fromPrice } = useSwap();
32713
+ const fromToken = useMemo(() => findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken), [tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken]);
32714
+ const { chainFeeParams, gasToken } = useSourceChainGasToken({
32715
+ fromChain,
32716
+ fromToken,
32717
+ });
32286
32718
  const estimateResults = useMemo(() => calculateEstimateResults({
32287
32719
  squidRoute,
32288
32720
  tokens,
32289
32721
  fromChain,
32290
32722
  toChain,
32291
32723
  collectFees: !!collectFees && collectFees.fee > 0,
32292
- nativeTokenBalanceFromChainWei: nativeBalance?.value ?? BigInt("0"),
32293
- }), [squidRoute, tokens, fromChain, toChain, collectFees, nativeBalance]);
32294
- const balanceFormatted = useMultiChainBalance({
32724
+ chainFeeParams,
32725
+ gasToken,
32726
+ }), [
32727
+ squidRoute,
32728
+ tokens,
32729
+ fromChain,
32730
+ toChain,
32731
+ collectFees,
32732
+ chainFeeParams,
32733
+ gasToken,
32734
+ ]);
32735
+ const fromBalanceFormatted = useMultiChainBalance({
32295
32736
  chain: fromChain,
32296
32737
  token: estimateResults.fromToken,
32297
32738
  userAddress: squidRoute?.params.fromAddress ?? "",
@@ -32303,7 +32744,6 @@ const useEstimate = (squidRoute) => {
32303
32744
  estimateResults.expectedGasRefundCost,
32304
32745
  getUSDValue,
32305
32746
  ]);
32306
- const proposedGasDestinationAmount = useMemo(() => getProposedGasDestinationAmount(estimateResults.destChainNativeToken?.symbol), [estimateResults.destChainNativeToken]);
32307
32747
  const { feeCostsFormatted, totalFeeCostsUsd } = useMemo(() => {
32308
32748
  const feeCosts = squidRoute?.estimate.feeCosts ?? [];
32309
32749
  const feeCostsRenamed = [];
@@ -32362,21 +32802,21 @@ const useEstimate = (squidRoute) => {
32362
32802
  totalFeeCostsUsd: totalFeeCostsUsdFormatted,
32363
32803
  };
32364
32804
  }, [squidRoute?.estimate.feeCosts]);
32365
- const slippageFormatted =
32366
- // TODO: update types
32367
- Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) +
32368
- "%";
32369
- const enoughBalanceToSwap = +balanceFormatted >= 0 &&
32370
- +balanceFormatted > +estimateResults.fromAmountFormatted;
32805
+ const slippageFormatted = Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) + "%";
32806
+ const fromBalanceEnoughToSwap = useMemo(() => {
32807
+ const fromBalanceNum = Number(fromBalanceFormatted ?? 0);
32808
+ const fromPriceNum = Number(fromPrice ?? 0);
32809
+ return fromBalanceNum >= fromPriceNum;
32810
+ }, [fromBalanceFormatted, fromPrice]);
32371
32811
  return {
32372
32812
  ...estimateResults,
32373
- balanceFormatted,
32813
+ fromBalanceFormatted,
32374
32814
  slippageFormatted,
32375
32815
  totalWithRefundEstimate,
32376
- proposedGasDestinationAmount,
32377
- enoughBalanceToSwap,
32816
+ fromBalanceEnoughToSwap,
32378
32817
  feeCostsFormatted,
32379
32818
  totalFeeCostsUsd,
32819
+ gasToken,
32380
32820
  };
32381
32821
  };
32382
32822
 
@@ -35836,20 +36276,42 @@ const useExecuteTransaction = (squidRoute) => {
35836
36276
  if (stellarNetwork == null) {
35837
36277
  throw new Error(`No Stellar network found for chainId ${fromChainId}`);
35838
36278
  }
35839
- const { data: xdrHex, gasPrice } = route.transactionRequest;
35840
- const { address } = await stellarSigner.getAddress();
35841
36279
  const client = await getClient(fromChain);
35842
- const account = await client.getAccount(address);
35843
- const operation = xdr.Operation.fromXDR(xdrHex, "hex");
35844
- const builtTransaction = new TransactionBuilder(account, {
35845
- fee: gasPrice || (BigInt(BASE_FEE) * BigInt(2)).toString(),
35846
- networkPassphrase: stellarNetwork,
35847
- })
35848
- .addOperation(operation)
35849
- .setTimeout(300)
35850
- .build();
35851
- const preparedTransaction = await client.prepareTransaction(builtTransaction);
35852
- const { signedTxXdr } = await stellarSigner.signTransaction(preparedTransaction.toXDR(), {
36280
+ // TODO: at the moment we're receiving different formats depending on the route type.
36281
+ //
36282
+ // For OnChainExecution we get a single operation xdr string, so we need to build
36283
+ // a full transaction frontend-side (by adding the operation to a new transaction).
36284
+ //
36285
+ // For DepositAddressCalldata we get the full transaction xdr,
36286
+ // so we just need to send it to the wallet for signing.
36287
+ //
36288
+ // In the future this will be standardized backend-side so we have a unified flow for all route types.
36289
+ let txXdrToSign;
36290
+ if (route.transactionRequest.type === SquidDataType.OnChainExecution) {
36291
+ const { data: operationXdrHex, gasPrice } = route.transactionRequest;
36292
+ const { address } = await stellarSigner.getAddress();
36293
+ const account = await client.getAccount(address);
36294
+ const operation = xdr.Operation.fromXDR(operationXdrHex, "hex");
36295
+ const builtTransaction = new TransactionBuilder(account, {
36296
+ fee: gasPrice || (BigInt(BASE_FEE) * BigInt(2)).toString(),
36297
+ networkPassphrase: stellarNetwork,
36298
+ })
36299
+ .addOperation(operation)
36300
+ .setTimeout(300)
36301
+ .build();
36302
+ // This is a smart-contract transaction, so it needs to be simulated first
36303
+ const preparedTransaction = await client.prepareTransaction(builtTransaction);
36304
+ txXdrToSign = preparedTransaction.toXDR();
36305
+ }
36306
+ else if (route.transactionRequest.type === SquidDataType.DepositAddressCalldata) {
36307
+ // For this route type, we get the full transaction xdr in the data field.
36308
+ // This is a payment transaction, so simulation must be skipped.
36309
+ txXdrToSign = route.transactionRequest.data;
36310
+ }
36311
+ else {
36312
+ throw new Error(`Invalid route type ${route.transactionRequest.type}`);
36313
+ }
36314
+ const { signedTxXdr } = await stellarSigner.signTransaction(txXdrToSign, {
35853
36315
  networkPassphrase: stellarNetwork,
35854
36316
  });
35855
36317
  const signedTransaction = new Transaction$1(signedTxXdr, stellarNetwork);
@@ -36122,7 +36584,7 @@ const useGetRoute = () => {
36122
36584
  });
36123
36585
  // Cache the route data
36124
36586
  // Useful when the getRoute mutation is called from another hook
36125
- queryClient.setQueryData(keys().transaction(fromChain, toChain, toToken.address, fromToken.address, fromPrice, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, swapRoute?.fallbackAddress, quoteOnly, fromChainType, config.preHook, config.postHook, overrideGasRefundAddress), route);
36587
+ queryClient.setQueryData(keys().transaction(fromChain, toChain, toToken.address, fromToken.address, fromPrice, config.slippage, sourceUserAddress, config.degenMode, destinationAddress, swapRoute?.fallbackAddress, quoteOnly, fromChainType, config.preHook, config.postHook, overrideGasRefundAddress), route);
36126
36588
  return route;
36127
36589
  });
36128
36590
  };
@@ -36145,14 +36607,13 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36145
36607
  const sourceUserAddress = isDepositAddressEnabled && isAvailableAsPaymentMethod
36146
36608
  ? depositRefundAddress ?? sourceConnectedAddress
36147
36609
  : sourceConnectedAddress;
36148
- const squidRouteQueryKeys = useMemo(() => keys().transaction(fromChain?.chainId, toChain?.chainId, toToken?.address, fromToken?.address, fromPrice, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChain?.chainType, config.preHook, config.postHook, config.overrideGasRefundAddress), [
36610
+ const squidRouteQueryKeys = useMemo(() => keys().transaction(fromChain?.chainId, toChain?.chainId, toToken?.address, fromToken?.address, fromPrice, config.slippage, sourceUserAddress, config.degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChain?.chainType, config.preHook, config.postHook, config.overrideGasRefundAddress), [
36149
36611
  fromChain?.chainId,
36150
36612
  toChain?.chainId,
36151
36613
  toToken?.address,
36152
36614
  fromToken?.address,
36153
36615
  fromPrice,
36154
36616
  config.slippage,
36155
- config.enableGetGasOnDestination,
36156
36617
  sourceUserAddress,
36157
36618
  config.degenMode,
36158
36619
  destinationAddress,
@@ -36424,35 +36885,6 @@ const useAvatar = (seed = zeroAddress) => {
36424
36885
  return avatar || "";
36425
36886
  };
36426
36887
 
36427
- const useUserParams = () => {
36428
- const enableGetGasOnDestination = useConfigStore((state) => state.config.enableGetGasOnDestination);
36429
- const { fromToken, toToken, toChain, fromChain } = useSwap();
36430
- // =============
36431
- // GAS
36432
- // =============
36433
- const getGasOnDestSupportedForThisRoute = useMemo(() =>
36434
- // Not supporting get gas on dest for same chains
36435
- fromChain?.chainId !== toChain?.chainId &&
36436
- // If the destination chain is cosmos, we don't support getting gas there
36437
- toChain?.chainType !== ChainType.COSMOS &&
36438
- // Not supporting get gas on dest for same tokens (bridge)
36439
- ((fromToken?.subGraphIds?.some((sgi) => !!toToken?.subGraphIds?.includes(sgi)) &&
36440
- toToken?.subGraphIds?.some((sgi) => !!fromToken?.subGraphIds?.includes(sgi))) ||
36441
- // Except for uusdc -> uusdc
36442
- (fromToken?.subGraphIds?.includes("uusdc") &&
36443
- toToken?.subGraphIds?.includes("uusdc"))), [
36444
- fromChain?.chainId,
36445
- fromToken?.subGraphIds,
36446
- toChain?.chainId,
36447
- toToken?.subGraphIds,
36448
- toChain?.chainType,
36449
- ]);
36450
- return {
36451
- gasEnabled: enableGetGasOnDestination && getGasOnDestSupportedForThisRoute,
36452
- getGasOnDestSupportedForThisRoute,
36453
- };
36454
- };
36455
-
36456
36888
  const useAddToken = (chainToCompare, tokenToCompare) => {
36457
36889
  const { chain: currentEvmChain } = useAccount();
36458
36890
  const { connector } = useAccount();
@@ -36637,12 +37069,12 @@ function useXrplTrustLine({ address, chain, token, amount }) {
36637
37069
  if (!address || !isXrplAddressValid(address)) {
36638
37070
  return null;
36639
37071
  }
36640
- const [currency, issuer] = token.address.split(".");
36641
- if (!currency || !issuer) {
37072
+ const trustLineAsset = parseXrplTokenAddress(token.address);
37073
+ if (!trustLineAsset) {
36642
37074
  return null;
36643
37075
  }
36644
37076
  const xrplClient = await getClient(chain);
36645
- const trustLine = await xrplClient.getTrustLine(address, issuer, currency);
37077
+ const trustLine = await xrplClient.getTrustLine(address, trustLineAsset);
36646
37078
  return trustLine;
36647
37079
  },
36648
37080
  enabled: !!address &&
@@ -36880,5 +37312,5 @@ const SquidProvider = ({ children, config, placeholder, }) => {
36880
37312
  React.createElement(CosmosProvider, null, children)))))))))) : (placeholder);
36881
37313
  };
36882
37314
 
36883
- export { useSwap as $, AxelarStatusResponseType as A, useKeyboardNavigation as B, CHAIN_IDS as C, DEFAULT_LOCALE as D, useSquidQueryClient as E, useSquid as F, useStellarAccountActivation as G, HistoryTxType as H, useHederaAccountActivation as I, useAddressBookStore as J, useAssetsColorsStore as K, useFavoriteTokensStore as L, useHistoryStore as M, Nr as N, useSendTransactionStore as O, useConfigStore as P, QueryKeys as Q, useSquidStore as R, SquidStatusErrorType as S, TransactionErrorType as T, useSwapRoutePersistStore as U, useTransactionStore as V, Wo as W, XamanXrplNetwork as X, ConnectingWalletStatus as Y, useWalletStore as Z, useDepositAddress as _, WindowWalletFlag as a, getXummClient as a$, useAllConnectedWalletBalances as a0, useAllTokensWithBalanceForChainType as a1, useCosmosBalance as a2, useEvmBalance as a3, useMultiChainBalance as a4, useMultipleTokenPrices as a5, useBitcoinNativeBalance as a6, useCosmosNativeBalance as a7, useEvmNativeBalance as a8, useNativeBalance as a9, useAddToken as aA, useAutoConnect as aB, useEnsDataForAddress as aC, useEnsSearch as aD, useGnosisContext as aE, useIsSameAddressAndGnosisContext as aF, useIntegratorContext as aG, useMultiChainWallet as aH, useSigner as aI, useWallet as aJ, useWallets as aK, useXrplTrustLine as aL, TX_STATUS_CONSTANTS as aM, FINAL_TRANSACTION_STATUSES as aN, useGetFiatQuote as aO, useGetOnRampConfig as aP, useExecuteFiatQuote as aQ, useFiatOnRampTxStatus as aR, useFiatTransactions as aS, useCurrencyDetails as aT, useCountryDetails as aU, useAvailableQuotes as aV, useRecommendedQuote as aW, useGetOnrampPaymentTypes as aX, useSuggestedFiatAmounts as aY, SquidProvider as aZ, EnsService as a_, useSolanaNativeBalance as aa, useStellarNativeBalance as ab, useSuiNativeBalance as ac, useXrplNativeBalance as ad, useNativeTokenForChain as ae, useSingleTokenPrice as af, useSquidTokens as ag, useHistoricalData as ah, useTokensData as ai, useEstimateSendTransaction as aj, useSendTransaction as ak, useSendTransactionGas as al, useAllTransactionsStatus as am, useApproval as an, useEstimate as ao, useEstimatePriceImpact as ap, useExecuteTransaction as aq, useGetRoute as ar, useGetRouteWrapper as as, useRouteWarnings as at, useSendTransactionStatus as au, useSwapTransactionStatus as av, useAvatar as aw, useHistory as ax, useUserParams as ay, useDebouncedValue as az, chainTypeToZeroAddressMap as b, adaptiveRound as b$, isXamanXAppContext as b0, getQueryHeaders as b1, getStatusCode as b2, is404Error as b3, assetsBaseUrl as b4, shareSubgraphId as b5, sortTokensBySharedSubgraphIds as b6, getSupportedChainIdsForDirection as b7, filterChains as b8, filterTokens as b9, getCosmosKey as bA, getKeysSettled as bB, getAllKeysForSupportedCosmosChains as bC, isCosmosAddressValid as bD, getCosmosSigningClient as bE, getCosmosChainInfosObject as bF, connectCosmosWallet as bG, isFallbackAddressNeeded as bH, suggestChainOrThrow as bI, normalizeError as bJ, transactionErrorCode as bK, isUserRejectionError as bL, getTransactionError as bM, handleTransactionErrorEvents as bN, isSwapRouteError as bO, isStatusError as bP, createQuoteRequestParamsHash as bQ, WidgetEvents as bR, EvmNetworkNotSupportedErrorCode as bS, addEthereumChain as bT, parseEvmAddress as bU, formatEvmWallet as bV, filterWagmiConnector as bW, waitForReceiptWithRetry as bX, getUserCountry as bY, getCountryData as bZ, getCurrencyData as b_, getTokenImage as ba, getNewSwapParamsFromInput as bb, sortAllTokens as bc, findToken as bd, findNativeToken as be, normalizeIbcAddress as bf, groupTokensBySymbol as bg, groupTokensByChainId as bh, filterViewableTokens as bi, getSecretNetworkBalances as bj, getTokenAssetsKey as bk, fetchAssetsColors as bl, initializeSquidWithAssetsColors as bm, isEmptyObject as bn, normalizeTokenSymbol as bo, areTokenSymbolsCompatible as bp, isEvmosChain as bq, getConfigWithDefaults as br, randomIntFromInterval as bs, getTokensForChain as bt, getFirstAvailableChainId as bu, fetchHighestBalanceToken as bv, getInitialOrDefaultTokenAddressForChain as bw, getInitialTokenAddressForChain as bx, filterTokensForDestination as by, getInitialChainIdFromConfig as bz, chainTypeToNativeTokenAddressMap as c, isEvmChainNotSupportedError as c$, getSuggestedAmountsForCurrency as c0, HederaExtensionHelper as c1, convertHederaAccountIdToEvmAddress as c2, convertEvmAddressToHederaAccountId as c3, scaleHbarToWei as c4, scaleWeiToHbar as c5, parseToBigInt as c6, roundNumericValue as c7, formatUnitsRounded as c8, formatTokenAmount as c9, isChainflipBridgeTransaction as cA, isOnChainTxData as cB, getHistoryTransactionId as cC, getStepStatuses as cD, getHalfSuccessState as cE, getStepsInfos as cF, getSwapTxStatusRefetchInterval as cG, getSendTxStatusRefetchInterval as cH, chainflipMultihopBridgeType as cI, getBridgeType as cJ, getTransactionStatus as cK, getTransactionEndStatus as cL, isHistoryTransactionPending as cM, isHistoryTransactionFailed as cN, isHistoryTransactionWarning as cO, isHistoryTransactionEnded as cP, formatHash as cQ, isWalletAddressValid as cR, redirectToExtensionsStore as cS, accessProperty as cT, populateWallets as cU, getDefaultChain as cV, sortWallets as cW, areSameAddress as cX, sortAddressBook as cY, calculateTotalUsdBalanceUSD as cZ, addTokenToWallet as c_, formatUsdAmount as ca, trimExtraDecimals as cb, getNumericValue as cc, cleanAmount as cd, convertTokenAmountToUSD as ce, convertUSDToTokenAmount as cf, calculateTotal24hChange as cg, getRouteExpiry as ch, searchTokens as ci, filterSolanaWallets as cj, isSolanaAddressValid as ck, executeSolanaSwap as cl, executeSolanaTransfer as cm, formatTransactionHistoryDate as cn, getAxelarExplorerTxUrl as co, getSourceExplorerTxUrl as cp, getMainExplorerUrl as cq, formatDistance as cr, formatSeconds as cs, formatSwapTxStatusResponseForStorage as ct, simplifyRouteAction as cu, fetchSwapTransactionStatus as cv, compareTransactionIds as cw, isCoralBridgeAction as cx, sleep as cy, isDepositRoute as cz, definedInWindow as d, getWalletSupportedChainTypes as d0, getConnectorForChainType as d1, walletSupportsChainType as d2, connectWallet as d3, cancelConnectWallet as d4, isProblematicConnector as d5, mergeWallets as d6, isXionSmartContractAddress as d7, isXrplAddressValid as d8, buildXrplTrustSetTx as d9, getXrplNetwork as da, parseXrplPaymentTx as db, er as e, formatBNToReadable as f, DEFAULT_ROUTE_REFETCH_INTERVAL as g, destinationAddressResetValue as h, fallbackAddressResetValue as i, nativeCosmosTokenAddress as j, nativeEvmTokenAddress as k, nativeSolanaTokenAddress as l, nativeStellarTokenAddress as m, nativeBitcoinTokenAddress as n, nativeSuiTokenAddress as o, nativeXrplTokenAddress as p, CosmosProvider as q, SendTransactionStatus as r, TransactionStatus as s, useTrackSearchEmpty as t, useCosmosContext as u, useSquidChains as v, walletIconBaseUrl as w, useClient as x, useCosmosForChain as y, useHederaTokenAssociations as z };
36884
- //# sourceMappingURL=index-BGVXRZI6.js.map
37315
+ export { useDepositAddress as $, AxelarStatusResponseType as A, useHederaTokenAssociations as B, CHAIN_IDS as C, DEFAULT_LOCALE as D, useKeyboardNavigation as E, useSquidQueryClient as F, useSquid as G, HistoryTxType as H, useStellarAccountActivation as I, useStellarTrustLine as J, useAddressBookStore as K, useAssetsColorsStore as L, useFavoriteTokensStore as M, Nr as N, useHistoryStore as O, useSendTransactionStore as P, QueryKeys as Q, useConfigStore as R, SquidStatusErrorType as S, TransactionErrorType as T, useSquidStore as U, useSwapRoutePersistStore as V, Wo as W, XamanXrplNetwork as X, useTransactionStore as Y, ConnectingWalletStatus as Z, useWalletStore as _, WindowWalletFlag as a, EnsService as a$, useSwap as a0, useAllConnectedWalletBalances as a1, useAllTokensWithBalanceForChainType as a2, useCosmosBalance as a3, useEvmBalance as a4, useMultiChainBalance as a5, useMultipleTokenPrices as a6, useBitcoinNativeBalance as a7, useCosmosNativeBalance as a8, useEvmNativeBalance as a9, useDebouncedValue as aA, useAddToken as aB, useAutoConnect as aC, useEnsDataForAddress as aD, useEnsSearch as aE, useGnosisContext as aF, useIsSameAddressAndGnosisContext as aG, useIntegratorContext as aH, useMultiChainWallet as aI, useSigner as aJ, useWallet as aK, useWallets as aL, useXrplTrustLine as aM, TX_STATUS_CONSTANTS as aN, FINAL_TRANSACTION_STATUSES as aO, useGetFiatQuote as aP, useGetOnRampConfig as aQ, useExecuteFiatQuote as aR, useFiatOnRampTxStatus as aS, useFiatTransactions as aT, useCurrencyDetails as aU, useCountryDetails as aV, useAvailableQuotes as aW, useRecommendedQuote as aX, useGetOnrampPaymentTypes as aY, useSuggestedFiatAmounts as aZ, SquidProvider as a_, useNativeBalance as aa, useSolanaNativeBalance as ab, useStellarNativeBalance as ac, useSuiNativeBalance as ad, useXrplNativeBalance as ae, useNativeTokenForChain as af, useSingleTokenPrice as ag, useSourceChainGasToken as ah, useSquidTokens as ai, useHistoricalData as aj, useTokensData as ak, useEstimateSendTransaction as al, useSendTransaction as am, useSendTransactionGas as an, useAllTransactionsStatus as ao, useApproval as ap, useEstimate as aq, useEstimatePriceImpact as ar, useExecuteTransaction as as, useGetRoute as at, useGetRouteWrapper as au, useRouteWarnings as av, useSendTransactionStatus as aw, useSwapTransactionStatus as ax, useAvatar as ay, useHistory as az, chainTypeToZeroAddressMap as b, getCurrencyData as b$, getXummClient as b0, isXamanXAppContext as b1, getQueryHeaders as b2, getStatusCode as b3, is404Error as b4, assetsBaseUrl as b5, shareSubgraphId as b6, sortTokensBySharedSubgraphIds as b7, getSupportedChainIdsForDirection as b8, filterChains as b9, getInitialChainIdFromConfig as bA, getCosmosKey as bB, getKeysSettled as bC, getAllKeysForSupportedCosmosChains as bD, isCosmosAddressValid as bE, getCosmosSigningClient as bF, getCosmosChainInfosObject as bG, connectCosmosWallet as bH, isFallbackAddressNeeded as bI, suggestChainOrThrow as bJ, normalizeError as bK, transactionErrorCode as bL, isUserRejectionError as bM, getTransactionError as bN, handleTransactionErrorEvents as bO, isSwapRouteError as bP, isStatusError as bQ, createQuoteRequestParamsHash as bR, WidgetEvents as bS, EvmNetworkNotSupportedErrorCode as bT, addEthereumChain as bU, parseEvmAddress as bV, formatEvmWallet as bW, filterWagmiConnector as bX, waitForReceiptWithRetry as bY, getUserCountry as bZ, getCountryData as b_, filterTokens as ba, getTokenImage as bb, getNewSwapParamsFromInput as bc, sortAllTokens as bd, findToken as be, findNativeToken as bf, normalizeIbcAddress as bg, groupTokensBySymbol as bh, groupTokensByChainId as bi, filterViewableTokens as bj, getSecretNetworkBalances as bk, getTokenAssetsKey as bl, fetchAssetsColors as bm, initializeSquidWithAssetsColors as bn, isEmptyObject as bo, normalizeTokenSymbol as bp, areTokenSymbolsCompatible as bq, isEvmosChain as br, getConfigWithDefaults as bs, randomIntFromInterval as bt, getTokensForChain as bu, getFirstAvailableChainId as bv, fetchHighestBalanceToken as bw, getInitialOrDefaultTokenAddressForChain as bx, getInitialTokenAddressForChain as by, filterTokensForDestination as bz, chainTypeToNativeTokenAddressMap as c, isWalletAddressValid as c$, adaptiveRound as c0, getSuggestedAmountsForCurrency as c1, HederaExtensionHelper as c2, convertHederaAccountIdToEvmAddress as c3, convertEvmAddressToHederaAccountId as c4, scaleHbarToWei as c5, scaleWeiToHbar as c6, parseToBigInt as c7, roundNumericValue as c8, formatUnitsRounded as c9, getMainExplorerUrl as cA, formatDistance as cB, formatSeconds as cC, formatSwapTxStatusResponseForStorage as cD, simplifyRouteAction as cE, fetchSwapTransactionStatus as cF, compareTransactionIds as cG, isCoralBridgeAction as cH, sleep as cI, isDepositRoute as cJ, isChainflipBridgeTransaction as cK, isOnChainTxData as cL, getHistoryTransactionId as cM, getStepStatuses as cN, getHalfSuccessState as cO, getStepsInfos as cP, getSwapTxStatusRefetchInterval as cQ, getSendTxStatusRefetchInterval as cR, chainflipMultihopBridgeType as cS, getBridgeType as cT, getTransactionStatus as cU, getTransactionEndStatus as cV, isHistoryTransactionPending as cW, isHistoryTransactionFailed as cX, isHistoryTransactionWarning as cY, isHistoryTransactionEnded as cZ, formatHash as c_, formatTokenAmount as ca, formatUsdAmount as cb, trimExtraDecimals as cc, getNumericValue as cd, cleanAmount as ce, convertTokenAmountToUSD as cf, convertUSDToTokenAmount as cg, calculateTotal24hChange as ch, getRouteExpiry as ci, searchTokens as cj, filterSolanaWallets as ck, isSolanaAddressValid as cl, executeSolanaSwap as cm, executeSolanaTransfer as cn, isStellarAddressValid as co, getStellarNetwork as cp, stellarAddressToScVal as cq, getStellarTrustLineAsset as cr, isStellarToken as cs, isStellarIssuedToken as ct, getStellarHorizonApiUrl as cu, isValidIssuedAsset as cv, isValidHorizonAsset as cw, formatTransactionHistoryDate as cx, getAxelarExplorerTxUrl as cy, getSourceExplorerTxUrl as cz, definedInWindow as d, redirectToExtensionsStore as d0, accessProperty as d1, populateWallets as d2, getDefaultChain as d3, sortWallets as d4, areSameAddress as d5, sortAddressBook as d6, calculateTotalUsdBalanceUSD as d7, addTokenToWallet as d8, isEvmChainNotSupportedError as d9, getWalletSupportedChainTypes as da, getConnectorForChainType as db, walletSupportsChainType as dc, connectWallet as dd, cancelConnectWallet as de, isProblematicConnector as df, mergeWallets as dg, isXionSmartContractAddress as dh, isXrplAddressValid as di, buildXrplTrustSetTx as dj, getXrplNetwork as dk, parseXrplPaymentTx as dl, parseXrplTokenAddress as dm, er as e, formatBNToReadable as f, DEFAULT_ROUTE_REFETCH_INTERVAL as g, destinationAddressResetValue as h, fallbackAddressResetValue as i, nativeCosmosTokenAddress as j, nativeEvmTokenAddress as k, nativeSolanaTokenAddress as l, nativeStellarTokenAddress as m, nativeBitcoinTokenAddress as n, nativeSuiTokenAddress as o, nativeXrplTokenAddress as p, CosmosProvider as q, SendTransactionStatus as r, TransactionStatus as s, useTrackSearchEmpty as t, useCosmosContext as u, useSquidChains as v, walletIconBaseUrl as w, useClient as x, useCosmosForChain as y, useHederaAccountActivation as z };
37316
+ //# sourceMappingURL=index-XR8ODWxH.js.map