@0xsquid/react-hooks 8.4.1-beta-interactive-to-amount.0 → 8.4.1-beta-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 (39) hide show
  1. package/dist/core/constants.d.ts +3 -0
  2. package/dist/core/queries/queries-keys.d.ts +1 -1
  3. package/dist/core/types/config.d.ts +0 -1
  4. package/dist/core/types/route.d.ts +1 -1
  5. package/dist/hooks/index.d.ts +2 -2
  6. package/dist/hooks/store/useSquidStore.d.ts +1 -2
  7. package/dist/hooks/swap/useSwap.d.ts +2 -4
  8. package/dist/hooks/tokens/useSourceChainGasToken.d.ts +11 -0
  9. package/dist/hooks/transaction/send/useEstimateSendTransactionGas.d.ts +6 -2
  10. package/dist/hooks/transaction/useEstimate.d.ts +5 -11
  11. package/dist/hooks/transaction/useGetRoute.d.ts +0 -1
  12. package/dist/hooks/transaction/useTempoFeeCheck.d.ts +11 -0
  13. package/dist/{index-DnkELFs9.js → index-B4aeecpP.js} +1319 -1203
  14. package/dist/index-B4aeecpP.js.map +1 -0
  15. package/dist/{index-Bg7CS2Uo.js → index-CaI-xWCW.js} +1316 -1200
  16. package/dist/index-CaI-xWCW.js.map +1 -0
  17. package/dist/{index.es-tWag56u3.js → index.es-BXf9jwuI.js} +3 -3
  18. package/dist/{index.es-tWag56u3.js.map → index.es-BXf9jwuI.js.map} +1 -1
  19. package/dist/{index.es-BQGUcysL.js → index.es-DAfqL2H0.js} +3 -3
  20. package/dist/{index.es-BQGUcysL.js.map → index.es-DAfqL2H0.js.map} +1 -1
  21. package/dist/index.esm.js +2 -2
  22. package/dist/index.js +3 -3
  23. package/dist/{secretService-Bs5SPC6i.js → secretService-CIYxEkTN.js} +3 -3
  24. package/dist/{secretService-Bs5SPC6i.js.map → secretService-CIYxEkTN.js.map} +1 -1
  25. package/dist/{secretService-BXleHutG.js → secretService-Cld4gYfG.js} +3 -3
  26. package/dist/{secretService-BXleHutG.js.map → secretService-Cld4gYfG.js.map} +1 -1
  27. package/dist/services/internal/assetsService.d.ts +2 -2
  28. package/dist/services/internal/estimateService.d.ts +13 -27
  29. package/dist/services/internal/tempoService.d.ts +82 -0
  30. package/dist/{stellarService.client-li6iEHAu.js → stellarService.client-COeQeah_.js} +3 -3
  31. package/dist/{stellarService.client-li6iEHAu.js.map → stellarService.client-COeQeah_.js.map} +1 -1
  32. package/dist/{stellarService.client-TTYw1-aB.js → stellarService.client-ocLzRIB4.js} +3 -3
  33. package/dist/{stellarService.client-TTYw1-aB.js.map → stellarService.client-ocLzRIB4.js.map} +1 -1
  34. package/dist/tests/networkGasToken.test.d.ts +1 -0
  35. package/dist/tests/tempoService.test.d.ts +1 -0
  36. package/package.json +1 -1
  37. package/dist/hooks/user/useUserParams.d.ts +0 -4
  38. package/dist/index-Bg7CS2Uo.js.map +0 -1
  39. package/dist/index-DnkELFs9.js.map +0 -1
@@ -27,9 +27,9 @@ var wagmi = require('wagmi');
27
27
  var SafeAppsSDK = require('@safe-global/safe-apps-sdk');
28
28
  var core$1 = require('@wallet-standard/core');
29
29
  var slushWallet = require('@mysten/slush-wallet');
30
- var connectors = require('wagmi/connectors');
31
30
  var stargate = require('@cosmjs/stargate');
32
31
  var client = require('@mysten/sui/client');
32
+ var connectors = require('wagmi/connectors');
33
33
  var protoSigning = require('@cosmjs/proto-signing');
34
34
  var transactions = require('@mysten/sui/transactions');
35
35
  var cosmwasmStargate = require('@cosmjs/cosmwasm-stargate');
@@ -145,6 +145,9 @@ const CHAIN_IDS = {
145
145
  SONEIUM: "1868",
146
146
  PEAQ: "3338",
147
147
  HEDERA: "295",
148
+ MANTRA: "5888",
149
+ CITREA: "4114",
150
+ TEMPO: "4217",
148
151
  // others
149
152
  BITCOIN: "bitcoin",
150
153
  SOLANA: "solana-mainnet-beta",
@@ -22306,17 +22309,15 @@ const keys = () => ({
22306
22309
  // ============
22307
22310
  // Transactions
22308
22311
  // ============
22309
- transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, fromAmount, toAmount, slippage, getGasOnDestination, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22312
+ transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, price, slippage, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22310
22313
  ...keys().transactions(),
22311
22314
  exports.QueryKeys.Transaction,
22312
22315
  fromChainId,
22313
22316
  toChainId,
22314
22317
  toTokenAddress,
22315
22318
  fromTokenAddress,
22316
- fromAmount,
22317
- toAmount,
22319
+ price,
22318
22320
  slippage,
22319
- getGasOnDestination,
22320
22321
  sourceUserAddress,
22321
22322
  degenMode,
22322
22323
  destinationAddress,
@@ -22477,7 +22478,6 @@ const getConfigWithDefaults = (config) => {
22477
22478
  integratorId: get$2(config, "integratorId", defaultConfigValues.integratorId),
22478
22479
  slippage: get$2(config, "slippage", defaultConfigValues.slippage),
22479
22480
  collectFees: get$2(config, "collectFees", defaultConfigValues.collectFees),
22480
- enableGetGasOnDestination: get$2(config, "enableGetGasOnDestination", defaultConfigValues.enableGetGasOnDestination),
22481
22481
  apiUrl: get$2(config, "apiUrl", defaultConfigValues.apiUrl),
22482
22482
  priceImpactWarnings: get$2(config, "priceImpactWarnings", defaultConfigValues.priceImpactWarnings),
22483
22483
  initialAssets: get$2(config, "initialAssets", defaultConfigValues.initialAssets),
@@ -22967,8 +22967,8 @@ const sortAllTokens = (tokenA, tokenB) => {
22967
22967
  return 0;
22968
22968
  };
22969
22969
  const findToken = (tokens, chainId, address) => tokens.find((t) => t.chainId === chainId && t.address === address);
22970
- const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain?.nativeCurrency.symbol.toUpperCase() &&
22971
- t.chainId == chain?.chainId);
22970
+ const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain.nativeCurrency.symbol.toUpperCase() &&
22971
+ t.chainId == chain.chainId);
22972
22972
  const normalizeIbcAddress = (address) => {
22973
22973
  if (!address.toLowerCase().startsWith("ibc/")) {
22974
22974
  return address;
@@ -23179,7 +23179,7 @@ const filterViewableTokens = (tokens, config, direction) => {
23179
23179
  };
23180
23180
  const getSecretNetworkBalances = async (chainData, cosmosAddress, squidTokens, keplrTypeWallet) => {
23181
23181
  const squidSecretTokens = squidTokens.filter((t) => t.chainId === CHAIN_IDS.SECRET);
23182
- const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-BXleHutG.js'); });
23182
+ const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-Cld4gYfG.js'); });
23183
23183
  return fetchAllSecretBalances(chainData, cosmosAddress, squidSecretTokens, keplrTypeWallet);
23184
23184
  };
23185
23185
  function getTokenAssetsKey(token) {
@@ -24870,7 +24870,7 @@ class OnrampService {
24870
24870
  });
24871
24871
  return data;
24872
24872
  }
24873
- async getConfiguration({ chains, tokens, }) {
24873
+ async getConfiguration({ chains: _, tokens, }) {
24874
24874
  const { data } = await axios.get(`${this.baseUrl}/config`);
24875
24875
  // Filter supportedCryptos to only include tokens that match our provided tokens
24876
24876
  const filteredCryptos = data.supportedCryptos.filter((supportedCrypto) => tokens.some((token) => token.address.toLowerCase() ===
@@ -25036,7 +25036,10 @@ const useConfigStore = zustand.create(() => ({
25036
25036
  isInitialized: false,
25037
25037
  }));
25038
25038
  const useTransactionStore = zustand.create((set, get) => ({
25039
+ fromPrice: undefined,
25040
+ txLocalId: undefined,
25039
25041
  transactions: {},
25042
+ currentTransaction: undefined,
25040
25043
  setTransactionState(txId, tx) {
25041
25044
  if (!txId)
25042
25045
  return;
@@ -26492,7 +26495,7 @@ function useStellarWallets() {
26492
26495
  try {
26493
26496
  const { allowAllModules: initializeAllModules } = await import('@creit.tech/stellar-wallets-kit');
26494
26497
  const { LedgerModule } = await import('@creit.tech/stellar-wallets-kit/modules/ledger.module');
26495
- const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-li6iEHAu.js'); });
26498
+ const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-COeQeah_.js'); });
26496
26499
  const modules = [...initializeAllModules(), new LedgerModule()];
26497
26500
  const promises = modules.map(async (module) => {
26498
26501
  const isAvailable = await module.isAvailable();
@@ -27391,8 +27394,7 @@ const useMultiChainWallet = (chain) => {
27391
27394
 
27392
27395
  const useSwap = () => {
27393
27396
  const { initialAssets, defaultTokensPerChain, disabledChains, availableChains, } = useConfigStore((state) => state.config);
27394
- const fromAmount = useTransactionStore((state) => state.fromAmount);
27395
- const toAmount = useTransactionStore((state) => state.toAmount);
27397
+ const fromPrice = useTransactionStore((state) => state.fromPrice);
27396
27398
  const { swapRoute } = useSwapRoutePersistStore();
27397
27399
  const { tokens } = useSquidTokens();
27398
27400
  const queryClient = reactQuery.useQueryClient();
@@ -27450,17 +27452,8 @@ const useSwap = () => {
27450
27452
  }),
27451
27453
  };
27452
27454
  }, [destAddressData, destAddressEnsData.data, toChain?.chainType]);
27453
- const fromAmountChanged = React.useCallback((amount) => {
27454
- useTransactionStore.setState({
27455
- fromAmount: amount || undefined,
27456
- toAmount: undefined,
27457
- });
27458
- }, []);
27459
- const toAmountChanged = React.useCallback((amount) => {
27460
- useTransactionStore.setState({
27461
- toAmount: amount || undefined,
27462
- fromAmount: undefined,
27463
- });
27455
+ const fromPriceChanged = React.useCallback((price) => {
27456
+ useTransactionStore.setState({ fromPrice: price || undefined });
27464
27457
  }, []);
27465
27458
  /**
27466
27459
  * When user changes something from the SwapView
@@ -27601,10 +27594,8 @@ const useSwap = () => {
27601
27594
  tokenItems,
27602
27595
  onSwapChange,
27603
27596
  invertSwaps,
27604
- fromAmount,
27605
- toAmount,
27606
- fromAmountChanged,
27607
- toAmountChanged,
27597
+ fromPrice,
27598
+ fromPriceChanged,
27608
27599
  toToken,
27609
27600
  fromToken,
27610
27601
  fromChain,
@@ -27811,579 +27802,309 @@ function useTrackSearchEmpty({ searchQuery, resultsLength, context, debounceMs =
27811
27802
  }, [context, debounceMs, resultsLength, searchQuery]);
27812
27803
  }
27813
27804
 
27814
- var hrc20 = [
27815
- {
27816
- inputs: [
27817
- ],
27818
- name: "associate",
27819
- outputs: [
27820
- {
27821
- internalType: "uint256",
27822
- name: "responseCode",
27823
- type: "uint256"
27824
- }
27825
- ],
27826
- stateMutability: "nonpayable",
27827
- type: "function"
27828
- }
27829
- ];
27830
-
27831
- /**
27832
- * Client for interacting with the Hedera Mirrornode API.
27833
- *
27834
- * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
27835
- */
27836
- class HederaApiClient {
27837
- apiUrl;
27838
- constructor(apiUrl) {
27839
- this.apiUrl = apiUrl;
27840
- }
27841
- async isTokenAssociated({ address, token, }) {
27842
- const accountInfo = await this.getAccountInfo(address);
27843
- // Unlimited auto associations
27844
- if (accountInfo.max_automatic_token_associations === -1) {
27845
- return true;
27846
- }
27847
- // If there's no unlimited auto-associations, we need to check if the token is already associated.
27848
- const { tokens: accountTokens } = await this.getAccountTokens(address);
27849
- const tokenId = convertEvmAddressToHederaAccountId(token.address);
27850
- if (accountTokens.some((t) => t.token_id === tokenId)) {
27851
- // Token is already associated
27852
- return true;
27853
- }
27854
- // Finally, if not auto-associated, check if there is an available auto-association slot
27855
- const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
27856
- const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
27857
- autoAssociatedTokens.length;
27858
- return remainingAutoAssociations > 0;
27805
+ class StellarRpcClient {
27806
+ server;
27807
+ constructor(rpcUrl) {
27808
+ this.server = new stellarSdk.rpc.Server(rpcUrl);
27859
27809
  }
27860
- async getAccountInfo(address) {
27861
- const data = await this.fetch(`accounts/${address}`);
27862
- if (typeof data.max_automatic_token_associations !== "number") {
27863
- throw new Error("Invalid max_automatic_token_associations type, expected number");
27810
+ /**
27811
+ * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
27812
+ *
27813
+ * With Contract Tokens, we need to call the .balance method on the token contract
27814
+ * and simulate the transaction to get the balance.
27815
+ */
27816
+ async getBalance(userAddress, tokenAddress, chainId) {
27817
+ const account = await this.server.getAccount(userAddress);
27818
+ const network = getStellarNetwork(chainId);
27819
+ if (network == null) {
27820
+ throw new Error(`No Stellar network found for chainId ${chainId}`);
27864
27821
  }
27865
- if (typeof data.balance.balance !== "number") {
27866
- throw new Error("Invalid balance type, expected number");
27822
+ const txBuilder = new stellarSdk.TransactionBuilder(account, {
27823
+ fee: (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
27824
+ networkPassphrase: network,
27825
+ });
27826
+ const contract = new stellarSdk.Contract(tokenAddress);
27827
+ const tx = txBuilder
27828
+ .addOperation(contract.call("balance", new stellarSdk.Address(userAddress).toScVal()))
27829
+ .setTimeout(stellarSdk.TimeoutInfinite)
27830
+ .build();
27831
+ const simulateTxResponse = await this.server.simulateTransaction(tx);
27832
+ if ("error" in simulateTxResponse) {
27833
+ const isNoBalanceError = simulateTxResponse.error.includes("trying to get non-existing value for contract instance");
27834
+ // If the error message indicates that the user has no balance just return 0
27835
+ // We don't want to spam with this error as it's pretty common
27836
+ if (isNoBalanceError) {
27837
+ return "0";
27838
+ }
27839
+ throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
27867
27840
  }
27868
- if (!Array.isArray(data.balance.tokens)) {
27869
- throw new Error("Invalid tokens type, expected array");
27841
+ if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
27842
+ const native = stellarSdk.scValToNative(simulateTxResponse.result.retval);
27843
+ return native.toString();
27870
27844
  }
27871
- return data;
27845
+ throw new Error("Failed to fetch balance");
27872
27846
  }
27873
- async getAccountTokens(address) {
27874
- const data = await this.fetch(`accounts/${address}/tokens`);
27875
- if (!Array.isArray(data.tokens)) {
27876
- throw new Error("Invalid tokens type, expected array");
27847
+ async getAllBalances(userAddress, tokens) {
27848
+ const balancePromises = tokens.map((token) => {
27849
+ return this.getBalance(userAddress, token.address, token.chainId);
27850
+ });
27851
+ const results = await Promise.allSettled(balancePromises);
27852
+ const balances = results.map((result) => {
27853
+ if (result.status === "fulfilled") {
27854
+ return result.value;
27855
+ }
27856
+ return "0";
27857
+ });
27858
+ return balances.map((balance, index) => {
27859
+ return {
27860
+ ...tokens[index],
27861
+ balance,
27862
+ };
27863
+ });
27864
+ }
27865
+ /**
27866
+ * Resolves when the transaction is confirmed, or fails after a timeout.
27867
+ */
27868
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
27869
+ const startTime = Date.now();
27870
+ while (true) {
27871
+ const result = await this.server.getTransaction(txHash);
27872
+ if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.NOT_FOUND) {
27873
+ if (Date.now() - startTime > timeout) {
27874
+ throw new Error(`Transaction ${txHash} not found within timeout`);
27875
+ }
27876
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
27877
+ await new Promise((res) => setTimeout(res, interval));
27878
+ continue;
27879
+ }
27880
+ if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) {
27881
+ return result.status;
27882
+ }
27883
+ throw new Error(`Transaction failed with status: ${result.status}`);
27877
27884
  }
27878
- const firstToken = data.tokens[0];
27879
- if (typeof firstToken?.automatic_association !== "boolean") {
27880
- throw new Error("Invalid automatic_association type, expected boolean");
27885
+ }
27886
+ async isAccountActivated(address) {
27887
+ try {
27888
+ await this.getAccount(address);
27889
+ return true;
27881
27890
  }
27882
- if (typeof firstToken?.token_id !== "string") {
27883
- throw new Error("Invalid token_id type, expected string");
27891
+ catch (error) {
27892
+ if (error?.message && error.message.includes("Account not found")) {
27893
+ return false;
27894
+ }
27895
+ throw error;
27884
27896
  }
27885
- return data;
27886
27897
  }
27887
- async fetch(path) {
27888
- const url = new URL(path, this.apiUrl);
27889
- const response = await fetch(url);
27890
- if (!response.ok) {
27891
- throw new Error(`Hedera API error: ${response.status}`);
27898
+ async getAccount(address) {
27899
+ return this.server.getAccount(address);
27900
+ }
27901
+ async sendTransaction(transaction) {
27902
+ const transactionResponse = await this.server.sendTransaction(transaction);
27903
+ if (transactionResponse.status === "ERROR") {
27904
+ throw new Error("Error sending transaction");
27892
27905
  }
27893
- return response.json();
27906
+ return transactionResponse;
27907
+ }
27908
+ async prepareTransaction(transaction) {
27909
+ return this.server.prepareTransaction(transaction);
27894
27910
  }
27895
27911
  }
27896
27912
 
27897
- hederaWalletConnect.type = "hederaWalletConnect";
27898
- /**
27899
- * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
27900
- *
27901
- * The connector removes the need for a WalletConnect modal, and instead
27902
- * communicates with the provided `extension` by opening the extension popup
27903
- * for user interaction upon request.
27904
- */
27905
- function hederaWalletConnect(parameters) {
27906
- const { extension, ...restParameters } = parameters;
27907
- let provider_;
27908
- let providerPromise;
27909
- let connect;
27910
- let displayUri;
27911
- let sessionDelete;
27912
- let disconnect;
27913
- return createConnector((config) => ({
27914
- id: `hedera-wc-${extension.id}`,
27915
- name: extension.name,
27916
- icon: extension.icon,
27917
- type: hederaWalletConnect.type,
27918
- async setup() {
27919
- const provider = await this.getProvider().catch(() => null);
27920
- if (!provider)
27921
- return;
27922
- if (!connect) {
27923
- connect = this.onConnect.bind(this);
27924
- provider.on("connect", connect);
27925
- }
27926
- if (!sessionDelete) {
27927
- sessionDelete = this.onSessionDelete.bind(this);
27928
- provider.on("session_delete", sessionDelete);
27913
+ class XrplRpcClient {
27914
+ rpcUrl;
27915
+ constructor(rpcUrl) {
27916
+ this.rpcUrl = rpcUrl;
27917
+ }
27918
+ async getBalance(address, tokenAddress) {
27919
+ if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
27920
+ return this.getNativeBalance(address);
27921
+ }
27922
+ return this.getIssuedCurrencyBalance(address, tokenAddress);
27923
+ }
27924
+ async getAllBalances(address) {
27925
+ const [nativeBalance, trustLineBalances] = await Promise.all([
27926
+ this.getNativeBalance(address),
27927
+ this.getTrustLines(address),
27928
+ ]);
27929
+ return [
27930
+ {
27931
+ balance: nativeBalance,
27932
+ address: nativeXrplTokenAddress,
27933
+ },
27934
+ ...trustLineBalances.lines.map((line) => ({
27935
+ balance: line.balance,
27936
+ address: `${line.currency}.${line.account}`,
27937
+ })),
27938
+ ];
27939
+ }
27940
+ async getTrustLines(address, issuer) {
27941
+ return this.call("account_lines", [
27942
+ {
27943
+ account: address,
27944
+ ledger_index: "validated",
27945
+ peer: issuer,
27946
+ },
27947
+ ]);
27948
+ }
27949
+ async getTrustLine(address, issuer, currency) {
27950
+ const response = await this.getTrustLines(address, issuer);
27951
+ const trustLine = response.lines.find((line) => line.currency === currency);
27952
+ return trustLine ?? null;
27953
+ }
27954
+ async accountActivatedInfo(address) {
27955
+ const serverState = await this.getServerState();
27956
+ const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
27957
+ try {
27958
+ const accountInfo = await this.getAccountInfo(address);
27959
+ const balanceBn = BigInt(accountInfo.account_data.Balance);
27960
+ return {
27961
+ isActivated: balanceBn >= reserveBaseBn,
27962
+ reserveBaseBn,
27963
+ };
27964
+ }
27965
+ catch (error) {
27966
+ if (error.message?.includes("actNotFound")) {
27967
+ return { isActivated: false, reserveBaseBn };
27929
27968
  }
27930
- },
27931
- async connect(params = {}) {
27969
+ throw error;
27970
+ }
27971
+ }
27972
+ /**
27973
+ * Waits for a transaction to be validated and returns its final status.
27974
+ * Resolves to 'success' or throws an error with the failed status.
27975
+ */
27976
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
27977
+ const startTime = Date.now();
27978
+ while (true) {
27932
27979
  try {
27933
- const provider = await this.getProvider();
27934
- if (!provider)
27935
- throw new ProviderNotFoundError();
27936
- if (!displayUri) {
27937
- displayUri = this.onDisplayUri;
27938
- provider.on("display_uri", displayUri);
27939
- }
27940
- if (!provider.session) {
27941
- await provider.connect({
27942
- ...("pairingTopic" in params
27943
- ? { pairingTopic: params.pairingTopic }
27944
- : {}),
27945
- });
27946
- }
27947
- const accounts = (await provider.enable()).map((x) => viem.getAddress(x));
27948
- const currentChainId = await this.getChainId();
27949
- if (displayUri) {
27950
- provider.removeListener("display_uri", displayUri);
27951
- displayUri = undefined;
27952
- }
27953
- if (connect) {
27954
- provider.removeListener("connect", connect);
27955
- connect = undefined;
27980
+ const response = await this.call("tx", [
27981
+ {
27982
+ transaction: txHash,
27983
+ binary: false,
27984
+ },
27985
+ ]);
27986
+ if (!response.validated) {
27987
+ if (Date.now() - startTime > timeout) {
27988
+ throw new Error(`Transaction ${txHash} not validated within timeout`);
27989
+ }
27990
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
27991
+ await new Promise((res) => setTimeout(res, interval));
27992
+ continue;
27956
27993
  }
27957
- if (!disconnect) {
27958
- disconnect = this.onDisconnect.bind(this);
27959
- provider.on("disconnect", disconnect);
27994
+ const status = response.meta?.TransactionResult;
27995
+ if (status === XrplTxStatus.SUCCESS) {
27996
+ return status;
27960
27997
  }
27961
- if (!sessionDelete) {
27962
- sessionDelete = this.onSessionDelete.bind(this);
27963
- provider.on("session_delete", sessionDelete);
27998
+ else {
27999
+ throw new Error(`Transaction failed with status: ${status}`);
27964
28000
  }
27965
- return { accounts, chainId: currentChainId };
27966
28001
  }
27967
28002
  catch (error) {
27968
- if (/(user rejected|connection request reset)/i.test(error?.message)) {
27969
- throw new viem.UserRejectedRequestError(error);
28003
+ // txnNotFound = still pending or non-existent
28004
+ if (error?.message?.includes("txnNotFound")) {
28005
+ if (Date.now() - startTime > timeout) {
28006
+ throw new Error(`Transaction ${txHash} not found within timeout`);
28007
+ }
28008
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28009
+ await new Promise((res) => setTimeout(res, interval));
28010
+ continue;
27970
28011
  }
27971
28012
  throw error;
27972
28013
  }
27973
- },
27974
- async disconnect() {
27975
- const provider = await this.getProvider();
27976
- try {
27977
- await provider?.disconnect();
27978
- }
27979
- catch (error) {
27980
- if (!/No matching key/i.test(error.message))
27981
- throw error;
27982
- }
27983
- finally {
27984
- if (disconnect) {
27985
- provider?.removeListener("disconnect", disconnect);
27986
- disconnect = undefined;
27987
- }
27988
- if (!connect) {
27989
- connect = this.onConnect.bind(this);
27990
- provider?.on("connect", connect);
27991
- }
27992
- if (sessionDelete) {
27993
- provider?.removeListener("session_delete", sessionDelete);
27994
- sessionDelete = undefined;
27995
- }
27996
- }
27997
- },
27998
- async getAccounts() {
27999
- const provider = await this.getProvider();
28000
- return provider.accounts.map((x) => viem.getAddress(x));
28001
- },
28002
- async getProvider() {
28003
- async function initProvider() {
28004
- const optionalChains = config.chains.map((x) => x.id);
28005
- if (!optionalChains.length)
28006
- return;
28007
- const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-BQGUcysL.js'); });
28008
- const rawProvider = await EthereumProvider.init({
28009
- ...restParameters,
28010
- disableProviderPing: true,
28011
- optionalChains,
28012
- projectId: restParameters.projectId,
28013
- rpcMap: Object.fromEntries(config.chains.map((chain) => {
28014
- const [url] = extractRpcUrls({
28015
- chain,
28016
- transports: config.transports,
28017
- });
28018
- return [chain.id, url];
28019
- })),
28020
- showQrModal: false,
28021
- // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
28022
- // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
28023
- customStoragePrefix: "squid-hedera",
28024
- });
28025
- const proxiedProvider = new Proxy(rawProvider, {
28026
- get(target, prop, receiver) {
28027
- if (prop === "request") {
28028
- return async (args) => {
28029
- const signingMethods = [
28030
- "eth_sendTransaction",
28031
- "eth_signTransaction",
28032
- ];
28033
- if (signingMethods.includes(args.method)) {
28034
- try {
28035
- HederaExtensionHelper.extensionOpen(extension.id);
28036
- }
28037
- catch { }
28038
- }
28039
- // forward request to original provider
28040
- return target.request(args);
28041
- };
28042
- }
28043
- // forward all other properties/methods
28044
- return Reflect.get(target, prop, receiver);
28045
- },
28046
- });
28047
- return proxiedProvider;
28048
- }
28049
- if (!provider_) {
28050
- if (!providerPromise)
28051
- providerPromise = initProvider();
28052
- provider_ = await providerPromise;
28053
- provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
28054
- }
28055
- return provider_;
28056
- },
28057
- async getChainId() {
28058
- const provider = await this.getProvider();
28059
- return provider.chainId;
28060
- },
28061
- async isAuthorized() {
28062
- try {
28063
- const accounts = await this.getAccounts();
28064
- return accounts.length > 0;
28065
- }
28066
- catch {
28067
- return false;
28068
- }
28069
- },
28070
- onAccountsChanged(accounts) {
28071
- if (accounts.length === 0)
28072
- this.onDisconnect();
28073
- else
28074
- config.emitter.emit("change", {
28075
- accounts: accounts.map((x) => viem.getAddress(x)),
28076
- });
28077
- },
28078
- onChainChanged(chain) {
28079
- const chainId = Number(chain);
28080
- config.emitter.emit("change", { chainId });
28081
- },
28082
- async onConnect(connectInfo) {
28083
- const chainId = Number(connectInfo.chainId);
28084
- const accounts = await this.getAccounts();
28085
- config.emitter.emit("connect", { accounts, chainId });
28086
- },
28087
- async onDisconnect() {
28088
- config.emitter.emit("disconnect");
28089
- const provider = await this.getProvider();
28090
- if (disconnect) {
28091
- provider.removeListener("disconnect", disconnect);
28092
- disconnect = undefined;
28093
- }
28094
- if (sessionDelete) {
28095
- provider.removeListener("session_delete", sessionDelete);
28096
- sessionDelete = undefined;
28097
- }
28098
- if (!connect) {
28099
- connect = this.onConnect.bind(this);
28100
- provider.on("connect", connect);
28101
- }
28102
- },
28103
- onDisplayUri(uri) {
28104
- config.emitter.emit("message", { type: "display_uri", data: uri });
28105
- HederaExtensionHelper.extensionConnect(extension.id, uri);
28106
- },
28107
- onSessionDelete() {
28108
- this.onDisconnect();
28109
- },
28110
- }));
28111
- }
28112
-
28113
- const createWagmiConfig = (squidChains, hederaExtensions) => {
28114
- const filteredEvmChains = squidChains.filter((chain) => chain.chainType === squidTypes.ChainType.EVM);
28115
- if (filteredEvmChains.length === 0) {
28116
- throw new Error("At least one chain is required");
28014
+ }
28117
28015
  }
28118
- const wagmiChains = filteredEvmChains.map((chain) => {
28119
- return viem.defineChain({
28120
- id: Number(chain.chainId),
28121
- name: chain.networkName,
28122
- nativeCurrency: {
28123
- name: chain.nativeCurrency.name,
28124
- symbol: chain.nativeCurrency.symbol,
28125
- decimals: chain.nativeCurrency.decimals,
28126
- },
28127
- rpcUrls: {
28128
- public: {
28129
- http: [chain.rpc],
28130
- },
28131
- default: {
28132
- http: [chain.rpc],
28133
- },
28016
+ async getServerState() {
28017
+ return this.call("server_state", [{}]);
28018
+ }
28019
+ async getAccountInfo(address) {
28020
+ return this.call("account_info", [
28021
+ {
28022
+ account: address,
28023
+ ledger_index: "validated",
28134
28024
  },
28135
- });
28136
- });
28137
- const wcMetadata = {
28138
- url: SQUID_METADATA.url,
28139
- name: SQUID_METADATA.name,
28140
- icons: [SQUID_METADATA.icon],
28141
- description: SQUID_METADATA.description,
28142
- };
28143
- return wagmi.createConfig({
28144
- chains: wagmiChains,
28145
- transports: Object.fromEntries(wagmiChains.map((chain) => [
28146
- chain.id,
28147
- wagmi.http(chain.rpcUrls.public.http[0] ?? ""),
28148
- ])),
28149
- connectors: [
28150
- connectors.injected(),
28151
- connectors.safe({
28152
- allowedDomains: [/app.safe.global$/],
28153
- }),
28154
- connectors.metaMask({
28155
- dappMetadata: {
28156
- name: SQUID_METADATA.name,
28157
- url: SQUID_METADATA.url,
28158
- iconUrl: SQUID_METADATA.icon,
28159
- },
28160
- }),
28161
- connectors.coinbaseWallet({
28162
- appName: SQUID_METADATA.name,
28163
- appLogoUrl: SQUID_METADATA.icon,
28164
- }),
28165
- connectors.walletConnect({
28166
- projectId: WALLETCONNECT_PROJECT_ID,
28167
- metadata: wcMetadata,
28168
- }),
28169
- ...hederaExtensions.map((extension) => hederaWalletConnect({
28170
- projectId: WALLETCONNECT_PROJECT_ID,
28171
- metadata: wcMetadata,
28172
- extension,
28173
- })),
28174
- ],
28175
- });
28176
- };
28177
- // Taken from wagmi docs
28178
- // https://wagmi.sh/react/guides/ethers
28179
- function clientToSigner(client) {
28180
- const { account, chain, transport } = client;
28181
- if (!account || !chain || !transport) {
28182
- return undefined;
28025
+ ]);
28183
28026
  }
28184
- const network = {
28185
- chainId: chain.id,
28186
- name: chain.name,
28187
- ensAddress: chain.contracts?.ensRegistry?.address,
28188
- };
28189
- const provider = new ethers.BrowserProvider(transport, network);
28190
- const signer = new ethers.JsonRpcSigner(provider, account.address);
28191
- return signer;
28192
- }
28193
-
28194
- function useEvmSigner({ chainId }) {
28195
- const { connector } = wagmi.useAccount();
28196
- const { data: client } = wagmi.useWalletClient({ chainId, connector });
28197
- const signer = React.useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
28198
- return { signer };
28199
- }
28200
- const useSigner = ({ chain }) => {
28201
- const evmChainId = chain?.chainType === squidTypes.ChainType.EVM ? Number(chain.chainId) : undefined;
28202
- // EVM and Cosmos need a different signer for each chain
28203
- // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
28204
- const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
28205
- const { signer: cosmosSigner } = useCosmosSigner({ chain });
28206
- const { signer: solanaSigner } = useSolanaContext();
28207
- const { signer: bitcoinSigner } = useBitcoinContext();
28208
- const { signer: suiSigner } = useSuiContext();
28209
- const { signer: xrplSigner } = useXrplContext();
28210
- const { signer: stellarSigner } = useStellarContext();
28211
- const isEvmSignerReady = !!evmSigner;
28212
- const isSolanaSignerReady = !!solanaSigner;
28213
- const isCosmosSignerReady = !!cosmosSigner;
28214
- const isBitcoinSignerReady = !!bitcoinSigner;
28215
- const isSuiSignerReady = !!suiSigner;
28216
- const isXrplSignerReady = !!xrplSigner;
28217
- const isStellarSignerReady = !!stellarSigner;
28218
- const isSignerReady = React.useMemo(() => {
28219
- if (!chain?.chainType)
28220
- return false;
28221
- switch (chain.chainType) {
28222
- case squidTypes.ChainType.EVM:
28223
- return isEvmSignerReady;
28224
- case squidTypes.ChainType.COSMOS:
28225
- return isCosmosSignerReady;
28226
- case squidTypes.ChainType.BTC:
28227
- return isBitcoinSignerReady;
28228
- case squidTypes.ChainType.SOLANA:
28229
- return isSolanaSignerReady;
28230
- case squidTypes.ChainType.SUI:
28231
- return isSuiSignerReady;
28232
- case squidTypes.ChainType.XRPL:
28233
- return isXrplSignerReady;
28234
- case squidTypes.ChainType.STELLAR:
28235
- return isStellarSignerReady;
28236
- }
28237
- }, [
28238
- chain?.chainType,
28239
- isEvmSignerReady,
28240
- isCosmosSignerReady,
28241
- isBitcoinSignerReady,
28242
- isSolanaSignerReady,
28243
- isSuiSignerReady,
28244
- isXrplSignerReady,
28245
- isStellarSignerReady,
28246
- ]);
28247
- return {
28248
- isSignerReady,
28249
- evmSigner,
28250
- cosmosSigner,
28251
- bitcoinSigner,
28252
- solanaSigner,
28253
- suiSigner,
28254
- xrplSigner,
28255
- stellarSigner,
28256
- };
28257
- };
28258
-
28259
- function useHederaTokenAssociations({ address, chain, token }) {
28260
- const publicClient = wagmi.usePublicClient({
28261
- chainId: Number(CHAIN_IDS.HEDERA),
28262
- });
28263
- const { evmSigner } = useSigner({ chain });
28264
- const queryClient = reactQuery.useQueryClient();
28265
28027
  /**
28266
- * Creates a token association transaction where the destination account authorizes to receive the given token
28028
+ * Returns the balance of the user in the native XRP token
28029
+ * formatted as a string
28267
28030
  */
28268
- const associateToken = reactQuery.useMutation({
28269
- mutationFn: async () => {
28270
- try {
28271
- if (!evmSigner) {
28272
- throw new Error("EVM signer not found");
28273
- }
28274
- if (chain?.chainId !== CHAIN_IDS.HEDERA ||
28275
- token?.chainId !== CHAIN_IDS.HEDERA) {
28276
- throw new Error("Chain and token to associate must be on Hedera");
28277
- }
28278
- const tokenAddress = parseEvmAddress(token.address);
28279
- if (!tokenAddress) {
28280
- throw new Error("Invalid token address");
28281
- }
28282
- const userAddress = parseEvmAddress(address);
28283
- if (!userAddress) {
28284
- throw new Error("Invalid user address");
28285
- }
28286
- const encodedData = viem.encodeFunctionData({
28287
- abi: hrc20,
28288
- functionName: "associate",
28289
- args: [],
28290
- });
28291
- const txRes = await evmSigner.sendTransaction({
28292
- data: encodedData,
28293
- to: tokenAddress,
28294
- chainId: chain.chainId,
28295
- });
28296
- const receipt = await txRes.wait();
28297
- if (receipt?.status !== 1) {
28298
- throw new Error(`Transaction failed with status: ${receipt?.status}`);
28299
- }
28300
- return true;
28301
- }
28302
- catch (error) {
28303
- console.error("Error associating Hedera token:", error);
28304
- return false;
28305
- }
28306
- },
28307
- async onSuccess() {
28308
- queryClient.refetchQueries({
28309
- queryKey: getPrefixKey(exports.QueryKeys.IsHederaTokenAssociated),
28310
- });
28311
- },
28312
- });
28031
+ async getNativeBalance(address) {
28032
+ const [accountInfo, serverState] = await Promise.all([
28033
+ this.getAccountInfo(address),
28034
+ this.getServerState(),
28035
+ ]);
28036
+ const balance = BigInt(accountInfo.account_data.Balance);
28037
+ const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
28038
+ const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
28039
+ const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
28040
+ const reserveBalance = reserveBase + ownerCount * reserveIncrement;
28041
+ const spendableBalance = balance - reserveBalance;
28042
+ return formatBNToReadable(spendableBalance, 6);
28043
+ }
28313
28044
  /**
28314
- * Checks if the destination account has associated the given token.
28315
- *
28316
- * Hedera requires accounts to associate a token before being able to receive it.
28317
- *
28318
- * Accounts which have max. associations set to -1 can receive any token without previous association
28045
+ * Returns the balance of the user in the given issued currency (e.g. RLUSD)
28046
+ * formatted as a string
28319
28047
  */
28320
- const isTokenAssociated = reactQuery.useQuery({
28321
- queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
28322
- queryFn: async () => {
28323
- if (token?.chainId !== CHAIN_IDS.HEDERA) {
28324
- return true;
28325
- }
28326
- // The native HBAR token doesn't need an association
28327
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
28328
- return true;
28329
- }
28330
- if (!chain || !address || !publicClient) {
28331
- throw new Error("Missing required parameters");
28332
- }
28333
- // TODO: update types
28334
- const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
28335
- if (!apiUrl) {
28336
- throw new Error("Missing Hedera mirror node URL in chain config");
28337
- }
28338
- const hederaApiClient = new HederaApiClient(apiUrl);
28339
- return hederaApiClient.isTokenAssociated({
28340
- address,
28341
- token,
28342
- });
28343
- },
28344
- enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
28345
- });
28346
- return {
28347
- isTokenAssociated,
28348
- associateToken,
28349
- };
28350
- }
28351
-
28352
- const useKeyboardNavigation = ({ onEscape }) => {
28353
- const onKeyDown = React.useCallback((event) => {
28354
- if (event.key === "Escape") {
28355
- onEscape?.();
28356
- return;
28048
+ async getIssuedCurrencyBalance(address, tokenAddress) {
28049
+ const response = await this.getTrustLines(address);
28050
+ const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
28051
+ return tokenBalance?.balance || "0";
28052
+ }
28053
+ async call(method, params) {
28054
+ const response = await fetch(this.rpcUrl, {
28055
+ method: "POST",
28056
+ headers: {
28057
+ "Content-Type": "application/json",
28058
+ },
28059
+ body: JSON.stringify({
28060
+ jsonrpc: "2.0",
28061
+ id: 1,
28062
+ method,
28063
+ params,
28064
+ }),
28065
+ });
28066
+ const data = await response.json();
28067
+ if (!data.result) {
28068
+ throw new Error(`Invalid response from RPC (${method})`);
28357
28069
  }
28358
- }, [onEscape]);
28359
- React.useEffect(() => {
28360
- document.addEventListener("keydown", onKeyDown, false);
28361
- return () => {
28362
- document.removeEventListener("keydown", onKeyDown, false);
28363
- };
28364
- }, [onKeyDown]);
28365
- };
28070
+ if ("error" in data.result) {
28071
+ throw new Error(`Error from RPC (${method}): ${data.result.error}`);
28072
+ }
28073
+ return data.result;
28074
+ }
28075
+ }
28366
28076
 
28367
- const useSquidQueryClient = () => {
28368
- const queryClient = reactQuery.useQueryClient();
28369
- const invalidateQueries = (key) => {
28370
- const prefixKey = getPrefixKey(key);
28371
- queryClient.invalidateQueries(prefixKey);
28372
- };
28373
- const refetchQueries = (key) => {
28374
- const prefixKey = getPrefixKey(key);
28375
- queryClient.refetchQueries(prefixKey);
28376
- };
28377
- const invalidateAndRefetchQueries = (key) => {
28378
- invalidateQueries(key);
28379
- refetchQueries(key);
28380
- };
28381
- return {
28382
- invalidateQueries,
28383
- refetchQueries,
28384
- invalidateAndRefetchQueries,
28385
- };
28386
- };
28077
+ const clientCache = new Map();
28078
+ async function getClient(chain) {
28079
+ const key = `${chain.chainType}:${chain.chainId}`;
28080
+ if (clientCache.has(key)) {
28081
+ return clientCache.get(key);
28082
+ }
28083
+ const client = await createClient(chain);
28084
+ clientCache.set(key, client);
28085
+ return client;
28086
+ }
28087
+ async function createClient(chain) {
28088
+ switch (chain.chainType) {
28089
+ case squidTypes.ChainType.EVM:
28090
+ return new ethers.JsonRpcProvider(chain.rpc);
28091
+ case squidTypes.ChainType.COSMOS:
28092
+ const rpcUrl = await getWorkingCosmosRpcUrl(chain);
28093
+ return (await stargate.StargateClient.connect(rpcUrl));
28094
+ case squidTypes.ChainType.SOLANA:
28095
+ return new web3_js.Connection(SOLANA_RPC_URL);
28096
+ case squidTypes.ChainType.BTC:
28097
+ return null;
28098
+ case squidTypes.ChainType.SUI:
28099
+ return new client.SuiClient({
28100
+ url: chain.rpc,
28101
+ });
28102
+ case squidTypes.ChainType.XRPL:
28103
+ return new XrplRpcClient(chain.rpc);
28104
+ case squidTypes.ChainType.STELLAR:
28105
+ return new StellarRpcClient(chain.rpc);
28106
+ }
28107
+ }
28387
28108
 
28388
28109
  /**
28389
28110
  * The default multicall3 address
@@ -29399,372 +29120,6 @@ function timeout(ms, promise) {
29399
29120
  return Promise.race([promise, timeoutPromise]);
29400
29121
  }
29401
29122
 
29402
- class StellarRpcClient {
29403
- server;
29404
- constructor(rpcUrl) {
29405
- this.server = new stellarSdk.rpc.Server(rpcUrl);
29406
- }
29407
- /**
29408
- * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
29409
- *
29410
- * With Contract Tokens, we need to call the .balance method on the token contract
29411
- * and simulate the transaction to get the balance.
29412
- */
29413
- async getBalance(userAddress, tokenAddress, chainId) {
29414
- const account = await this.server.getAccount(userAddress);
29415
- const network = getStellarNetwork(chainId);
29416
- if (network == null) {
29417
- throw new Error(`No Stellar network found for chainId ${chainId}`);
29418
- }
29419
- const txBuilder = new stellarSdk.TransactionBuilder(account, {
29420
- fee: (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
29421
- networkPassphrase: network,
29422
- });
29423
- const contract = new stellarSdk.Contract(tokenAddress);
29424
- const tx = txBuilder
29425
- .addOperation(contract.call("balance", new stellarSdk.Address(userAddress).toScVal()))
29426
- .setTimeout(stellarSdk.TimeoutInfinite)
29427
- .build();
29428
- const simulateTxResponse = await this.server.simulateTransaction(tx);
29429
- if ("error" in simulateTxResponse) {
29430
- const isNoBalanceError = simulateTxResponse.error.includes("trying to get non-existing value for contract instance");
29431
- // If the error message indicates that the user has no balance just return 0
29432
- // We don't want to spam with this error as it's pretty common
29433
- if (isNoBalanceError) {
29434
- return "0";
29435
- }
29436
- throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
29437
- }
29438
- if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
29439
- const native = stellarSdk.scValToNative(simulateTxResponse.result.retval);
29440
- return native.toString();
29441
- }
29442
- throw new Error("Failed to fetch balance");
29443
- }
29444
- async getAllBalances(userAddress, tokens) {
29445
- const balancePromises = tokens.map((token) => {
29446
- return this.getBalance(userAddress, token.address, token.chainId);
29447
- });
29448
- const results = await Promise.allSettled(balancePromises);
29449
- const balances = results.map((result) => {
29450
- if (result.status === "fulfilled") {
29451
- return result.value;
29452
- }
29453
- return "0";
29454
- });
29455
- return balances.map((balance, index) => {
29456
- return {
29457
- ...tokens[index],
29458
- balance,
29459
- };
29460
- });
29461
- }
29462
- /**
29463
- * Resolves when the transaction is confirmed, or fails after a timeout.
29464
- */
29465
- async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
29466
- const startTime = Date.now();
29467
- while (true) {
29468
- const result = await this.server.getTransaction(txHash);
29469
- if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.NOT_FOUND) {
29470
- if (Date.now() - startTime > timeout) {
29471
- throw new Error(`Transaction ${txHash} not found within timeout`);
29472
- }
29473
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29474
- await new Promise((res) => setTimeout(res, interval));
29475
- continue;
29476
- }
29477
- if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) {
29478
- return result.status;
29479
- }
29480
- throw new Error(`Transaction failed with status: ${result.status}`);
29481
- }
29482
- }
29483
- async isAccountActivated(address) {
29484
- try {
29485
- await this.getAccount(address);
29486
- return true;
29487
- }
29488
- catch (error) {
29489
- if (error?.message && error.message.includes("Account not found")) {
29490
- return false;
29491
- }
29492
- throw error;
29493
- }
29494
- }
29495
- async getAccount(address) {
29496
- return this.server.getAccount(address);
29497
- }
29498
- async sendTransaction(transaction) {
29499
- const transactionResponse = await this.server.sendTransaction(transaction);
29500
- if (transactionResponse.status === "ERROR") {
29501
- throw new Error("Error sending transaction");
29502
- }
29503
- return transactionResponse;
29504
- }
29505
- async prepareTransaction(transaction) {
29506
- return this.server.prepareTransaction(transaction);
29507
- }
29508
- }
29509
-
29510
- class XrplRpcClient {
29511
- rpcUrl;
29512
- constructor(rpcUrl) {
29513
- this.rpcUrl = rpcUrl;
29514
- }
29515
- async getBalance(address, tokenAddress) {
29516
- if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
29517
- return this.getNativeBalance(address);
29518
- }
29519
- return this.getIssuedCurrencyBalance(address, tokenAddress);
29520
- }
29521
- async getAllBalances(address) {
29522
- const [nativeBalance, trustLineBalances] = await Promise.all([
29523
- this.getNativeBalance(address),
29524
- this.getTrustLines(address),
29525
- ]);
29526
- return [
29527
- {
29528
- balance: nativeBalance,
29529
- address: nativeXrplTokenAddress,
29530
- },
29531
- ...trustLineBalances.lines.map((line) => ({
29532
- balance: line.balance,
29533
- address: `${line.currency}.${line.account}`,
29534
- })),
29535
- ];
29536
- }
29537
- async getTrustLines(address, issuer) {
29538
- return this.call("account_lines", [
29539
- {
29540
- account: address,
29541
- ledger_index: "validated",
29542
- peer: issuer,
29543
- },
29544
- ]);
29545
- }
29546
- async getTrustLine(address, issuer, currency) {
29547
- const response = await this.getTrustLines(address, issuer);
29548
- const trustLine = response.lines.find((line) => line.currency === currency);
29549
- return trustLine ?? null;
29550
- }
29551
- async accountActivatedInfo(address) {
29552
- const serverState = await this.getServerState();
29553
- const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
29554
- try {
29555
- const accountInfo = await this.getAccountInfo(address);
29556
- const balanceBn = BigInt(accountInfo.account_data.Balance);
29557
- return {
29558
- isActivated: balanceBn >= reserveBaseBn,
29559
- reserveBaseBn,
29560
- };
29561
- }
29562
- catch (error) {
29563
- if (error.message?.includes("actNotFound")) {
29564
- return { isActivated: false, reserveBaseBn };
29565
- }
29566
- throw error;
29567
- }
29568
- }
29569
- /**
29570
- * Waits for a transaction to be validated and returns its final status.
29571
- * Resolves to 'success' or throws an error with the failed status.
29572
- */
29573
- async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
29574
- const startTime = Date.now();
29575
- while (true) {
29576
- try {
29577
- const response = await this.call("tx", [
29578
- {
29579
- transaction: txHash,
29580
- binary: false,
29581
- },
29582
- ]);
29583
- if (!response.validated) {
29584
- if (Date.now() - startTime > timeout) {
29585
- throw new Error(`Transaction ${txHash} not validated within timeout`);
29586
- }
29587
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29588
- await new Promise((res) => setTimeout(res, interval));
29589
- continue;
29590
- }
29591
- const status = response.meta?.TransactionResult;
29592
- if (status === XrplTxStatus.SUCCESS) {
29593
- return status;
29594
- }
29595
- else {
29596
- throw new Error(`Transaction failed with status: ${status}`);
29597
- }
29598
- }
29599
- catch (error) {
29600
- // txnNotFound = still pending or non-existent
29601
- if (error?.message?.includes("txnNotFound")) {
29602
- if (Date.now() - startTime > timeout) {
29603
- throw new Error(`Transaction ${txHash} not found within timeout`);
29604
- }
29605
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29606
- await new Promise((res) => setTimeout(res, interval));
29607
- continue;
29608
- }
29609
- throw error;
29610
- }
29611
- }
29612
- }
29613
- async getServerState() {
29614
- return this.call("server_state", [{}]);
29615
- }
29616
- async getAccountInfo(address) {
29617
- return this.call("account_info", [
29618
- {
29619
- account: address,
29620
- ledger_index: "validated",
29621
- },
29622
- ]);
29623
- }
29624
- /**
29625
- * Returns the balance of the user in the native XRP token
29626
- * formatted as a string
29627
- */
29628
- async getNativeBalance(address) {
29629
- const [accountInfo, serverState] = await Promise.all([
29630
- this.getAccountInfo(address),
29631
- this.getServerState(),
29632
- ]);
29633
- const balance = BigInt(accountInfo.account_data.Balance);
29634
- const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
29635
- const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
29636
- const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
29637
- const reserveBalance = reserveBase + ownerCount * reserveIncrement;
29638
- const spendableBalance = balance - reserveBalance;
29639
- return formatBNToReadable(spendableBalance, 6);
29640
- }
29641
- /**
29642
- * Returns the balance of the user in the given issued currency (e.g. RLUSD)
29643
- * formatted as a string
29644
- */
29645
- async getIssuedCurrencyBalance(address, tokenAddress) {
29646
- const response = await this.getTrustLines(address);
29647
- const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
29648
- return tokenBalance?.balance || "0";
29649
- }
29650
- async call(method, params) {
29651
- const response = await fetch(this.rpcUrl, {
29652
- method: "POST",
29653
- headers: {
29654
- "Content-Type": "application/json",
29655
- },
29656
- body: JSON.stringify({
29657
- jsonrpc: "2.0",
29658
- id: 1,
29659
- method,
29660
- params,
29661
- }),
29662
- });
29663
- const data = await response.json();
29664
- if (!data.result) {
29665
- throw new Error(`Invalid response from RPC (${method})`);
29666
- }
29667
- if ("error" in data.result) {
29668
- throw new Error(`Error from RPC (${method}): ${data.result.error}`);
29669
- }
29670
- return data.result;
29671
- }
29672
- }
29673
-
29674
- const clientCache = new Map();
29675
- async function getClient(chain) {
29676
- const key = `${chain.chainType}:${chain.chainId}`;
29677
- if (clientCache.has(key)) {
29678
- return clientCache.get(key);
29679
- }
29680
- const client = await createClient(chain);
29681
- clientCache.set(key, client);
29682
- return client;
29683
- }
29684
- async function createClient(chain) {
29685
- switch (chain.chainType) {
29686
- case squidTypes.ChainType.EVM:
29687
- return new ethers.JsonRpcProvider(chain.rpc);
29688
- case squidTypes.ChainType.COSMOS:
29689
- const rpcUrl = await getWorkingCosmosRpcUrl(chain);
29690
- return (await stargate.StargateClient.connect(rpcUrl));
29691
- case squidTypes.ChainType.SOLANA:
29692
- return new web3_js.Connection(SOLANA_RPC_URL);
29693
- case squidTypes.ChainType.BTC:
29694
- return null;
29695
- case squidTypes.ChainType.SUI:
29696
- return new client.SuiClient({
29697
- url: chain.rpc,
29698
- });
29699
- case squidTypes.ChainType.XRPL:
29700
- return new XrplRpcClient(chain.rpc);
29701
- case squidTypes.ChainType.STELLAR:
29702
- return new StellarRpcClient(chain.rpc);
29703
- }
29704
- }
29705
-
29706
- class StellarApiClient {
29707
- apiUrl;
29708
- constructor(apiUrl) {
29709
- this.apiUrl = apiUrl;
29710
- }
29711
- async getBaseReserve() {
29712
- const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
29713
- if (!response.ok) {
29714
- throw new Error(`Failed to fetch ledgers: ${response.status}`);
29715
- }
29716
- const ledgers = await response.json();
29717
- const latestLedger = ledgers?._embedded.records?.[0];
29718
- if (latestLedger?.base_reserve_in_stroops == null) {
29719
- throw new Error("Invalid ledger data");
29720
- }
29721
- const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
29722
- return baseReserveBn;
29723
- }
29724
- }
29725
-
29726
- const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
29727
- function useStellarAccountActivation({ address, chain, token, }) {
29728
- /**
29729
- * Checks if the destination account exists on the Stellar network
29730
- * Stellar accounts need to have a minimum balance before they can receive payments
29731
- */
29732
- const accountActivatedInfo = reactQuery.useQuery({
29733
- queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
29734
- queryFn: async () => {
29735
- if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
29736
- token?.type !== squidTypes.ChainType.STELLAR) {
29737
- return null;
29738
- }
29739
- if (!address) {
29740
- throw new Error("Destination address is required");
29741
- }
29742
- // TODO: update types
29743
- const [horizonApiUrl] = chain?.horizonRpcList;
29744
- if (typeof horizonApiUrl !== "string") {
29745
- throw new Error("Invalid Horizon API URL");
29746
- }
29747
- const stellarApiClient = new StellarApiClient(horizonApiUrl);
29748
- const reserveBase = await stellarApiClient.getBaseReserve();
29749
- // Stellar accounts require two base reserves to be activated
29750
- // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
29751
- const accountReserveBase = reserveBase * BigInt(2);
29752
- const stellarRpcClient = await getClient(chain);
29753
- const isActivated = await stellarRpcClient.isAccountActivated(address);
29754
- return {
29755
- isActivated,
29756
- reserveBaseBn: accountReserveBase,
29757
- };
29758
- },
29759
- enabled: !!address && chain?.chainType === squidTypes.ChainType.STELLAR,
29760
- refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
29761
- refetchOnWindowFocus: true,
29762
- });
29763
- return {
29764
- accountActivatedInfo,
29765
- };
29766
- }
29767
-
29768
29123
  const DEFAULT_REFRESH_INTERVAL_MS = 15000;
29769
29124
  const useEvmBalance = ({ chain, token, userAddress, enabled = true, refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS, }) => {
29770
29125
  const { isChainTypeConnected } = useWallet();
@@ -29959,6 +29314,8 @@ function useNativeTokenForChain(chain) {
29959
29314
  }
29960
29315
  };
29961
29316
  const nativeTokenForChainType = React.useMemo(() => {
29317
+ if (!chain)
29318
+ return undefined;
29962
29319
  return findNativeToken(getTokensForChainType(), chain);
29963
29320
  }, [chain]);
29964
29321
  return { nativeToken: nativeTokenForChainType };
@@ -30116,136 +29473,772 @@ const useStellarNativeBalance = ({ address, chain, }) => {
30116
29473
  userAddress: address,
30117
29474
  enabled: chain?.chainType === squidTypes.ChainType.STELLAR,
30118
29475
  });
30119
- const balance = React.useMemo(() => {
30120
- if (nativeToken?.decimals && rawBalance) {
30121
- return {
30122
- decimals: nativeToken.decimals,
30123
- value: parseToBigInt(rawBalance, nativeToken.decimals),
30124
- };
30125
- }
30126
- }, [nativeToken?.decimals, rawBalance]);
30127
- return {
30128
- balance,
30129
- isLoading,
29476
+ const balance = React.useMemo(() => {
29477
+ if (nativeToken?.decimals && rawBalance) {
29478
+ return {
29479
+ decimals: nativeToken.decimals,
29480
+ value: parseToBigInt(rawBalance, nativeToken.decimals),
29481
+ };
29482
+ }
29483
+ }, [nativeToken?.decimals, rawBalance]);
29484
+ return {
29485
+ balance,
29486
+ isLoading,
29487
+ };
29488
+ };
29489
+ const useNativeBalance = (chain) => {
29490
+ const { connectedAddresses } = useWallet();
29491
+ const { data: cosmosAddressForChain } = useCosmosForChain(chain);
29492
+ // Cosmos is a special case because the address changes on every chain
29493
+ // so we can't use the default cosmos connected address
29494
+ const { balance: nativeCosmosBalance, isLoading: isCosmosLoading } = useCosmosNativeBalance({
29495
+ address: cosmosAddressForChain,
29496
+ chain,
29497
+ });
29498
+ const { balance: nativeEvmBalance, isLoading: isEvmLoading } = useEvmNativeBalance({ address: connectedAddresses[squidTypes.ChainType.EVM], chain });
29499
+ const { balance: nativeBitcoinBalance, isLoading: isBitcoinLoading } = useBitcoinNativeBalance({
29500
+ address: connectedAddresses[squidTypes.ChainType.BTC],
29501
+ chain,
29502
+ });
29503
+ const { balance: nativeSolanaBalance, isLoading: isSolanaLoading } = useSolanaNativeBalance({
29504
+ address: connectedAddresses[squidTypes.ChainType.SOLANA],
29505
+ chain,
29506
+ });
29507
+ const { balance: nativeSuiBalance, isLoading: isSuiLoading } = useSuiNativeBalance({
29508
+ address: connectedAddresses[squidTypes.ChainType.SUI],
29509
+ chain,
29510
+ });
29511
+ const { balance: nativeXrplBalance, isLoading: isXrpLoading } = useXrplNativeBalance({
29512
+ address: connectedAddresses[squidTypes.ChainType.XRPL],
29513
+ chain,
29514
+ });
29515
+ const { balance: nativeStellarBalance, isLoading: isStellarLoading } = useStellarNativeBalance({
29516
+ address: connectedAddresses[squidTypes.ChainType.STELLAR],
29517
+ chain,
29518
+ });
29519
+ const { nativeBalance, nativeBalanceFormatted } = React.useMemo(() => {
29520
+ let balance;
29521
+ switch (chain?.chainType) {
29522
+ case squidTypes.ChainType.EVM:
29523
+ balance = nativeEvmBalance;
29524
+ break;
29525
+ case squidTypes.ChainType.COSMOS:
29526
+ balance = nativeCosmosBalance;
29527
+ break;
29528
+ case squidTypes.ChainType.BTC:
29529
+ balance = nativeBitcoinBalance;
29530
+ break;
29531
+ case squidTypes.ChainType.SOLANA:
29532
+ balance = nativeSolanaBalance;
29533
+ break;
29534
+ case squidTypes.ChainType.SUI:
29535
+ balance = nativeSuiBalance;
29536
+ break;
29537
+ case squidTypes.ChainType.XRPL:
29538
+ balance = nativeXrplBalance;
29539
+ break;
29540
+ case squidTypes.ChainType.STELLAR:
29541
+ balance = nativeStellarBalance;
29542
+ }
29543
+ const balanceFormatted = !!balance
29544
+ ? formatBNToReadable(balance.value, balance.decimals)
29545
+ : undefined;
29546
+ return {
29547
+ nativeBalance: balance,
29548
+ nativeBalanceFormatted: balanceFormatted,
29549
+ };
29550
+ }, [
29551
+ chain?.chainType,
29552
+ nativeEvmBalance,
29553
+ nativeCosmosBalance,
29554
+ nativeBitcoinBalance,
29555
+ nativeSolanaBalance,
29556
+ nativeSuiBalance,
29557
+ nativeXrplBalance,
29558
+ nativeStellarBalance,
29559
+ ]);
29560
+ const isLoading = React.useMemo(() => {
29561
+ if (!chain?.chainType)
29562
+ return false;
29563
+ switch (chain.chainType) {
29564
+ case squidTypes.ChainType.EVM:
29565
+ return isEvmLoading;
29566
+ case squidTypes.ChainType.COSMOS:
29567
+ return isCosmosLoading;
29568
+ case squidTypes.ChainType.BTC:
29569
+ return isBitcoinLoading;
29570
+ case squidTypes.ChainType.SOLANA:
29571
+ return isSolanaLoading;
29572
+ case squidTypes.ChainType.SUI:
29573
+ return isSuiLoading;
29574
+ case squidTypes.ChainType.XRPL:
29575
+ return isXrpLoading;
29576
+ case squidTypes.ChainType.STELLAR:
29577
+ return isStellarLoading;
29578
+ }
29579
+ }, [
29580
+ chain?.chainType,
29581
+ isEvmLoading,
29582
+ isCosmosLoading,
29583
+ isBitcoinLoading,
29584
+ isSolanaLoading,
29585
+ isSuiLoading,
29586
+ isXrpLoading,
29587
+ isStellarLoading,
29588
+ ]);
29589
+ return { nativeBalance, nativeBalanceFormatted, isLoading };
29590
+ };
29591
+
29592
+ function useHederaAccountActivation({ address, chain, token }) {
29593
+ const { balance: destNativeEvmBalance } = useEvmNativeBalance({
29594
+ chain,
29595
+ address,
29596
+ });
29597
+ const isHederaAccountActivated = React.useMemo(() => {
29598
+ if (token?.chainId !== CHAIN_IDS.HEDERA)
29599
+ return true;
29600
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
29601
+ return true;
29602
+ return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
29603
+ }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
29604
+ return {
29605
+ isHederaAccountActivated,
29606
+ };
29607
+ }
29608
+
29609
+ var hrc20 = [
29610
+ {
29611
+ inputs: [
29612
+ ],
29613
+ name: "associate",
29614
+ outputs: [
29615
+ {
29616
+ internalType: "uint256",
29617
+ name: "responseCode",
29618
+ type: "uint256"
29619
+ }
29620
+ ],
29621
+ stateMutability: "nonpayable",
29622
+ type: "function"
29623
+ }
29624
+ ];
29625
+
29626
+ /**
29627
+ * Client for interacting with the Hedera Mirrornode API.
29628
+ *
29629
+ * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
29630
+ */
29631
+ class HederaApiClient {
29632
+ apiUrl;
29633
+ constructor(apiUrl) {
29634
+ this.apiUrl = apiUrl;
29635
+ }
29636
+ async isTokenAssociated({ address, token, }) {
29637
+ const accountInfo = await this.getAccountInfo(address);
29638
+ // Unlimited auto associations
29639
+ if (accountInfo.max_automatic_token_associations === -1) {
29640
+ return true;
29641
+ }
29642
+ // If there's no unlimited auto-associations, we need to check if the token is already associated.
29643
+ const { tokens: accountTokens } = await this.getAccountTokens(address);
29644
+ const tokenId = convertEvmAddressToHederaAccountId(token.address);
29645
+ if (accountTokens.some((t) => t.token_id === tokenId)) {
29646
+ // Token is already associated
29647
+ return true;
29648
+ }
29649
+ // Finally, if not auto-associated, check if there is an available auto-association slot
29650
+ const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
29651
+ const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
29652
+ autoAssociatedTokens.length;
29653
+ return remainingAutoAssociations > 0;
29654
+ }
29655
+ async getAccountInfo(address) {
29656
+ const data = await this.fetch(`accounts/${address}`);
29657
+ if (typeof data.max_automatic_token_associations !== "number") {
29658
+ throw new Error("Invalid max_automatic_token_associations type, expected number");
29659
+ }
29660
+ if (typeof data.balance.balance !== "number") {
29661
+ throw new Error("Invalid balance type, expected number");
29662
+ }
29663
+ if (!Array.isArray(data.balance.tokens)) {
29664
+ throw new Error("Invalid tokens type, expected array");
29665
+ }
29666
+ return data;
29667
+ }
29668
+ async getAccountTokens(address) {
29669
+ const data = await this.fetch(`accounts/${address}/tokens`);
29670
+ if (!Array.isArray(data.tokens)) {
29671
+ throw new Error("Invalid tokens type, expected array");
29672
+ }
29673
+ const firstToken = data.tokens[0];
29674
+ if (typeof firstToken?.automatic_association !== "boolean") {
29675
+ throw new Error("Invalid automatic_association type, expected boolean");
29676
+ }
29677
+ if (typeof firstToken?.token_id !== "string") {
29678
+ throw new Error("Invalid token_id type, expected string");
29679
+ }
29680
+ return data;
29681
+ }
29682
+ async fetch(path) {
29683
+ const url = new URL(path, this.apiUrl);
29684
+ const response = await fetch(url);
29685
+ if (!response.ok) {
29686
+ throw new Error(`Hedera API error: ${response.status}`);
29687
+ }
29688
+ return response.json();
29689
+ }
29690
+ }
29691
+
29692
+ hederaWalletConnect.type = "hederaWalletConnect";
29693
+ /**
29694
+ * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
29695
+ *
29696
+ * The connector removes the need for a WalletConnect modal, and instead
29697
+ * communicates with the provided `extension` by opening the extension popup
29698
+ * for user interaction upon request.
29699
+ */
29700
+ function hederaWalletConnect(parameters) {
29701
+ const { extension, ...restParameters } = parameters;
29702
+ let provider_;
29703
+ let providerPromise;
29704
+ let connect;
29705
+ let displayUri;
29706
+ let sessionDelete;
29707
+ let disconnect;
29708
+ return createConnector((config) => ({
29709
+ id: `hedera-wc-${extension.id}`,
29710
+ name: extension.name,
29711
+ icon: extension.icon,
29712
+ type: hederaWalletConnect.type,
29713
+ async setup() {
29714
+ const provider = await this.getProvider().catch(() => null);
29715
+ if (!provider)
29716
+ return;
29717
+ if (!connect) {
29718
+ connect = this.onConnect.bind(this);
29719
+ provider.on("connect", connect);
29720
+ }
29721
+ if (!sessionDelete) {
29722
+ sessionDelete = this.onSessionDelete.bind(this);
29723
+ provider.on("session_delete", sessionDelete);
29724
+ }
29725
+ },
29726
+ async connect(params = {}) {
29727
+ try {
29728
+ const provider = await this.getProvider();
29729
+ if (!provider)
29730
+ throw new ProviderNotFoundError();
29731
+ if (!displayUri) {
29732
+ displayUri = this.onDisplayUri;
29733
+ provider.on("display_uri", displayUri);
29734
+ }
29735
+ if (!provider.session) {
29736
+ await provider.connect({
29737
+ ...("pairingTopic" in params
29738
+ ? { pairingTopic: params.pairingTopic }
29739
+ : {}),
29740
+ });
29741
+ }
29742
+ const accounts = (await provider.enable()).map((x) => viem.getAddress(x));
29743
+ const currentChainId = await this.getChainId();
29744
+ if (displayUri) {
29745
+ provider.removeListener("display_uri", displayUri);
29746
+ displayUri = undefined;
29747
+ }
29748
+ if (connect) {
29749
+ provider.removeListener("connect", connect);
29750
+ connect = undefined;
29751
+ }
29752
+ if (!disconnect) {
29753
+ disconnect = this.onDisconnect.bind(this);
29754
+ provider.on("disconnect", disconnect);
29755
+ }
29756
+ if (!sessionDelete) {
29757
+ sessionDelete = this.onSessionDelete.bind(this);
29758
+ provider.on("session_delete", sessionDelete);
29759
+ }
29760
+ return { accounts, chainId: currentChainId };
29761
+ }
29762
+ catch (error) {
29763
+ if (/(user rejected|connection request reset)/i.test(error?.message)) {
29764
+ throw new viem.UserRejectedRequestError(error);
29765
+ }
29766
+ throw error;
29767
+ }
29768
+ },
29769
+ async disconnect() {
29770
+ const provider = await this.getProvider();
29771
+ try {
29772
+ await provider?.disconnect();
29773
+ }
29774
+ catch (error) {
29775
+ if (!/No matching key/i.test(error.message))
29776
+ throw error;
29777
+ }
29778
+ finally {
29779
+ if (disconnect) {
29780
+ provider?.removeListener("disconnect", disconnect);
29781
+ disconnect = undefined;
29782
+ }
29783
+ if (!connect) {
29784
+ connect = this.onConnect.bind(this);
29785
+ provider?.on("connect", connect);
29786
+ }
29787
+ if (sessionDelete) {
29788
+ provider?.removeListener("session_delete", sessionDelete);
29789
+ sessionDelete = undefined;
29790
+ }
29791
+ }
29792
+ },
29793
+ async getAccounts() {
29794
+ const provider = await this.getProvider();
29795
+ return provider.accounts.map((x) => viem.getAddress(x));
29796
+ },
29797
+ async getProvider() {
29798
+ async function initProvider() {
29799
+ const optionalChains = config.chains.map((x) => x.id);
29800
+ if (!optionalChains.length)
29801
+ return;
29802
+ const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-DAfqL2H0.js'); });
29803
+ const rawProvider = await EthereumProvider.init({
29804
+ ...restParameters,
29805
+ disableProviderPing: true,
29806
+ optionalChains,
29807
+ projectId: restParameters.projectId,
29808
+ rpcMap: Object.fromEntries(config.chains.map((chain) => {
29809
+ const [url] = extractRpcUrls({
29810
+ chain,
29811
+ transports: config.transports,
29812
+ });
29813
+ return [chain.id, url];
29814
+ })),
29815
+ showQrModal: false,
29816
+ // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
29817
+ // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
29818
+ customStoragePrefix: "squid-hedera",
29819
+ });
29820
+ const proxiedProvider = new Proxy(rawProvider, {
29821
+ get(target, prop, receiver) {
29822
+ if (prop === "request") {
29823
+ return async (args) => {
29824
+ const signingMethods = [
29825
+ "eth_sendTransaction",
29826
+ "eth_signTransaction",
29827
+ ];
29828
+ if (signingMethods.includes(args.method)) {
29829
+ try {
29830
+ HederaExtensionHelper.extensionOpen(extension.id);
29831
+ }
29832
+ catch { }
29833
+ }
29834
+ // forward request to original provider
29835
+ return target.request(args);
29836
+ };
29837
+ }
29838
+ // forward all other properties/methods
29839
+ return Reflect.get(target, prop, receiver);
29840
+ },
29841
+ });
29842
+ return proxiedProvider;
29843
+ }
29844
+ if (!provider_) {
29845
+ if (!providerPromise)
29846
+ providerPromise = initProvider();
29847
+ provider_ = await providerPromise;
29848
+ provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
29849
+ }
29850
+ return provider_;
29851
+ },
29852
+ async getChainId() {
29853
+ const provider = await this.getProvider();
29854
+ return provider.chainId;
29855
+ },
29856
+ async isAuthorized() {
29857
+ try {
29858
+ const accounts = await this.getAccounts();
29859
+ return accounts.length > 0;
29860
+ }
29861
+ catch {
29862
+ return false;
29863
+ }
29864
+ },
29865
+ onAccountsChanged(accounts) {
29866
+ if (accounts.length === 0)
29867
+ this.onDisconnect();
29868
+ else
29869
+ config.emitter.emit("change", {
29870
+ accounts: accounts.map((x) => viem.getAddress(x)),
29871
+ });
29872
+ },
29873
+ onChainChanged(chain) {
29874
+ const chainId = Number(chain);
29875
+ config.emitter.emit("change", { chainId });
29876
+ },
29877
+ async onConnect(connectInfo) {
29878
+ const chainId = Number(connectInfo.chainId);
29879
+ const accounts = await this.getAccounts();
29880
+ config.emitter.emit("connect", { accounts, chainId });
29881
+ },
29882
+ async onDisconnect() {
29883
+ config.emitter.emit("disconnect");
29884
+ const provider = await this.getProvider();
29885
+ if (disconnect) {
29886
+ provider.removeListener("disconnect", disconnect);
29887
+ disconnect = undefined;
29888
+ }
29889
+ if (sessionDelete) {
29890
+ provider.removeListener("session_delete", sessionDelete);
29891
+ sessionDelete = undefined;
29892
+ }
29893
+ if (!connect) {
29894
+ connect = this.onConnect.bind(this);
29895
+ provider.on("connect", connect);
29896
+ }
29897
+ },
29898
+ onDisplayUri(uri) {
29899
+ config.emitter.emit("message", { type: "display_uri", data: uri });
29900
+ HederaExtensionHelper.extensionConnect(extension.id, uri);
29901
+ },
29902
+ onSessionDelete() {
29903
+ this.onDisconnect();
29904
+ },
29905
+ }));
29906
+ }
29907
+
29908
+ const createWagmiConfig = (squidChains, hederaExtensions) => {
29909
+ const filteredEvmChains = squidChains.filter((chain) => chain.chainType === squidTypes.ChainType.EVM);
29910
+ if (filteredEvmChains.length === 0) {
29911
+ throw new Error("At least one chain is required");
29912
+ }
29913
+ const wagmiChains = filteredEvmChains.map((chain) => {
29914
+ return viem.defineChain({
29915
+ id: Number(chain.chainId),
29916
+ name: chain.networkName,
29917
+ nativeCurrency: {
29918
+ name: chain.nativeCurrency.name,
29919
+ symbol: chain.nativeCurrency.symbol,
29920
+ decimals: chain.nativeCurrency.decimals,
29921
+ },
29922
+ rpcUrls: {
29923
+ public: {
29924
+ http: [chain.rpc],
29925
+ },
29926
+ default: {
29927
+ http: [chain.rpc],
29928
+ },
29929
+ },
29930
+ });
29931
+ });
29932
+ const wcMetadata = {
29933
+ url: SQUID_METADATA.url,
29934
+ name: SQUID_METADATA.name,
29935
+ icons: [SQUID_METADATA.icon],
29936
+ description: SQUID_METADATA.description,
30130
29937
  };
30131
- };
30132
- const useNativeBalance = (chain) => {
30133
- const { connectedAddresses } = useWallet();
30134
- const { data: cosmosAddressForChain } = useCosmosForChain(chain);
30135
- // Cosmos is a special case because the address changes on every chain
30136
- // so we can't use the default cosmos connected address
30137
- const { balance: nativeCosmosBalance, isLoading: isCosmosLoading } = useCosmosNativeBalance({
30138
- address: cosmosAddressForChain,
30139
- chain,
30140
- });
30141
- const { balance: nativeEvmBalance, isLoading: isEvmLoading } = useEvmNativeBalance({ address: connectedAddresses[squidTypes.ChainType.EVM], chain });
30142
- const { balance: nativeBitcoinBalance, isLoading: isBitcoinLoading } = useBitcoinNativeBalance({
30143
- address: connectedAddresses[squidTypes.ChainType.BTC],
30144
- chain,
30145
- });
30146
- const { balance: nativeSolanaBalance, isLoading: isSolanaLoading } = useSolanaNativeBalance({
30147
- address: connectedAddresses[squidTypes.ChainType.SOLANA],
30148
- chain,
30149
- });
30150
- const { balance: nativeSuiBalance, isLoading: isSuiLoading } = useSuiNativeBalance({
30151
- address: connectedAddresses[squidTypes.ChainType.SUI],
30152
- chain,
30153
- });
30154
- const { balance: nativeXrplBalance, isLoading: isXrpLoading } = useXrplNativeBalance({
30155
- address: connectedAddresses[squidTypes.ChainType.XRPL],
30156
- chain,
30157
- });
30158
- const { balance: nativeStellarBalance, isLoading: isStellarLoading } = useStellarNativeBalance({
30159
- address: connectedAddresses[squidTypes.ChainType.STELLAR],
30160
- chain,
29938
+ return wagmi.createConfig({
29939
+ chains: wagmiChains,
29940
+ transports: Object.fromEntries(wagmiChains.map((chain) => [
29941
+ chain.id,
29942
+ wagmi.http(chain.rpcUrls.public.http[0] ?? ""),
29943
+ ])),
29944
+ connectors: [
29945
+ connectors.injected(),
29946
+ connectors.safe({
29947
+ allowedDomains: [/app.safe.global$/],
29948
+ }),
29949
+ connectors.metaMask({
29950
+ dappMetadata: {
29951
+ name: SQUID_METADATA.name,
29952
+ url: SQUID_METADATA.url,
29953
+ iconUrl: SQUID_METADATA.icon,
29954
+ },
29955
+ }),
29956
+ connectors.coinbaseWallet({
29957
+ appName: SQUID_METADATA.name,
29958
+ appLogoUrl: SQUID_METADATA.icon,
29959
+ }),
29960
+ connectors.walletConnect({
29961
+ projectId: WALLETCONNECT_PROJECT_ID,
29962
+ metadata: wcMetadata,
29963
+ }),
29964
+ ...hederaExtensions.map((extension) => hederaWalletConnect({
29965
+ projectId: WALLETCONNECT_PROJECT_ID,
29966
+ metadata: wcMetadata,
29967
+ extension,
29968
+ })),
29969
+ ],
30161
29970
  });
30162
- const { nativeBalance, nativeBalanceFormatted } = React.useMemo(() => {
30163
- let balance;
30164
- switch (chain?.chainType) {
30165
- case squidTypes.ChainType.EVM:
30166
- balance = nativeEvmBalance;
30167
- break;
30168
- case squidTypes.ChainType.COSMOS:
30169
- balance = nativeCosmosBalance;
30170
- break;
30171
- case squidTypes.ChainType.BTC:
30172
- balance = nativeBitcoinBalance;
30173
- break;
30174
- case squidTypes.ChainType.SOLANA:
30175
- balance = nativeSolanaBalance;
30176
- break;
30177
- case squidTypes.ChainType.SUI:
30178
- balance = nativeSuiBalance;
30179
- break;
30180
- case squidTypes.ChainType.XRPL:
30181
- balance = nativeXrplBalance;
30182
- break;
30183
- case squidTypes.ChainType.STELLAR:
30184
- balance = nativeStellarBalance;
30185
- }
30186
- const balanceFormatted = !!balance
30187
- ? formatBNToReadable(balance.value, balance.decimals)
30188
- : undefined;
30189
- return {
30190
- nativeBalance: balance,
30191
- nativeBalanceFormatted: balanceFormatted,
30192
- };
30193
- }, [
30194
- chain?.chainType,
30195
- nativeEvmBalance,
30196
- nativeCosmosBalance,
30197
- nativeBitcoinBalance,
30198
- nativeSolanaBalance,
30199
- nativeSuiBalance,
30200
- nativeXrplBalance,
30201
- nativeStellarBalance,
30202
- ]);
30203
- const isLoading = React.useMemo(() => {
29971
+ };
29972
+ // Taken from wagmi docs
29973
+ // https://wagmi.sh/react/guides/ethers
29974
+ function clientToSigner(client) {
29975
+ const { account, chain, transport } = client;
29976
+ if (!account || !chain || !transport) {
29977
+ return undefined;
29978
+ }
29979
+ const network = {
29980
+ chainId: chain.id,
29981
+ name: chain.name,
29982
+ ensAddress: chain.contracts?.ensRegistry?.address,
29983
+ };
29984
+ const provider = new ethers.BrowserProvider(transport, network);
29985
+ const signer = new ethers.JsonRpcSigner(provider, account.address);
29986
+ return signer;
29987
+ }
29988
+
29989
+ function useEvmSigner({ chainId }) {
29990
+ const { connector } = wagmi.useAccount();
29991
+ const { data: client } = wagmi.useWalletClient({ chainId, connector });
29992
+ const signer = React.useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
29993
+ return { signer };
29994
+ }
29995
+ const useSigner = ({ chain }) => {
29996
+ const evmChainId = chain?.chainType === squidTypes.ChainType.EVM ? Number(chain.chainId) : undefined;
29997
+ // EVM and Cosmos need a different signer for each chain
29998
+ // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
29999
+ const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
30000
+ const { signer: cosmosSigner } = useCosmosSigner({ chain });
30001
+ const { signer: solanaSigner } = useSolanaContext();
30002
+ const { signer: bitcoinSigner } = useBitcoinContext();
30003
+ const { signer: suiSigner } = useSuiContext();
30004
+ const { signer: xrplSigner } = useXrplContext();
30005
+ const { signer: stellarSigner } = useStellarContext();
30006
+ const isEvmSignerReady = !!evmSigner;
30007
+ const isSolanaSignerReady = !!solanaSigner;
30008
+ const isCosmosSignerReady = !!cosmosSigner;
30009
+ const isBitcoinSignerReady = !!bitcoinSigner;
30010
+ const isSuiSignerReady = !!suiSigner;
30011
+ const isXrplSignerReady = !!xrplSigner;
30012
+ const isStellarSignerReady = !!stellarSigner;
30013
+ const isSignerReady = React.useMemo(() => {
30204
30014
  if (!chain?.chainType)
30205
30015
  return false;
30206
30016
  switch (chain.chainType) {
30207
30017
  case squidTypes.ChainType.EVM:
30208
- return isEvmLoading;
30018
+ return isEvmSignerReady;
30209
30019
  case squidTypes.ChainType.COSMOS:
30210
- return isCosmosLoading;
30020
+ return isCosmosSignerReady;
30211
30021
  case squidTypes.ChainType.BTC:
30212
- return isBitcoinLoading;
30022
+ return isBitcoinSignerReady;
30213
30023
  case squidTypes.ChainType.SOLANA:
30214
- return isSolanaLoading;
30024
+ return isSolanaSignerReady;
30215
30025
  case squidTypes.ChainType.SUI:
30216
- return isSuiLoading;
30026
+ return isSuiSignerReady;
30217
30027
  case squidTypes.ChainType.XRPL:
30218
- return isXrpLoading;
30028
+ return isXrplSignerReady;
30219
30029
  case squidTypes.ChainType.STELLAR:
30220
- return isStellarLoading;
30030
+ return isStellarSignerReady;
30221
30031
  }
30222
30032
  }, [
30223
30033
  chain?.chainType,
30224
- isEvmLoading,
30225
- isCosmosLoading,
30226
- isBitcoinLoading,
30227
- isSolanaLoading,
30228
- isSuiLoading,
30229
- isXrpLoading,
30230
- isStellarLoading,
30034
+ isEvmSignerReady,
30035
+ isCosmosSignerReady,
30036
+ isBitcoinSignerReady,
30037
+ isSolanaSignerReady,
30038
+ isSuiSignerReady,
30039
+ isXrplSignerReady,
30040
+ isStellarSignerReady,
30231
30041
  ]);
30232
- return { nativeBalance, nativeBalanceFormatted, isLoading };
30042
+ return {
30043
+ isSignerReady,
30044
+ evmSigner,
30045
+ cosmosSigner,
30046
+ bitcoinSigner,
30047
+ solanaSigner,
30048
+ suiSigner,
30049
+ xrplSigner,
30050
+ stellarSigner,
30051
+ };
30233
30052
  };
30234
30053
 
30235
- function useHederaAccountActivation({ address, chain, token }) {
30236
- const { balance: destNativeEvmBalance } = useEvmNativeBalance({
30237
- chain,
30238
- address,
30054
+ function useHederaTokenAssociations({ address, chain, token }) {
30055
+ const publicClient = wagmi.usePublicClient({
30056
+ chainId: Number(CHAIN_IDS.HEDERA),
30057
+ });
30058
+ const { evmSigner } = useSigner({ chain });
30059
+ const queryClient = reactQuery.useQueryClient();
30060
+ /**
30061
+ * Creates a token association transaction where the destination account authorizes to receive the given token
30062
+ */
30063
+ const associateToken = reactQuery.useMutation({
30064
+ mutationFn: async () => {
30065
+ try {
30066
+ if (!evmSigner) {
30067
+ throw new Error("EVM signer not found");
30068
+ }
30069
+ if (chain?.chainId !== CHAIN_IDS.HEDERA ||
30070
+ token?.chainId !== CHAIN_IDS.HEDERA) {
30071
+ throw new Error("Chain and token to associate must be on Hedera");
30072
+ }
30073
+ const tokenAddress = parseEvmAddress(token.address);
30074
+ if (!tokenAddress) {
30075
+ throw new Error("Invalid token address");
30076
+ }
30077
+ const userAddress = parseEvmAddress(address);
30078
+ if (!userAddress) {
30079
+ throw new Error("Invalid user address");
30080
+ }
30081
+ const encodedData = viem.encodeFunctionData({
30082
+ abi: hrc20,
30083
+ functionName: "associate",
30084
+ args: [],
30085
+ });
30086
+ const txRes = await evmSigner.sendTransaction({
30087
+ data: encodedData,
30088
+ to: tokenAddress,
30089
+ chainId: chain.chainId,
30090
+ });
30091
+ const receipt = await txRes.wait();
30092
+ if (receipt?.status !== 1) {
30093
+ throw new Error(`Transaction failed with status: ${receipt?.status}`);
30094
+ }
30095
+ return true;
30096
+ }
30097
+ catch (error) {
30098
+ console.error("Error associating Hedera token:", error);
30099
+ return false;
30100
+ }
30101
+ },
30102
+ async onSuccess() {
30103
+ queryClient.refetchQueries({
30104
+ queryKey: getPrefixKey(exports.QueryKeys.IsHederaTokenAssociated),
30105
+ });
30106
+ },
30107
+ });
30108
+ /**
30109
+ * Checks if the destination account has associated the given token.
30110
+ *
30111
+ * Hedera requires accounts to associate a token before being able to receive it.
30112
+ *
30113
+ * Accounts which have max. associations set to -1 can receive any token without previous association
30114
+ */
30115
+ const isTokenAssociated = reactQuery.useQuery({
30116
+ queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
30117
+ queryFn: async () => {
30118
+ if (token?.chainId !== CHAIN_IDS.HEDERA) {
30119
+ return true;
30120
+ }
30121
+ // The native HBAR token doesn't need an association
30122
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
30123
+ return true;
30124
+ }
30125
+ if (!chain || !address || !publicClient) {
30126
+ throw new Error("Missing required parameters");
30127
+ }
30128
+ // TODO: update types
30129
+ const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
30130
+ if (!apiUrl) {
30131
+ throw new Error("Missing Hedera mirror node URL in chain config");
30132
+ }
30133
+ const hederaApiClient = new HederaApiClient(apiUrl);
30134
+ return hederaApiClient.isTokenAssociated({
30135
+ address,
30136
+ token,
30137
+ });
30138
+ },
30139
+ enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
30239
30140
  });
30240
- const isHederaAccountActivated = React.useMemo(() => {
30241
- if (token?.chainId !== CHAIN_IDS.HEDERA)
30242
- return true;
30243
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
30244
- return true;
30245
- return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
30246
- }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
30247
30141
  return {
30248
- isHederaAccountActivated,
30142
+ isTokenAssociated,
30143
+ associateToken,
30144
+ };
30145
+ }
30146
+
30147
+ const useKeyboardNavigation = ({ onEscape }) => {
30148
+ const onKeyDown = React.useCallback((event) => {
30149
+ if (event.key === "Escape") {
30150
+ onEscape?.();
30151
+ return;
30152
+ }
30153
+ }, [onEscape]);
30154
+ React.useEffect(() => {
30155
+ document.addEventListener("keydown", onKeyDown, false);
30156
+ return () => {
30157
+ document.removeEventListener("keydown", onKeyDown, false);
30158
+ };
30159
+ }, [onKeyDown]);
30160
+ };
30161
+
30162
+ const useSquidQueryClient = () => {
30163
+ const queryClient = reactQuery.useQueryClient();
30164
+ const invalidateQueries = (key) => {
30165
+ const prefixKey = getPrefixKey(key);
30166
+ queryClient.invalidateQueries(prefixKey);
30167
+ };
30168
+ const refetchQueries = (key) => {
30169
+ const prefixKey = getPrefixKey(key);
30170
+ queryClient.refetchQueries(prefixKey);
30171
+ };
30172
+ const invalidateAndRefetchQueries = (key) => {
30173
+ invalidateQueries(key);
30174
+ refetchQueries(key);
30175
+ };
30176
+ return {
30177
+ invalidateQueries,
30178
+ refetchQueries,
30179
+ invalidateAndRefetchQueries,
30180
+ };
30181
+ };
30182
+
30183
+ class StellarApiClient {
30184
+ apiUrl;
30185
+ constructor(apiUrl) {
30186
+ this.apiUrl = apiUrl;
30187
+ }
30188
+ async getBaseReserve() {
30189
+ const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
30190
+ if (!response.ok) {
30191
+ throw new Error(`Failed to fetch ledgers: ${response.status}`);
30192
+ }
30193
+ const ledgers = await response.json();
30194
+ const latestLedger = ledgers?._embedded.records?.[0];
30195
+ if (latestLedger?.base_reserve_in_stroops == null) {
30196
+ throw new Error("Invalid ledger data");
30197
+ }
30198
+ const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
30199
+ return baseReserveBn;
30200
+ }
30201
+ }
30202
+
30203
+ const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
30204
+ function useStellarAccountActivation({ address, chain, token, }) {
30205
+ /**
30206
+ * Checks if the destination account exists on the Stellar network
30207
+ * Stellar accounts need to have a minimum balance before they can receive payments
30208
+ */
30209
+ const accountActivatedInfo = reactQuery.useQuery({
30210
+ queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
30211
+ queryFn: async () => {
30212
+ if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
30213
+ token?.type !== squidTypes.ChainType.STELLAR) {
30214
+ return null;
30215
+ }
30216
+ if (!address) {
30217
+ throw new Error("Destination address is required");
30218
+ }
30219
+ // TODO: update types
30220
+ const [horizonApiUrl] = chain?.horizonRpcList;
30221
+ if (typeof horizonApiUrl !== "string") {
30222
+ throw new Error("Invalid Horizon API URL");
30223
+ }
30224
+ const stellarApiClient = new StellarApiClient(horizonApiUrl);
30225
+ const reserveBase = await stellarApiClient.getBaseReserve();
30226
+ // Stellar accounts require two base reserves to be activated
30227
+ // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
30228
+ const accountReserveBase = reserveBase * BigInt(2);
30229
+ const stellarRpcClient = await getClient(chain);
30230
+ const isActivated = await stellarRpcClient.isAccountActivated(address);
30231
+ return {
30232
+ isActivated,
30233
+ reserveBaseBn: accountReserveBase,
30234
+ };
30235
+ },
30236
+ enabled: !!address && chain?.chainType === squidTypes.ChainType.STELLAR,
30237
+ refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
30238
+ refetchOnWindowFocus: true,
30239
+ });
30240
+ return {
30241
+ accountActivatedInfo,
30249
30242
  };
30250
30243
  }
30251
30244
 
@@ -30773,6 +30766,161 @@ const useSingleTokenPrice = (tokenData) => {
30773
30766
  return { tokenPrice, getUSDValue };
30774
30767
  };
30775
30768
 
30769
+ const TEMPO_FEE_MANAGER_ADDRESS = "0xfeec000000000000000000000000000000000000";
30770
+ const feeManagerAbi = [
30771
+ {
30772
+ name: "userTokens",
30773
+ type: "function",
30774
+ stateMutability: "view",
30775
+ inputs: [{ type: "address", name: "user" }],
30776
+ outputs: [{ type: "address" }],
30777
+ },
30778
+ ];
30779
+ const isTempoChain = (chainId) => chainId === CHAIN_IDS.TEMPO;
30780
+ /**
30781
+ * Convert a fee amount from virtual USD (18 decimals, attodollars)
30782
+ * to 6-decimal TIP-20 stablecoin units (microdollars).
30783
+ * All TIP-20 tokens on Tempo have 6 decimals.
30784
+ */
30785
+ const convertTempoFeeToStablecoinUnits = (feeIn18Dec) => feeIn18Dec / BigInt(10 ** 12);
30786
+ /**
30787
+ * Pure function that derives TempoFeeData from on-chain query results.
30788
+ * Extracted from useTempoFeeCheck so the branching logic is unit-testable
30789
+ * without mocking React hooks.
30790
+ */
30791
+ function resolveTempoFeeData({ hasAccountFeePreference, accountGasTokenAddress, accountGasTokenBalance, fromTokenAddress, fromTokenBalance, }) {
30792
+ if (hasAccountFeePreference && accountGasTokenAddress) {
30793
+ const gasTokenIsFromToken = !!fromTokenAddress &&
30794
+ accountGasTokenAddress.toLowerCase() === fromTokenAddress.toLowerCase();
30795
+ return {
30796
+ gasTokenBalance: accountGasTokenBalance ?? BigInt(0),
30797
+ gasTokenIsFromToken,
30798
+ gasTokenAddress: accountGasTokenAddress,
30799
+ };
30800
+ }
30801
+ if (fromTokenAddress != null && fromTokenBalance != null) {
30802
+ return {
30803
+ gasTokenBalance: fromTokenBalance,
30804
+ gasTokenIsFromToken: true,
30805
+ gasTokenAddress: fromTokenAddress,
30806
+ };
30807
+ }
30808
+ return null;
30809
+ }
30810
+ /**
30811
+ * Resolves the token that pays network gas fees for a given chain and transaction context.
30812
+ * For Tempo chains, this is the dynamically resolved gas token (from tempoFeeData).
30813
+ * For all other chains, this is the static native gas token.
30814
+ */
30815
+ function resolveGasToken({ nativeToken, chain, tempoFeeData, evmTokens, }) {
30816
+ if (!chain)
30817
+ return undefined;
30818
+ if (isTempoChain(chain.chainId)) {
30819
+ if (!tempoFeeData)
30820
+ return undefined;
30821
+ return evmTokens.find((t) => t.chainId === chain.chainId &&
30822
+ t.address.toLowerCase() === tempoFeeData.gasTokenAddress.toLowerCase());
30823
+ }
30824
+ return nativeToken;
30825
+ }
30826
+ /**
30827
+ * Resolves chain-agnostic fee parameters from chain-specific inputs.
30828
+ * This is the single place that knows about Tempo vs. native-token fee semantics.
30829
+ *
30830
+ * Safe defaults when tempoFeeData is missing on a Tempo chain: balance=0 and
30831
+ * fromTokenPaysNetworkFee=false, so the UI blocks rather than misleads.
30832
+ */
30833
+ function resolveChainFeeParams({ fromChainId, tempoFeeData, isFromTokenNative, nativeBalanceWei, }) {
30834
+ if (isTempoChain(fromChainId)) {
30835
+ if (!tempoFeeData) {
30836
+ return null;
30837
+ }
30838
+ return {
30839
+ fromTokenPaysNetworkFee: tempoFeeData?.gasTokenIsFromToken ?? false,
30840
+ gasTokenBalanceWei: tempoFeeData?.gasTokenBalance ?? BigInt(0),
30841
+ normalizeFee: convertTempoFeeToStablecoinUnits,
30842
+ };
30843
+ }
30844
+ return {
30845
+ fromTokenPaysNetworkFee: isFromTokenNative,
30846
+ gasTokenBalanceWei: nativeBalanceWei,
30847
+ normalizeFee: (fee) => fee,
30848
+ };
30849
+ }
30850
+
30851
+ const BALANCE_QUERY_STALE_TIME = 10_000;
30852
+ /**
30853
+ * Returns raw on-chain gas token data for Tempo chains, or null when the source
30854
+ * chain is not Tempo.
30855
+ */
30856
+ const useTempoFeeCheck = ({ fromChain, fromToken, }) => {
30857
+ const { connectedAddresses } = useWallet();
30858
+ const evmAddress = parseEvmAddress(connectedAddresses[squidTypes.ChainType.EVM]);
30859
+ const isTempo = isTempoChain(fromChain?.chainId);
30860
+ const chainId = Number(fromChain?.chainId);
30861
+ // Read account-level gas token preference from FeeManager precompile
30862
+ // See docs: https://docs.tempo.xyz/protocol/fees/spec-fee#account-level
30863
+ const { data: accountGasTokenAddress } = wagmi.useReadContract({
30864
+ address: TEMPO_FEE_MANAGER_ADDRESS,
30865
+ abi: feeManagerAbi,
30866
+ functionName: "userTokens",
30867
+ args: evmAddress ? [evmAddress] : undefined,
30868
+ chainId,
30869
+ query: {
30870
+ enabled: isTempo && !!evmAddress,
30871
+ staleTime: 30_000,
30872
+ },
30873
+ });
30874
+ const hasAccountFeePreference = !!accountGasTokenAddress &&
30875
+ // By default, the zero address is returned when the user has no preference set
30876
+ viem.zeroAddress.toLowerCase() !== accountGasTokenAddress.toLowerCase();
30877
+ // Fetch balance of the account-preferred gas token (if set)
30878
+ const { data: accountGasTokenBalance } = wagmi.useBalance({
30879
+ address: evmAddress,
30880
+ token: hasAccountFeePreference ? accountGasTokenAddress : undefined,
30881
+ chainId,
30882
+ query: {
30883
+ enabled: isTempo && hasAccountFeePreference && !!evmAddress,
30884
+ staleTime: BALANCE_QUERY_STALE_TIME,
30885
+ },
30886
+ });
30887
+ const fromTokenAddress = parseEvmAddress(fromToken?.address);
30888
+ const { data: fromTokenBalance } = wagmi.useBalance({
30889
+ address: evmAddress,
30890
+ token: fromTokenAddress,
30891
+ chainId,
30892
+ query: {
30893
+ enabled: isTempo && !!evmAddress,
30894
+ staleTime: BALANCE_QUERY_STALE_TIME,
30895
+ },
30896
+ });
30897
+ if (!isTempo)
30898
+ return null;
30899
+ return resolveTempoFeeData({
30900
+ hasAccountFeePreference,
30901
+ accountGasTokenAddress,
30902
+ accountGasTokenBalance: accountGasTokenBalance?.value,
30903
+ fromTokenAddress,
30904
+ fromTokenBalance: fromTokenBalance?.value,
30905
+ });
30906
+ };
30907
+
30908
+ /**
30909
+ * Resolves the token that pays network gas fees for a given chain
30910
+ */
30911
+ function useSourceChainGasToken({ fromChain, fromToken, }) {
30912
+ const { nativeToken } = useNativeTokenForChain(fromChain);
30913
+ const { evmTokens } = useSquidTokens();
30914
+ const tempoFeeData = useTempoFeeCheck({ fromChain, fromToken });
30915
+ const gasToken = React.useMemo(() => resolveGasToken({
30916
+ nativeToken,
30917
+ chain: fromChain,
30918
+ tempoFeeData,
30919
+ evmTokens,
30920
+ }), [nativeToken, fromChain, tempoFeeData, evmTokens]);
30921
+ return { gasToken };
30922
+ }
30923
+
30776
30924
  const MAX_COINGECKO_QUERY_TOKENS = 100;
30777
30925
  const fetchHistoricalData = async (coingeckoId, timeFrame) => {
30778
30926
  const now = Math.floor(Date.now() / 1000);
@@ -30923,24 +31071,20 @@ const calculateTotalNativeFees = ({ expressFeeCost, firstFeeCost, firstGasCost,
30923
31071
  (sameTokenBetweenFees
30924
31072
  ? BigInt(firstFeeCost?.amount ?? "0") + BigInt(firstGasCost?.amount ?? "0")
30925
31073
  : BigInt(firstGasCost?.amount ?? "0"));
30926
- const isFromBalanceEnoughToSwap = ({ isFromTokenNative, fromAmount, totalFeesInNativeTokenPlusRatio, nativeTokenBalanceFromChainWei, }) => {
30927
- const fromAmountBigInt = BigInt(fromAmount ?? "0");
30928
- const totalFeesInNativeTokenPlusRatioBigInt = totalFeesInNativeTokenPlusRatio;
30929
- const nativeTokenBalanceFromChainWeiBigInt = nativeTokenBalanceFromChainWei;
30930
- return isFromTokenNative
30931
- ? fromAmountBigInt + totalFeesInNativeTokenPlusRatioBigInt <=
30932
- nativeTokenBalanceFromChainWeiBigInt
30933
- : totalFeesInNativeTokenPlusRatioBigInt <=
30934
- nativeTokenBalanceFromChainWeiBigInt;
30935
- };
30936
- const calculateMinAmountValueWarnMsg = ({ isFromTokenNative, nativeTokenBalanceFromChainWei, sourceChainNativeTokenDecimals, totalFeesInNativeTokenPlusRatio, }) => isFromTokenNative
30937
- ? (() => {
30938
- const minAmount = nativeTokenBalanceFromChainWei - totalFeesInNativeTokenPlusRatio;
30939
- return minAmount > BigInt(0)
30940
- ? formatBNToReadable(minAmount, sourceChainNativeTokenDecimals)
30941
- : "0";
30942
- })()
30943
- : undefined;
31074
+ const isGasBalanceEnough = ({ fromAmount, networkFeesWei, chainFeeParams, }) => {
31075
+ if (!chainFeeParams)
31076
+ return false;
31077
+ if (chainFeeParams.fromTokenPaysNetworkFee) {
31078
+ return (BigInt(fromAmount ?? "0") + networkFeesWei <=
31079
+ chainFeeParams.gasTokenBalanceWei);
31080
+ }
31081
+ return networkFeesWei <= chainFeeParams.gasTokenBalanceWei;
31082
+ };
31083
+ const calculateMinAmountValueWarnMsg = ({ networkFeesWei, chainFeeParams, }) => {
31084
+ if (!chainFeeParams?.fromTokenPaysNetworkFee)
31085
+ return undefined;
31086
+ return chainFeeParams.gasTokenBalanceWei - networkFeesWei;
31087
+ };
30944
31088
  /**
30945
31089
  * Calculates the estimated duration of a route
30946
31090
  *
@@ -30979,12 +31123,10 @@ const formatEstimatedRouteDuration = ({ estimatedRouteDuration, isSingleChainRou
30979
31123
  * @returns {Object} An object containing various estimate results and calculations, including token information,
30980
31124
  * amounts, fees, gas costs, and other relevant data for the transaction.
30981
31125
  */
30982
- const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, nativeTokenBalanceFromChainWei, }) => {
31126
+ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, nativeTokenBalanceFromChainWei, tempoFeeData, }) => {
30983
31127
  const fromToken = findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken);
30984
31128
  const fromAmount = squidRoute?.estimate?.fromAmount;
30985
31129
  const fromAmountFormatted = formatAmount(fromAmount, fromToken?.decimals);
30986
- const sourceChainNativeToken = findNativeToken(tokens, fromChain);
30987
- const destChainNativeToken = findNativeToken(tokens, toChain);
30988
31130
  const toAmountUSD = squidRoute?.estimate?.toAmountUSD;
30989
31131
  const exchangeRate = squidRoute?.estimate.exchangeRate ?? "0";
30990
31132
  const toAmountMinUSD = squidRoute?.estimate.toAmountMinUSD ?? "0";
@@ -31003,10 +31145,6 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31003
31145
  const expectedGasRefundCostUSD = convertTokenAmountToUSD(formatBNToReadable(expectedGasRefundCost, firstFeeCost?.token.decimals ?? 18), firstFeeCost?.token.usdPrice ?? "0");
31004
31146
  const sameTokenBetweenFees = firstFeeCost?.token.address === firstGasCost?.token.address &&
31005
31147
  firstFeeCost?.token.chainId === firstGasCost?.token.chainId;
31006
- const isFromTokenNative =
31007
- // TODO: temporary fix, currently nativeCurrency.symbol is not always in uppercase
31008
- fromToken?.symbol.toUpperCase() ===
31009
- fromChain?.nativeCurrency.symbol.toUpperCase();
31010
31148
  const totalNativeFees = calculateTotalNativeFees({
31011
31149
  expressFeeCost,
31012
31150
  firstFeeCost,
@@ -31014,19 +31152,32 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31014
31152
  sameTokenBetweenFees,
31015
31153
  });
31016
31154
  const totalFeesInNativeTokenPlusRatio = (totalNativeFees * BigInt(110)) / BigInt(100);
31017
- const fromBalanceEnoughToSwap = isFromBalanceEnoughToSwap({
31018
- isFromTokenNative,
31155
+ const isNativeTokenFromChain =
31156
+ // TODO: nativeCurrency.symbol is not always uppercase
31157
+ fromToken?.symbol.toUpperCase() ===
31158
+ fromChain?.nativeCurrency.symbol.toUpperCase();
31159
+ const chainFeeParams = resolveChainFeeParams({
31160
+ tempoFeeData,
31161
+ isFromTokenNative: isNativeTokenFromChain,
31162
+ nativeBalanceWei: nativeTokenBalanceFromChainWei,
31163
+ fromChainId: fromChain?.chainId,
31164
+ });
31165
+ const networkFeesWei = chainFeeParams?.normalizeFee(totalFeesInNativeTokenPlusRatio) ?? BigInt(0);
31166
+ const gasBalanceEnough = isGasBalanceEnough({
31167
+ chainFeeParams,
31019
31168
  fromAmount,
31020
- fromTokenDecimals: fromToken?.decimals,
31021
- totalFeesInNativeTokenPlusRatio,
31022
- nativeTokenBalanceFromChainWei,
31169
+ networkFeesWei,
31023
31170
  });
31024
- const minAmountValueWarnMsg = calculateMinAmountValueWarnMsg({
31025
- isFromTokenNative,
31026
- nativeTokenBalanceFromChainWei,
31027
- sourceChainNativeTokenDecimals: sourceChainNativeToken?.decimals,
31028
- totalFeesInNativeTokenPlusRatio,
31171
+ const minAmount = calculateMinAmountValueWarnMsg({
31172
+ chainFeeParams,
31173
+ // gasTokenDecimals: chainFeeParams,
31174
+ networkFeesWei,
31029
31175
  });
31176
+ const minAmountValueWarnMsg = minAmount
31177
+ ? formatBNToReadable(minAmount, chainFeeParams?.fromTokenPaysNetworkFee
31178
+ ? fromToken?.decimals
31179
+ : undefined)
31180
+ : undefined;
31030
31181
  const isSingleChainRoute = fromChain?.chainId === toChain?.chainId;
31031
31182
  const estimatedRouteDuration = formatEstimatedRouteDuration({
31032
31183
  estimatedRouteDuration: squidRoute?.estimate?.estimatedRouteDuration,
@@ -31036,8 +31187,6 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31036
31187
  fromToken,
31037
31188
  fromAmount,
31038
31189
  fromAmountFormatted,
31039
- sourceChainNativeToken,
31040
- destChainNativeToken,
31041
31190
  toAmountUSD,
31042
31191
  exchangeRate,
31043
31192
  toAmountMinUSD,
@@ -31052,10 +31201,10 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31052
31201
  expectedGasRefundCost,
31053
31202
  expectedGasRefundCostUSD,
31054
31203
  sameTokenBetweenFees,
31055
- isFromTokenNative,
31204
+ fromTokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31056
31205
  totalNativeFees,
31057
31206
  totalFeesInNativeTokenPlusRatio,
31058
- fromBalanceEnoughToSwap,
31207
+ gasBalanceEnough,
31059
31208
  minAmountValueWarnMsg,
31060
31209
  estimatedRouteDuration,
31061
31210
  };
@@ -31091,28 +31240,6 @@ const calculateTotalWithRefundEstimate = (allFeeCosts, expectedGasRefundCost, ge
31091
31240
  feeToken: firstFeeCost?.token,
31092
31241
  };
31093
31242
  };
31094
- /**
31095
- * Calculates the proposed gas amount for the destination chain.
31096
- *
31097
- * @param destChainNativeToken - The symbol of the native token for the destination chain.
31098
- * @returns An object containing the proposed gas amount value and the currency symbol.
31099
- */
31100
- const getProposedGasDestinationAmount = (destChainNativeToken) => {
31101
- const gasAmounts = {
31102
- GLMR: 5.289,
31103
- ETH: 0.0009,
31104
- AVAX: 0.115,
31105
- BNB: 0.00425,
31106
- FTM: 4.45,
31107
- CELO: 3.052,
31108
- KAVA: 2.339,
31109
- MATIC: 1.795,
31110
- };
31111
- return {
31112
- value: gasAmounts[destChainNativeToken ?? ""] ?? 0,
31113
- currency: destChainNativeToken,
31114
- };
31115
- };
31116
31243
 
31117
31244
  function useSendTransactionGas({ chain, token, from, }) {
31118
31245
  return reactQuery.useQuery({
@@ -31134,7 +31261,11 @@ function useSendTransactionGas({ chain, token, from, }) {
31134
31261
  // Some RPC providers require the sender to have enough balance, otherwise estimation reverts
31135
31262
  // so we'll try to use the user provided address when possible
31136
31263
  const sender = from || dummyAddress;
31137
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
31264
+ // Tempo has no native token, so `value` transfers are rejected by the EVM.
31265
+ // We can simulate an ERC20 transfer instead
31266
+ const supportsNativeTransfers = chain.chainId !== CHAIN_IDS.TEMPO;
31267
+ const isNativeToken = token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase();
31268
+ if (isNativeToken && supportsNativeTransfers) {
31138
31269
  const gas = await client.estimateGas({
31139
31270
  from: sender,
31140
31271
  to: dummyAddress,
@@ -31154,7 +31285,14 @@ function useSendTransactionGas({ chain, token, from, }) {
31154
31285
  data,
31155
31286
  chainId: chain.chainId,
31156
31287
  });
31157
- return gas * maxFeePerGas;
31288
+ const feeWei = gas * maxFeePerGas;
31289
+ // Tempo fees are denominated in virtual USD (18 dec, 1e-18 USD units).
31290
+ // Convert to 6-dec stablecoin units so all callers receive a
31291
+ // consistent unit without needing to know about the chain.
31292
+ if (chain.chainId === CHAIN_IDS.TEMPO) {
31293
+ return convertTempoFeeToStablecoinUnits(feeWei);
31294
+ }
31295
+ return feeWei;
31158
31296
  }
31159
31297
  case squidTypes.ChainType.COSMOS: {
31160
31298
  // TODO: get gas estimation from backend
@@ -31202,18 +31340,24 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31202
31340
  from,
31203
31341
  });
31204
31342
  const { nativeBalance } = useNativeBalance(chain);
31205
- const isNativeBalanceEnoughToPayGasFees = React.useMemo(() => {
31206
- if (!nativeBalance?.value || !amount || !token?.decimals) {
31343
+ const tempoFeeData = useTempoFeeCheck({ fromChain: chain, fromToken: token });
31344
+ const chainFeeParams = resolveChainFeeParams({
31345
+ fromChainId: chain?.chainId,
31346
+ tempoFeeData,
31347
+ isFromTokenNative: token != null &&
31348
+ token.address.toLowerCase() ===
31349
+ chainTypeToNativeTokenAddressMap[token.type]?.toLowerCase(),
31350
+ nativeBalanceWei: nativeBalance?.value ?? BigInt(0),
31351
+ });
31352
+ const gasBalanceEnough = React.useMemo(() => {
31353
+ if (!amount || !token?.decimals)
31207
31354
  return false;
31208
- }
31209
- const isTokenNative = token.address.toLowerCase() ===
31210
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31211
- if (isTokenNative) {
31212
- return (parseToBigInt(amount, token.decimals) + estimatedGas <=
31213
- nativeBalance.value);
31214
- }
31215
- return estimatedGas <= nativeBalance.value;
31216
- }, [amount, estimatedGas, nativeBalance?.value, token]);
31355
+ return isGasBalanceEnough({
31356
+ fromAmount: parseToBigInt(amount, token.decimals).toString(),
31357
+ networkFeesWei: estimatedGas,
31358
+ chainFeeParams,
31359
+ });
31360
+ }, [amount, estimatedGas, token, chainFeeParams]);
31217
31361
  const isBalanceEnough = React.useMemo(() => {
31218
31362
  if (token?.decimals == null || !balance || !amount)
31219
31363
  return false;
@@ -31221,28 +31365,30 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31221
31365
  parseToBigInt(amount, token.decimals));
31222
31366
  }, [amount, balance, token?.decimals]);
31223
31367
  const minAmountValueWarnMsg = React.useMemo(() => {
31224
- if (!token?.address || !nativeBalance?.value || !estimatedGas)
31368
+ if (!token?.address || !estimatedGas)
31369
+ return undefined;
31370
+ if (!chainFeeParams)
31225
31371
  return undefined;
31226
- const isFromTokenNative = token.address.toLowerCase() ===
31227
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31228
- return calculateMinAmountValueWarnMsg({
31229
- isFromTokenNative,
31230
- nativeTokenBalanceFromChainWei: nativeBalance.value,
31231
- sourceChainNativeTokenDecimals: nativeBalance.decimals,
31232
- totalFeesInNativeTokenPlusRatio: estimatedGas,
31372
+ const minAmount = calculateMinAmountValueWarnMsg({
31373
+ chainFeeParams,
31374
+ // gasTokenDecimals: chainFeeParams.fromTokenPaysNetworkFee
31375
+ // ? token.decimals
31376
+ // : undefined,
31377
+ networkFeesWei: estimatedGas,
31233
31378
  });
31234
- }, [
31235
- estimatedGas,
31236
- nativeBalance?.decimals,
31237
- nativeBalance?.value,
31238
- token?.address,
31239
- token?.type,
31240
- ]);
31379
+ // return minAmount > BigInt(0)
31380
+ // ? formatBNToReadable(minAmount, gasTokenDecimals)
31381
+ // : "0";
31382
+ if (!minAmount)
31383
+ return undefined;
31384
+ return formatBNToReadable(minAmount, chainFeeParams.fromTokenPaysNetworkFee ? token.decimals : undefined);
31385
+ }, [estimatedGas, token, chainFeeParams]);
31241
31386
  return {
31242
31387
  estimatedGas,
31243
31388
  isBalanceEnough,
31389
+ tokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31244
31390
  isLoading,
31245
- isNativeBalanceEnoughToPayGasFees,
31391
+ isGasBalanceEnough: gasBalanceEnough,
31246
31392
  minAmountValueWarnMsg,
31247
31393
  };
31248
31394
  }
@@ -32128,7 +32274,7 @@ const useApproval = ({ squidRoute, }) => {
32128
32274
  const publicClient = wagmi.usePublicClient();
32129
32275
  const queryClient = reactQuery.useQueryClient();
32130
32276
  const squid = useSquidStore((state) => state.squid);
32131
- const { fromChain, fromToken, fromAmount, toAmount, isSameChain } = useSwap();
32277
+ const { fromChain, fromToken, fromPrice, isSameChain } = useSwap();
32132
32278
  const { evmSigner } = useSigner({ chain: fromChain });
32133
32279
  const { connector: activeConnector } = wagmi.useAccount();
32134
32280
  const { getChainType } = useSquidChains();
@@ -32301,7 +32447,7 @@ const useApproval = ({ squidRoute, }) => {
32301
32447
  // This is to ensure we're using the latest expiry timestamp
32302
32448
  if (squidRoute) {
32303
32449
  queryClient.refetchQueries({
32304
- queryKey: keys().transaction(squidRoute.params.fromChain, squidRoute.params.toChain, squidRoute.params.toToken, squidRoute.params.fromToken, fromAmount, toAmount, 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,
32450
+ 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,
32305
32451
  // TODO: update types
32306
32452
  squidRoute.params?.overrideGasRefundAddress),
32307
32453
  });
@@ -32320,8 +32466,10 @@ const AXELAR_PROVIDER_IMAGE_URL = "https://raw.githubusercontent.com/0xsquid/ass
32320
32466
  const useEstimate = (squidRoute) => {
32321
32467
  const collectFees = useConfigStore((state) => state.config.collectFees);
32322
32468
  const { tokens } = useSquidTokens();
32323
- const { fromChain, toChain } = useSwap();
32469
+ const { fromChain, toChain, fromPrice } = useSwap();
32324
32470
  const { nativeBalance } = useNativeBalance(fromChain);
32471
+ const fromToken = React.useMemo(() => findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken), [tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken]);
32472
+ const tempoFeeData = useTempoFeeCheck({ fromChain, fromToken });
32325
32473
  const estimateResults = React.useMemo(() => calculateEstimateResults({
32326
32474
  squidRoute,
32327
32475
  tokens,
@@ -32329,8 +32477,17 @@ const useEstimate = (squidRoute) => {
32329
32477
  toChain,
32330
32478
  collectFees: !!collectFees && collectFees.fee > 0,
32331
32479
  nativeTokenBalanceFromChainWei: nativeBalance?.value ?? BigInt("0"),
32332
- }), [squidRoute, tokens, fromChain, toChain, collectFees, nativeBalance]);
32333
- const balanceFormatted = useMultiChainBalance({
32480
+ tempoFeeData: tempoFeeData ?? undefined,
32481
+ }), [
32482
+ squidRoute,
32483
+ tokens,
32484
+ fromChain,
32485
+ toChain,
32486
+ collectFees,
32487
+ nativeBalance,
32488
+ tempoFeeData,
32489
+ ]);
32490
+ const fromBalanceFormatted = useMultiChainBalance({
32334
32491
  chain: fromChain,
32335
32492
  token: estimateResults.fromToken,
32336
32493
  userAddress: squidRoute?.params.fromAddress ?? "",
@@ -32342,7 +32499,6 @@ const useEstimate = (squidRoute) => {
32342
32499
  estimateResults.expectedGasRefundCost,
32343
32500
  getUSDValue,
32344
32501
  ]);
32345
- const proposedGasDestinationAmount = React.useMemo(() => getProposedGasDestinationAmount(estimateResults.destChainNativeToken?.symbol), [estimateResults.destChainNativeToken]);
32346
32502
  const { feeCostsFormatted, totalFeeCostsUsd } = React.useMemo(() => {
32347
32503
  const feeCosts = squidRoute?.estimate.feeCosts ?? [];
32348
32504
  const feeCostsRenamed = [];
@@ -32401,19 +32557,18 @@ const useEstimate = (squidRoute) => {
32401
32557
  totalFeeCostsUsd: totalFeeCostsUsdFormatted,
32402
32558
  };
32403
32559
  }, [squidRoute?.estimate.feeCosts]);
32404
- const slippageFormatted =
32405
- // TODO: update types
32406
- Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) +
32407
- "%";
32408
- const enoughBalanceToSwap = +balanceFormatted >= 0 &&
32409
- +balanceFormatted > +estimateResults.fromAmountFormatted;
32560
+ const slippageFormatted = Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) + "%";
32561
+ const fromBalanceEnoughToSwap = React.useMemo(() => {
32562
+ const fromBalanceNum = Number(fromBalanceFormatted ?? 0);
32563
+ const fromPriceNum = Number(fromPrice ?? 0);
32564
+ return fromBalanceNum >= fromPriceNum;
32565
+ }, [fromBalanceFormatted, fromPrice]);
32410
32566
  return {
32411
32567
  ...estimateResults,
32412
- balanceFormatted,
32568
+ fromBalanceFormatted,
32413
32569
  slippageFormatted,
32414
32570
  totalWithRefundEstimate,
32415
- proposedGasDestinationAmount,
32416
- enoughBalanceToSwap,
32571
+ fromBalanceEnoughToSwap,
32417
32572
  feeCostsFormatted,
32418
32573
  totalFeeCostsUsd,
32419
32574
  };
@@ -36114,12 +36269,8 @@ const useGetRoute = () => {
36114
36269
  * These data will be used to trigger the transaction
36115
36270
  * @returns {Route} Route data
36116
36271
  */
36117
- return reactQuery.useMutation(async ({ fromChain, toChain, fromToken, toToken, sourceUserAddress, destinationAddress, fromPrice = "", toPrice = "", bypassGuardrails, quoteOnly, fromChainType, postHook, preHook, overrideGasRefundAddress, }) => {
36118
- if (!fromChain ||
36119
- !toChain ||
36120
- !fromToken ||
36121
- !toToken ||
36122
- (!fromPrice && !toPrice)) {
36272
+ return reactQuery.useMutation(async ({ fromChain, toChain, fromToken, toToken, sourceUserAddress, destinationAddress, fromPrice, bypassGuardrails, quoteOnly, fromChainType, postHook, preHook, overrideGasRefundAddress, }) => {
36273
+ if (!fromChain || !toChain || !fromToken || !toToken || !fromPrice) {
36123
36274
  return undefined;
36124
36275
  }
36125
36276
  // Dispatch requestQuote event
@@ -36129,7 +36280,6 @@ const useGetRoute = () => {
36129
36280
  fromToken: fromToken.address,
36130
36281
  toToken: toToken.address,
36131
36282
  fromAmount: fromPrice,
36132
- toAmount: toPrice,
36133
36283
  fromAddress: sourceUserAddress,
36134
36284
  toAddress: destinationAddress,
36135
36285
  });
@@ -36138,7 +36288,6 @@ const useGetRoute = () => {
36138
36288
  const fromTokenAddress = fromToken.address;
36139
36289
  const toTokenAddress = toToken.address;
36140
36290
  const fromAmount = parseToBigInt(fromPrice?.toString() ?? "0", fromToken?.decimals).toString();
36141
- const toAmount = parseToBigInt(toPrice?.toString() ?? "0", toToken?.decimals).toString();
36142
36291
  const fromAddress = sourceUserAddress ??
36143
36292
  chainTypeToZeroAddressMap[fromChainType ?? squidTypes.ChainType.EVM];
36144
36293
  const params = {
@@ -36146,7 +36295,6 @@ const useGetRoute = () => {
36146
36295
  fromToken: fromTokenAddress,
36147
36296
  fromAddress,
36148
36297
  fromAmount,
36149
- toAmount,
36150
36298
  toChain,
36151
36299
  toToken: toTokenAddress,
36152
36300
  toAddress: destinationAddress ?? "",
@@ -36168,7 +36316,7 @@ const useGetRoute = () => {
36168
36316
  });
36169
36317
  // Cache the route data
36170
36318
  // Useful when the getRoute mutation is called from another hook
36171
- queryClient.setQueryData(keys().transaction(fromChain, toChain, toToken.address, fromToken.address, fromPrice, toPrice, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, swapRoute?.fallbackAddress, quoteOnly, fromChainType, config.preHook, config.postHook, overrideGasRefundAddress), route);
36319
+ 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);
36172
36320
  return route;
36173
36321
  });
36174
36322
  };
@@ -36182,7 +36330,7 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36182
36330
  const depositRefundAddress = useSwapRoutePersistStore((store) => store.swapRoute?.depositRefundAddress);
36183
36331
  const { isAvailableAsPaymentMethod, isEnabled: isDepositAddressEnabled } = useDepositAddress();
36184
36332
  const getRouteMutation = useGetRoute();
36185
- const { fromChain, toChain, fromAmount, toAmount, destinationAddress: { address: destinationAddress } = {}, fromToken, toToken, } = useSwap();
36333
+ const { fromChain, toChain, fromPrice, destinationAddress: { address: destinationAddress } = {}, fromToken, toToken, } = useSwap();
36186
36334
  const { connectedAddress: { address: sourceConnectedAddress }, } = useMultiChainWallet(fromChain);
36187
36335
  // When the payment method is deposit address, users can specify a refund address on the source chain
36188
36336
  // Tokens will be sent to this address in case of swap failure
@@ -36191,15 +36339,13 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36191
36339
  const sourceUserAddress = isDepositAddressEnabled && isAvailableAsPaymentMethod
36192
36340
  ? depositRefundAddress ?? sourceConnectedAddress
36193
36341
  : sourceConnectedAddress;
36194
- const squidRouteQueryKeys = React.useMemo(() => keys().transaction(fromChain?.chainId, toChain?.chainId, toToken?.address, fromToken?.address, fromAmount, toAmount, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChain?.chainType, config.preHook, config.postHook, config.overrideGasRefundAddress), [
36342
+ const squidRouteQueryKeys = React.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), [
36195
36343
  fromChain?.chainId,
36196
36344
  toChain?.chainId,
36197
36345
  toToken?.address,
36198
36346
  fromToken?.address,
36199
- fromAmount,
36200
- toAmount,
36347
+ fromPrice,
36201
36348
  config.slippage,
36202
- config.enableGetGasOnDestination,
36203
36349
  sourceUserAddress,
36204
36350
  config.degenMode,
36205
36351
  destinationAddress,
@@ -36213,8 +36359,8 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36213
36359
  const queryEnabled = enabled != undefined
36214
36360
  ? enabled
36215
36361
  : squid !== undefined &&
36216
- ((fromAmount !== undefined && fromAmount !== "0") ||
36217
- (toAmount !== undefined && toAmount !== "0")) &&
36362
+ fromPrice !== undefined &&
36363
+ fromPrice !== "0" &&
36218
36364
  toChain?.chainId !== undefined &&
36219
36365
  toToken?.address !== undefined;
36220
36366
  const queryClient = reactQuery.useQueryClient();
@@ -36232,8 +36378,7 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36232
36378
  toToken,
36233
36379
  sourceUserAddress,
36234
36380
  destinationAddress,
36235
- fromPrice: fromAmount,
36236
- toPrice: toAmount,
36381
+ fromPrice,
36237
36382
  bypassGuardrails: config.degenMode,
36238
36383
  quoteOnly,
36239
36384
  fromChainType: fromChain?.chainType,
@@ -36472,35 +36617,6 @@ const useAvatar = (seed = zeroAddress) => {
36472
36617
  return avatar || "";
36473
36618
  };
36474
36619
 
36475
- const useUserParams = () => {
36476
- const enableGetGasOnDestination = useConfigStore((state) => state.config.enableGetGasOnDestination);
36477
- const { fromToken, toToken, toChain, fromChain } = useSwap();
36478
- // =============
36479
- // GAS
36480
- // =============
36481
- const getGasOnDestSupportedForThisRoute = React.useMemo(() =>
36482
- // Not supporting get gas on dest for same chains
36483
- fromChain?.chainId !== toChain?.chainId &&
36484
- // If the destination chain is cosmos, we don't support getting gas there
36485
- toChain?.chainType !== squidTypes.ChainType.COSMOS &&
36486
- // Not supporting get gas on dest for same tokens (bridge)
36487
- ((fromToken?.subGraphIds?.some((sgi) => !!toToken?.subGraphIds?.includes(sgi)) &&
36488
- toToken?.subGraphIds?.some((sgi) => !!fromToken?.subGraphIds?.includes(sgi))) ||
36489
- // Except for uusdc -> uusdc
36490
- (fromToken?.subGraphIds?.includes("uusdc") &&
36491
- toToken?.subGraphIds?.includes("uusdc"))), [
36492
- fromChain?.chainId,
36493
- fromToken?.subGraphIds,
36494
- toChain?.chainId,
36495
- toToken?.subGraphIds,
36496
- toChain?.chainType,
36497
- ]);
36498
- return {
36499
- gasEnabled: enableGetGasOnDestination && getGasOnDestSupportedForThisRoute,
36500
- getGasOnDestSupportedForThisRoute,
36501
- };
36502
- };
36503
-
36504
36620
  const useAddToken = (chainToCompare, tokenToCompare) => {
36505
36621
  const { chain: currentEvmChain } = wagmi.useAccount();
36506
36622
  const { connector } = wagmi.useAccount();
@@ -37153,6 +37269,7 @@ exports.useSendTransactionStore = useSendTransactionStore;
37153
37269
  exports.useSigner = useSigner;
37154
37270
  exports.useSingleTokenPrice = useSingleTokenPrice;
37155
37271
  exports.useSolanaNativeBalance = useSolanaNativeBalance;
37272
+ exports.useSourceChainGasToken = useSourceChainGasToken;
37156
37273
  exports.useSquid = useSquid;
37157
37274
  exports.useSquidChains = useSquidChains;
37158
37275
  exports.useSquidQueryClient = useSquidQueryClient;
@@ -37168,7 +37285,6 @@ exports.useSwapTransactionStatus = useSwapTransactionStatus;
37168
37285
  exports.useTokensData = useTokensData;
37169
37286
  exports.useTrackSearchEmpty = useTrackSearchEmpty;
37170
37287
  exports.useTransactionStore = useTransactionStore;
37171
- exports.useUserParams = useUserParams;
37172
37288
  exports.useWallet = useWallet;
37173
37289
  exports.useWalletStore = useWalletStore;
37174
37290
  exports.useWallets = useWallets;
@@ -37177,4 +37293,4 @@ exports.useXrplTrustLine = useXrplTrustLine;
37177
37293
  exports.waitForReceiptWithRetry = waitForReceiptWithRetry;
37178
37294
  exports.walletIconBaseUrl = walletIconBaseUrl;
37179
37295
  exports.walletSupportsChainType = walletSupportsChainType;
37180
- //# sourceMappingURL=index-Bg7CS2Uo.js.map
37296
+ //# sourceMappingURL=index-CaI-xWCW.js.map