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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/core/constants.d.ts +3 -0
  2. package/dist/core/queries/queries-keys.d.ts +6 -2
  3. package/dist/core/types/config.d.ts +0 -1
  4. package/dist/core/types/index.d.ts +1 -1
  5. package/dist/core/types/stellar.d.ts +52 -0
  6. package/dist/core/types/tokens.d.ts +4 -0
  7. package/dist/hooks/index.d.ts +3 -2
  8. package/dist/hooks/stellar/useStellarTrustLine.d.ts +13 -0
  9. package/dist/hooks/tokens/useSourceChainGasToken.d.ts +10 -0
  10. package/dist/hooks/transaction/send/useEstimateSendTransactionGas.d.ts +10 -2
  11. package/dist/hooks/transaction/useEstimate.d.ts +7 -12
  12. package/dist/hooks/transaction/useTempoFeeCheck.d.ts +11 -0
  13. package/dist/{index-5cyMUZhY.js → index-BSX11dad.js} +1552 -1109
  14. package/dist/index-BSX11dad.js.map +1 -0
  15. package/dist/{index-BGVXRZI6.js → index-XR8ODWxH.js} +1545 -1113
  16. package/dist/index-XR8ODWxH.js.map +1 -0
  17. package/dist/{index.es-BfdAGErV.js → index.es-CczeKjuj.js} +3 -3
  18. package/dist/{index.es-BfdAGErV.js.map → index.es-CczeKjuj.js.map} +1 -1
  19. package/dist/{index.es-CeHwkxPw.js → index.es-CkrP1GZJ.js} +3 -3
  20. package/dist/{index.es-CeHwkxPw.js.map → index.es-CkrP1GZJ.js.map} +1 -1
  21. package/dist/index.esm.js +2 -2
  22. package/dist/index.js +14 -3
  23. package/dist/index.js.map +1 -1
  24. package/dist/{secretService-D_d3CFdp.js → secretService-ChCrHmS3.js} +3 -3
  25. package/dist/{secretService-D_d3CFdp.js.map → secretService-ChCrHmS3.js.map} +1 -1
  26. package/dist/{secretService-BMYOBXhv.js → secretService-ScgDU3bX.js} +3 -3
  27. package/dist/{secretService-BMYOBXhv.js.map → secretService-ScgDU3bX.js.map} +1 -1
  28. package/dist/services/external/rpcService.d.ts +1 -1
  29. package/dist/services/external/stellarApiClient.d.ts +4 -0
  30. package/dist/services/external/stellarRpcClient.d.ts +2 -2
  31. package/dist/services/external/xrplRpcClient.d.ts +2 -1
  32. package/dist/services/index.d.ts +1 -0
  33. package/dist/services/internal/assetsService.d.ts +2 -2
  34. package/dist/services/internal/estimateService.d.ts +15 -28
  35. package/dist/services/internal/stellarService.d.ts +9 -0
  36. package/dist/services/internal/tempoService.d.ts +38 -0
  37. package/dist/services/internal/xrplService.d.ts +2 -0
  38. package/dist/{stellarService.client-DOrCdvCd.js → stellarService.client-BaDOSK8x.js} +3 -3
  39. package/dist/{stellarService.client-DOrCdvCd.js.map → stellarService.client-BaDOSK8x.js.map} +1 -1
  40. package/dist/{stellarService.client-CIkvwxPo.js → stellarService.client-D65n-wCV.js} +3 -3
  41. package/dist/{stellarService.client-CIkvwxPo.js.map → stellarService.client-D65n-wCV.js.map} +1 -1
  42. package/package.json +1 -1
  43. package/dist/hooks/user/useUserParams.d.ts +0 -4
  44. package/dist/index-5cyMUZhY.js.map +0 -1
  45. package/dist/index-BGVXRZI6.js.map +0 -1
@@ -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",
@@ -20118,14 +20121,17 @@ function isXrplAddressValid(address) {
20118
20121
  return rippleAddressCodec.isValidXAddress(address) || rippleAddressCodec.isValidClassicAddress(address);
20119
20122
  }
20120
20123
  function buildXrplTrustSetTx({ amount, sourceAddress, token, }) {
20121
- const [currency, issuer] = token.address.split(".");
20124
+ const asset = parseXrplTokenAddress(token.address);
20125
+ if (!asset) {
20126
+ throw new Error("Invalid asset");
20127
+ }
20122
20128
  return {
20123
20129
  TransactionType: XrplTransactionType.TRUST_SET,
20124
20130
  Flags: XrplTransactionFlag.tfSetNoRipple,
20125
20131
  Account: sourceAddress,
20126
20132
  LimitAmount: {
20127
- currency,
20128
- issuer,
20133
+ currency: asset.code,
20134
+ issuer: asset.issuer,
20129
20135
  value: amount,
20130
20136
  },
20131
20137
  };
@@ -20154,6 +20160,16 @@ function parseXrplPaymentTx(data) {
20154
20160
  throw new Error("Could not parse payment transaction");
20155
20161
  }
20156
20162
  }
20163
+ function parseXrplTokenAddress(address) {
20164
+ const [code, issuer] = address.split(".");
20165
+ if (!code || !issuer) {
20166
+ return null;
20167
+ }
20168
+ return {
20169
+ code,
20170
+ issuer,
20171
+ };
20172
+ }
20157
20173
 
20158
20174
  const chains = [XrplCAIP2ChainId.MAINNET, XrplCAIP2ChainId.TESTNET];
20159
20175
  class XrplWalletConnect {
@@ -21136,8 +21152,10 @@ function isChainflipBridgeTransaction(actions = []) {
21136
21152
  function isOnChainTxData(squidData) {
21137
21153
  return [
21138
21154
  squidTypes.SquidDataType.OnChainExecution,
21139
- squidTypes.SquidDataType.DepositAddressWithSignature,
21140
21155
  squidTypes.SquidDataType.OnChainExecutionWithSignature,
21156
+ squidTypes.SquidDataType.DepositAddressWithSignature,
21157
+ squidTypes.SquidDataType.DepositAddressCalldata,
21158
+ squidTypes.SquidDataType.DepositAddressWithMemo,
21141
21159
  ].includes(squidData.type);
21142
21160
  }
21143
21161
  function getHistoryTransactionId(tx) {
@@ -21414,6 +21432,28 @@ const executeSolanaTransfer = async ({ amount, target, signer, connection, sourc
21414
21432
  return signature;
21415
21433
  };
21416
21434
 
21435
+ var StellarHorizonAssetType;
21436
+ (function (StellarHorizonAssetType) {
21437
+ /**
21438
+ * XLM native token
21439
+ */
21440
+ StellarHorizonAssetType["NATIVE"] = "native";
21441
+ /**
21442
+ * 1-4 char asset code (e.g. USDC)
21443
+ */
21444
+ StellarHorizonAssetType["ALPHANUM4"] = "credit_alphanum4";
21445
+ /**
21446
+ * 5-12 char asset code (e.g. wstETH)
21447
+ */
21448
+ StellarHorizonAssetType["ALPHANUM12"] = "credit_alphanum12";
21449
+ })(StellarHorizonAssetType || (StellarHorizonAssetType = {}));
21450
+ var StellarTokenType;
21451
+ (function (StellarTokenType) {
21452
+ StellarTokenType["NATIVE_TOKEN"] = "nativeToken";
21453
+ StellarTokenType["ISSUER_TOKEN"] = "issuerToken";
21454
+ StellarTokenType["CONTRACT_TOKEN"] = "contractToken";
21455
+ })(StellarTokenType || (StellarTokenType = {}));
21456
+
21417
21457
  function isStellarAddressValid(address) {
21418
21458
  return stellarSdk.StrKey.isValidEd25519PublicKey(address);
21419
21459
  }
@@ -21435,6 +21475,78 @@ function stellarAddressToScVal(addressString) {
21435
21475
  type: "address",
21436
21476
  });
21437
21477
  }
21478
+ function getStellarTrustLineAsset(token) {
21479
+ // The address format for issued assets is `CODE-ISSUER`
21480
+ // Example: USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
21481
+ const [code, issuer] = token.address.split("-");
21482
+ if (!code || !issuer) {
21483
+ return null;
21484
+ }
21485
+ return {
21486
+ code,
21487
+ issuer,
21488
+ };
21489
+ }
21490
+ function isStellarToken(token) {
21491
+ try {
21492
+ const stellarToken = token;
21493
+ return (Object.values(StellarTokenType).includes(stellarToken.chainAssetConfig.stellar.assetType) &&
21494
+ typeof stellarToken.chainAssetConfig.stellar.contractAddress === "string");
21495
+ }
21496
+ catch {
21497
+ return false;
21498
+ }
21499
+ }
21500
+ function isStellarIssuedToken(token) {
21501
+ try {
21502
+ return (isStellarToken(token) &&
21503
+ token.chainAssetConfig.stellar.assetType === StellarTokenType.ISSUER_TOKEN);
21504
+ }
21505
+ catch {
21506
+ return false;
21507
+ }
21508
+ }
21509
+ function getStellarHorizonApiUrl(chain) {
21510
+ try {
21511
+ const stellarChain = chain;
21512
+ const [horizonApiUrl] = stellarChain.horizonRpcList;
21513
+ if (typeof horizonApiUrl !== "string") {
21514
+ throw new Error("Invalid Horizon API URL");
21515
+ }
21516
+ return horizonApiUrl;
21517
+ }
21518
+ catch {
21519
+ return null;
21520
+ }
21521
+ }
21522
+ const VALID_ASSET_TYPES = new Set(Object.values(StellarHorizonAssetType));
21523
+ function isValidNativeAsset(asset) {
21524
+ return (typeof asset === "object" &&
21525
+ asset !== null &&
21526
+ "balance" in asset &&
21527
+ typeof asset.balance === "string" &&
21528
+ "asset_type" in asset &&
21529
+ asset.asset_type === StellarHorizonAssetType.NATIVE);
21530
+ }
21531
+ function isValidIssuedAsset(asset) {
21532
+ return (typeof asset === "object" &&
21533
+ asset !== null &&
21534
+ "balance" in asset &&
21535
+ typeof asset.balance === "string" &&
21536
+ "asset_type" in asset &&
21537
+ typeof asset.asset_type === "string" &&
21538
+ VALID_ASSET_TYPES.has(asset.asset_type) &&
21539
+ asset.asset_type !== StellarHorizonAssetType.NATIVE &&
21540
+ "asset_code" in asset &&
21541
+ typeof asset.asset_code === "string" &&
21542
+ "asset_issuer" in asset &&
21543
+ typeof asset.asset_issuer === "string" &&
21544
+ "limit" in asset &&
21545
+ typeof asset.limit === "string");
21546
+ }
21547
+ function isValidHorizonAsset(asset) {
21548
+ return isValidNativeAsset(asset) || isValidIssuedAsset(asset);
21549
+ }
21438
21550
 
21439
21551
  const SUI_FEATURES = ["sui:signTransaction"];
21440
21552
  function isSuiStandardWallet(wallet) {
@@ -22234,6 +22346,8 @@ exports.QueryKeys = void 0;
22234
22346
  QueryKeys["XrplAccountActivatedInfo"] = "xrplAccountActivatedInfo";
22235
22347
  QueryKeys["FiatToCryptoPaymentMethods"] = "fiatToCryptoPaymentMethods";
22236
22348
  QueryKeys["Stellar"] = "stellar";
22349
+ QueryKeys["StellarTrustLine"] = "stellarTrustLine";
22350
+ QueryKeys["IsStellarTrustLineApproved"] = "isStellarTrustLineApproved";
22237
22351
  QueryKeys["StellarAccountActivatedInfo"] = "stellarAccountActivatedInfo";
22238
22352
  QueryKeys["Hedera"] = "hedera";
22239
22353
  QueryKeys["IsHederaTokenAssociated"] = "isHederaTokenAssociated";
@@ -22298,7 +22412,7 @@ const keys = () => ({
22298
22412
  // ============
22299
22413
  // Transactions
22300
22414
  // ============
22301
- transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, price, slippage, getGasOnDestination, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22415
+ transaction: (fromChainId, toChainId, toTokenAddress, fromTokenAddress, price, slippage, sourceUserAddress, degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChainType, preHook, postHook, overrideGasRefundAddress) => [
22302
22416
  ...keys().transactions(),
22303
22417
  exports.QueryKeys.Transaction,
22304
22418
  fromChainId,
@@ -22307,7 +22421,6 @@ const keys = () => ({
22307
22421
  fromTokenAddress,
22308
22422
  price,
22309
22423
  slippage,
22310
- getGasOnDestination,
22311
22424
  sourceUserAddress,
22312
22425
  degenMode,
22313
22426
  destinationAddress,
@@ -22341,7 +22454,7 @@ const keys = () => ({
22341
22454
  // ============
22342
22455
  // Approval
22343
22456
  // ============
22344
- routeApproved: (routeData, allowanceInWei) => [
22457
+ routeApproved: (routeData, allowanceInWei, isAllowanceQueryEnabled, hasAllowance) => [
22345
22458
  ...keys().transactions(),
22346
22459
  exports.QueryKeys.RouteApproved,
22347
22460
  routeData?.params.fromAddress,
@@ -22349,6 +22462,8 @@ const keys = () => ({
22349
22462
  routeData?.params.fromToken,
22350
22463
  routeData?.params.fromAmount,
22351
22464
  allowanceInWei?.toString(),
22465
+ isAllowanceQueryEnabled,
22466
+ hasAllowance,
22352
22467
  ],
22353
22468
  sendTransactionGas: (chainId, tokenAddress, from) => [
22354
22469
  exports.QueryKeys.All,
@@ -22421,6 +22536,23 @@ const keys = () => ({
22421
22536
  // ============
22422
22537
  // Stellar
22423
22538
  // ============
22539
+ stellarTrustLine: (tokenAddress, chainId, address) => [
22540
+ ...keys().stellar(),
22541
+ exports.QueryKeys.StellarTrustLine,
22542
+ tokenAddress,
22543
+ chainId,
22544
+ address,
22545
+ ],
22546
+ isStellarTrustLineApproved: (address, chainId, chainType, tokenAddress, trustLineLimit, amountToApprove) => [
22547
+ ...keys().stellar(),
22548
+ exports.QueryKeys.IsStellarTrustLineApproved,
22549
+ address,
22550
+ chainId,
22551
+ chainType,
22552
+ tokenAddress,
22553
+ trustLineLimit,
22554
+ amountToApprove?.toString(),
22555
+ ],
22424
22556
  stellarAccountActivatedInfo: (address, chainId, chainType) => [
22425
22557
  ...keys().stellar(),
22426
22558
  exports.QueryKeys.StellarAccountActivatedInfo,
@@ -22456,6 +22588,8 @@ const getPrefixKey = (key) => {
22456
22588
  return [...keys().transactions(), key];
22457
22589
  case exports.QueryKeys.XrplTrustLine:
22458
22590
  return [...keys().xrpl(), key];
22591
+ case exports.QueryKeys.StellarTrustLine:
22592
+ return [...keys().stellar(), key];
22459
22593
  case exports.QueryKeys.IsHederaTokenAssociated:
22460
22594
  return [...keys().hedera(), key];
22461
22595
  default:
@@ -22468,7 +22602,6 @@ const getConfigWithDefaults = (config) => {
22468
22602
  integratorId: get$2(config, "integratorId", defaultConfigValues.integratorId),
22469
22603
  slippage: get$2(config, "slippage", defaultConfigValues.slippage),
22470
22604
  collectFees: get$2(config, "collectFees", defaultConfigValues.collectFees),
22471
- enableGetGasOnDestination: get$2(config, "enableGetGasOnDestination", defaultConfigValues.enableGetGasOnDestination),
22472
22605
  apiUrl: get$2(config, "apiUrl", defaultConfigValues.apiUrl),
22473
22606
  priceImpactWarnings: get$2(config, "priceImpactWarnings", defaultConfigValues.priceImpactWarnings),
22474
22607
  initialAssets: get$2(config, "initialAssets", defaultConfigValues.initialAssets),
@@ -22958,8 +23091,8 @@ const sortAllTokens = (tokenA, tokenB) => {
22958
23091
  return 0;
22959
23092
  };
22960
23093
  const findToken = (tokens, chainId, address) => tokens.find((t) => t.chainId === chainId && t.address === address);
22961
- const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain?.nativeCurrency.symbol.toUpperCase() &&
22962
- t.chainId == chain?.chainId);
23094
+ const findNativeToken = (tokens, chain) => tokens.find((t) => t.symbol.toUpperCase() === chain.nativeCurrency.symbol.toUpperCase() &&
23095
+ t.chainId == chain.chainId);
22963
23096
  const normalizeIbcAddress = (address) => {
22964
23097
  if (!address.toLowerCase().startsWith("ibc/")) {
22965
23098
  return address;
@@ -23170,7 +23303,7 @@ const filterViewableTokens = (tokens, config, direction) => {
23170
23303
  };
23171
23304
  const getSecretNetworkBalances = async (chainData, cosmosAddress, squidTokens, keplrTypeWallet) => {
23172
23305
  const squidSecretTokens = squidTokens.filter((t) => t.chainId === CHAIN_IDS.SECRET);
23173
- const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-D_d3CFdp.js'); });
23306
+ const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-ChCrHmS3.js'); });
23174
23307
  return fetchAllSecretBalances(chainData, cosmosAddress, squidSecretTokens, keplrTypeWallet);
23175
23308
  };
23176
23309
  function getTokenAssetsKey(token) {
@@ -24861,7 +24994,7 @@ class OnrampService {
24861
24994
  });
24862
24995
  return data;
24863
24996
  }
24864
- async getConfiguration({ chains, tokens, }) {
24997
+ async getConfiguration({ chains: _, tokens, }) {
24865
24998
  const { data } = await axios.get(`${this.baseUrl}/config`);
24866
24999
  // Filter supportedCryptos to only include tokens that match our provided tokens
24867
25000
  const filteredCryptos = data.supportedCryptos.filter((supportedCrypto) => tokens.some((token) => token.address.toLowerCase() ===
@@ -26486,7 +26619,7 @@ function useStellarWallets() {
26486
26619
  try {
26487
26620
  const { allowAllModules: initializeAllModules } = await import('@creit.tech/stellar-wallets-kit');
26488
26621
  const { LedgerModule } = await import('@creit.tech/stellar-wallets-kit/modules/ledger.module');
26489
- const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-CIkvwxPo.js'); });
26622
+ const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-D65n-wCV.js'); });
26490
26623
  const modules = [...initializeAllModules(), new LedgerModule()];
26491
26624
  const promises = modules.map(async (module) => {
26492
26625
  const isAvailable = await module.isAvailable();
@@ -27793,580 +27926,310 @@ function useTrackSearchEmpty({ searchQuery, resultsLength, context, debounceMs =
27793
27926
  }, [context, debounceMs, resultsLength, searchQuery]);
27794
27927
  }
27795
27928
 
27796
- var hrc20 = [
27797
- {
27798
- inputs: [
27799
- ],
27800
- name: "associate",
27801
- outputs: [
27802
- {
27803
- internalType: "uint256",
27804
- name: "responseCode",
27805
- type: "uint256"
27806
- }
27807
- ],
27808
- stateMutability: "nonpayable",
27809
- type: "function"
27810
- }
27811
- ];
27812
-
27813
- /**
27814
- * Client for interacting with the Hedera Mirrornode API.
27815
- *
27816
- * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
27817
- */
27818
- class HederaApiClient {
27819
- apiUrl;
27820
- constructor(apiUrl) {
27821
- this.apiUrl = apiUrl;
27822
- }
27823
- async isTokenAssociated({ address, token, }) {
27824
- const accountInfo = await this.getAccountInfo(address);
27825
- // Unlimited auto associations
27826
- if (accountInfo.max_automatic_token_associations === -1) {
27827
- return true;
27828
- }
27829
- // If there's no unlimited auto-associations, we need to check if the token is already associated.
27830
- const { tokens: accountTokens } = await this.getAccountTokens(address);
27831
- const tokenId = convertEvmAddressToHederaAccountId(token.address);
27832
- if (accountTokens.some((t) => t.token_id === tokenId)) {
27833
- // Token is already associated
27834
- return true;
27835
- }
27836
- // Finally, if not auto-associated, check if there is an available auto-association slot
27837
- const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
27838
- const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
27839
- autoAssociatedTokens.length;
27840
- return remainingAutoAssociations > 0;
27929
+ class StellarRpcClient {
27930
+ server;
27931
+ constructor(rpcUrl) {
27932
+ this.server = new stellarSdk.rpc.Server(rpcUrl);
27841
27933
  }
27842
- async getAccountInfo(address) {
27843
- const data = await this.fetch(`accounts/${address}`);
27844
- if (typeof data.max_automatic_token_associations !== "number") {
27845
- throw new Error("Invalid max_automatic_token_associations type, expected number");
27934
+ /**
27935
+ * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
27936
+ *
27937
+ * With Contract Tokens, we need to call the .balance method on the token contract
27938
+ * and simulate the transaction to get the balance.
27939
+ */
27940
+ async getBalance(userAddress, tokenAddress, chainId) {
27941
+ const account = await this.server.getAccount(userAddress);
27942
+ const network = getStellarNetwork(chainId);
27943
+ if (network == null) {
27944
+ throw new Error(`No Stellar network found for chainId ${chainId}`);
27846
27945
  }
27847
- if (typeof data.balance.balance !== "number") {
27848
- throw new Error("Invalid balance type, expected number");
27946
+ const txBuilder = new stellarSdk.TransactionBuilder(account, {
27947
+ fee: (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
27948
+ networkPassphrase: network,
27949
+ });
27950
+ const contract = new stellarSdk.Contract(tokenAddress);
27951
+ const tx = txBuilder
27952
+ .addOperation(contract.call("balance", new stellarSdk.Address(userAddress).toScVal()))
27953
+ .setTimeout(stellarSdk.TimeoutInfinite)
27954
+ .build();
27955
+ const simulateTxResponse = await this.server.simulateTransaction(tx);
27956
+ if ("error" in simulateTxResponse) {
27957
+ const isNoBalanceError = /trying to get non-existing value for contract instance|trustline entry is missing for account/.test(simulateTxResponse.error);
27958
+ // If the error message indicates that the user has no balance just return 0
27959
+ // We don't want to spam with this error as it's pretty common
27960
+ if (isNoBalanceError) {
27961
+ return "0";
27962
+ }
27963
+ throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
27849
27964
  }
27850
- if (!Array.isArray(data.balance.tokens)) {
27851
- throw new Error("Invalid tokens type, expected array");
27965
+ if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
27966
+ const native = stellarSdk.scValToNative(simulateTxResponse.result.retval);
27967
+ return native.toString();
27852
27968
  }
27853
- return data;
27969
+ throw new Error("Failed to fetch balance");
27854
27970
  }
27855
- async getAccountTokens(address) {
27856
- const data = await this.fetch(`accounts/${address}/tokens`);
27857
- if (!Array.isArray(data.tokens)) {
27858
- throw new Error("Invalid tokens type, expected array");
27971
+ async getAllBalances(userAddress, tokens) {
27972
+ const balancePromises = tokens.map((token) => {
27973
+ return this.getBalance(userAddress, token.chainAssetConfig.stellar.contractAddress, token.chainId);
27974
+ });
27975
+ const results = await Promise.allSettled(balancePromises);
27976
+ const balances = results.map((result) => {
27977
+ if (result.status === "fulfilled") {
27978
+ return result.value;
27979
+ }
27980
+ return "0";
27981
+ });
27982
+ return balances.map((balance, index) => {
27983
+ return {
27984
+ ...tokens[index],
27985
+ balance,
27986
+ };
27987
+ });
27988
+ }
27989
+ /**
27990
+ * Resolves when the transaction is confirmed, or fails after a timeout.
27991
+ */
27992
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
27993
+ const startTime = Date.now();
27994
+ while (true) {
27995
+ const result = await this.server.getTransaction(txHash);
27996
+ if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.NOT_FOUND) {
27997
+ if (Date.now() - startTime > timeout) {
27998
+ throw new Error(`Transaction ${txHash} not found within timeout`);
27999
+ }
28000
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28001
+ await new Promise((res) => setTimeout(res, interval));
28002
+ continue;
28003
+ }
28004
+ if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) {
28005
+ return result.status;
28006
+ }
28007
+ throw new Error(`Transaction failed with status: ${result.status}`);
27859
28008
  }
27860
- const firstToken = data.tokens[0];
27861
- if (typeof firstToken?.automatic_association !== "boolean") {
27862
- throw new Error("Invalid automatic_association type, expected boolean");
28009
+ }
28010
+ async isAccountActivated(address) {
28011
+ try {
28012
+ await this.getAccount(address);
28013
+ return true;
27863
28014
  }
27864
- if (typeof firstToken?.token_id !== "string") {
27865
- throw new Error("Invalid token_id type, expected string");
28015
+ catch (error) {
28016
+ if (error?.message && error.message.includes("Account not found")) {
28017
+ return false;
28018
+ }
28019
+ throw error;
27866
28020
  }
27867
- return data;
27868
28021
  }
27869
- async fetch(path) {
27870
- const url = new URL(path, this.apiUrl);
27871
- const response = await fetch(url);
27872
- if (!response.ok) {
27873
- throw new Error(`Hedera API error: ${response.status}`);
28022
+ async getAccount(address) {
28023
+ return this.server.getAccount(address);
28024
+ }
28025
+ async sendTransaction(transaction) {
28026
+ const transactionResponse = await this.server.sendTransaction(transaction);
28027
+ if (transactionResponse.status === "ERROR") {
28028
+ throw new Error("Error sending transaction");
27874
28029
  }
27875
- return response.json();
28030
+ return transactionResponse;
28031
+ }
28032
+ async prepareTransaction(transaction) {
28033
+ return this.server.prepareTransaction(transaction);
27876
28034
  }
27877
28035
  }
27878
28036
 
27879
- hederaWalletConnect.type = "hederaWalletConnect";
27880
- /**
27881
- * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
27882
- *
27883
- * The connector removes the need for a WalletConnect modal, and instead
27884
- * communicates with the provided `extension` by opening the extension popup
27885
- * for user interaction upon request.
27886
- */
27887
- function hederaWalletConnect(parameters) {
27888
- const { extension, ...restParameters } = parameters;
27889
- let provider_;
27890
- let providerPromise;
27891
- let connect;
27892
- let displayUri;
27893
- let sessionDelete;
27894
- let disconnect;
27895
- return createConnector((config) => ({
27896
- id: `hedera-wc-${extension.id}`,
27897
- name: extension.name,
27898
- icon: extension.icon,
27899
- type: hederaWalletConnect.type,
27900
- async setup() {
27901
- const provider = await this.getProvider().catch(() => null);
27902
- if (!provider)
27903
- return;
27904
- if (!connect) {
27905
- connect = this.onConnect.bind(this);
27906
- provider.on("connect", connect);
27907
- }
27908
- if (!sessionDelete) {
27909
- sessionDelete = this.onSessionDelete.bind(this);
27910
- provider.on("session_delete", sessionDelete);
28037
+ class XrplRpcClient {
28038
+ rpcUrl;
28039
+ constructor(rpcUrl) {
28040
+ this.rpcUrl = rpcUrl;
28041
+ }
28042
+ async getBalance(address, tokenAddress) {
28043
+ if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
28044
+ return this.getNativeBalance(address);
28045
+ }
28046
+ return this.getIssuedCurrencyBalance(address, tokenAddress);
28047
+ }
28048
+ async getAllBalances(address) {
28049
+ const [nativeBalance, trustLineBalances] = await Promise.all([
28050
+ this.getNativeBalance(address),
28051
+ this.getTrustLines(address),
28052
+ ]);
28053
+ return [
28054
+ {
28055
+ balance: nativeBalance,
28056
+ address: nativeXrplTokenAddress,
28057
+ },
28058
+ ...trustLineBalances.lines.map((line) => ({
28059
+ balance: line.balance,
28060
+ address: `${line.currency}.${line.account}`,
28061
+ })),
28062
+ ];
28063
+ }
28064
+ async getTrustLines(address, issuer) {
28065
+ return this.call("account_lines", [
28066
+ {
28067
+ account: address,
28068
+ ledger_index: "validated",
28069
+ peer: issuer,
28070
+ },
28071
+ ]);
28072
+ }
28073
+ async getTrustLine(address, asset) {
28074
+ const response = await this.getTrustLines(address, asset.issuer);
28075
+ const trustLine = response.lines.find((line) => line.currency === asset.code);
28076
+ return trustLine ?? null;
28077
+ }
28078
+ async accountActivatedInfo(address) {
28079
+ const serverState = await this.getServerState();
28080
+ const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
28081
+ try {
28082
+ const accountInfo = await this.getAccountInfo(address);
28083
+ const balanceBn = BigInt(accountInfo.account_data.Balance);
28084
+ return {
28085
+ isActivated: balanceBn >= reserveBaseBn,
28086
+ reserveBaseBn,
28087
+ };
28088
+ }
28089
+ catch (error) {
28090
+ if (error.message?.includes("actNotFound")) {
28091
+ return { isActivated: false, reserveBaseBn };
27911
28092
  }
27912
- },
27913
- async connect(params = {}) {
28093
+ throw error;
28094
+ }
28095
+ }
28096
+ /**
28097
+ * Waits for a transaction to be validated and returns its final status.
28098
+ * Resolves to 'success' or throws an error with the failed status.
28099
+ */
28100
+ async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
28101
+ const startTime = Date.now();
28102
+ while (true) {
27914
28103
  try {
27915
- const provider = await this.getProvider();
27916
- if (!provider)
27917
- throw new ProviderNotFoundError();
27918
- if (!displayUri) {
27919
- displayUri = this.onDisplayUri;
27920
- provider.on("display_uri", displayUri);
27921
- }
27922
- if (!provider.session) {
27923
- await provider.connect({
27924
- ...("pairingTopic" in params
27925
- ? { pairingTopic: params.pairingTopic }
27926
- : {}),
27927
- });
27928
- }
27929
- const accounts = (await provider.enable()).map((x) => viem.getAddress(x));
27930
- const currentChainId = await this.getChainId();
27931
- if (displayUri) {
27932
- provider.removeListener("display_uri", displayUri);
27933
- displayUri = undefined;
27934
- }
27935
- if (connect) {
27936
- provider.removeListener("connect", connect);
27937
- connect = undefined;
28104
+ const response = await this.call("tx", [
28105
+ {
28106
+ transaction: txHash,
28107
+ binary: false,
28108
+ },
28109
+ ]);
28110
+ if (!response.validated) {
28111
+ if (Date.now() - startTime > timeout) {
28112
+ throw new Error(`Transaction ${txHash} not validated within timeout`);
28113
+ }
28114
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28115
+ await new Promise((res) => setTimeout(res, interval));
28116
+ continue;
27938
28117
  }
27939
- if (!disconnect) {
27940
- disconnect = this.onDisconnect.bind(this);
27941
- provider.on("disconnect", disconnect);
28118
+ const status = response.meta?.TransactionResult;
28119
+ if (status === XrplTxStatus.SUCCESS) {
28120
+ return status;
27942
28121
  }
27943
- if (!sessionDelete) {
27944
- sessionDelete = this.onSessionDelete.bind(this);
27945
- provider.on("session_delete", sessionDelete);
28122
+ else {
28123
+ throw new Error(`Transaction failed with status: ${status}`);
27946
28124
  }
27947
- return { accounts, chainId: currentChainId };
27948
28125
  }
27949
28126
  catch (error) {
27950
- if (/(user rejected|connection request reset)/i.test(error?.message)) {
27951
- throw new viem.UserRejectedRequestError(error);
28127
+ // txnNotFound = still pending or non-existent
28128
+ if (error?.message?.includes("txnNotFound")) {
28129
+ if (Date.now() - startTime > timeout) {
28130
+ throw new Error(`Transaction ${txHash} not found within timeout`);
28131
+ }
28132
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
28133
+ await new Promise((res) => setTimeout(res, interval));
28134
+ continue;
27952
28135
  }
27953
28136
  throw error;
27954
28137
  }
27955
- },
27956
- async disconnect() {
27957
- const provider = await this.getProvider();
27958
- try {
27959
- await provider?.disconnect();
27960
- }
27961
- catch (error) {
27962
- if (!/No matching key/i.test(error.message))
27963
- throw error;
27964
- }
27965
- finally {
27966
- if (disconnect) {
27967
- provider?.removeListener("disconnect", disconnect);
27968
- disconnect = undefined;
27969
- }
27970
- if (!connect) {
27971
- connect = this.onConnect.bind(this);
27972
- provider?.on("connect", connect);
27973
- }
27974
- if (sessionDelete) {
27975
- provider?.removeListener("session_delete", sessionDelete);
27976
- sessionDelete = undefined;
27977
- }
27978
- }
27979
- },
27980
- async getAccounts() {
27981
- const provider = await this.getProvider();
27982
- return provider.accounts.map((x) => viem.getAddress(x));
27983
- },
27984
- async getProvider() {
27985
- async function initProvider() {
27986
- const optionalChains = config.chains.map((x) => x.id);
27987
- if (!optionalChains.length)
27988
- return;
27989
- const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-CeHwkxPw.js'); });
27990
- const rawProvider = await EthereumProvider.init({
27991
- ...restParameters,
27992
- disableProviderPing: true,
27993
- optionalChains,
27994
- projectId: restParameters.projectId,
27995
- rpcMap: Object.fromEntries(config.chains.map((chain) => {
27996
- const [url] = extractRpcUrls({
27997
- chain,
27998
- transports: config.transports,
27999
- });
28000
- return [chain.id, url];
28001
- })),
28002
- showQrModal: false,
28003
- // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
28004
- // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
28005
- customStoragePrefix: "squid-hedera",
28006
- });
28007
- const proxiedProvider = new Proxy(rawProvider, {
28008
- get(target, prop, receiver) {
28009
- if (prop === "request") {
28010
- return async (args) => {
28011
- const signingMethods = [
28012
- "eth_sendTransaction",
28013
- "eth_signTransaction",
28014
- ];
28015
- if (signingMethods.includes(args.method)) {
28016
- try {
28017
- HederaExtensionHelper.extensionOpen(extension.id);
28018
- }
28019
- catch { }
28020
- }
28021
- // forward request to original provider
28022
- return target.request(args);
28023
- };
28024
- }
28025
- // forward all other properties/methods
28026
- return Reflect.get(target, prop, receiver);
28027
- },
28028
- });
28029
- return proxiedProvider;
28030
- }
28031
- if (!provider_) {
28032
- if (!providerPromise)
28033
- providerPromise = initProvider();
28034
- provider_ = await providerPromise;
28035
- provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
28036
- }
28037
- return provider_;
28038
- },
28039
- async getChainId() {
28040
- const provider = await this.getProvider();
28041
- return provider.chainId;
28042
- },
28043
- async isAuthorized() {
28044
- try {
28045
- const accounts = await this.getAccounts();
28046
- return accounts.length > 0;
28047
- }
28048
- catch {
28049
- return false;
28050
- }
28051
- },
28052
- onAccountsChanged(accounts) {
28053
- if (accounts.length === 0)
28054
- this.onDisconnect();
28055
- else
28056
- config.emitter.emit("change", {
28057
- accounts: accounts.map((x) => viem.getAddress(x)),
28058
- });
28059
- },
28060
- onChainChanged(chain) {
28061
- const chainId = Number(chain);
28062
- config.emitter.emit("change", { chainId });
28063
- },
28064
- async onConnect(connectInfo) {
28065
- const chainId = Number(connectInfo.chainId);
28066
- const accounts = await this.getAccounts();
28067
- config.emitter.emit("connect", { accounts, chainId });
28068
- },
28069
- async onDisconnect() {
28070
- config.emitter.emit("disconnect");
28071
- const provider = await this.getProvider();
28072
- if (disconnect) {
28073
- provider.removeListener("disconnect", disconnect);
28074
- disconnect = undefined;
28075
- }
28076
- if (sessionDelete) {
28077
- provider.removeListener("session_delete", sessionDelete);
28078
- sessionDelete = undefined;
28079
- }
28080
- if (!connect) {
28081
- connect = this.onConnect.bind(this);
28082
- provider.on("connect", connect);
28083
- }
28084
- },
28085
- onDisplayUri(uri) {
28086
- config.emitter.emit("message", { type: "display_uri", data: uri });
28087
- HederaExtensionHelper.extensionConnect(extension.id, uri);
28088
- },
28089
- onSessionDelete() {
28090
- this.onDisconnect();
28091
- },
28092
- }));
28093
- }
28094
-
28095
- const createWagmiConfig = (squidChains, hederaExtensions) => {
28096
- const filteredEvmChains = squidChains.filter((chain) => chain.chainType === squidTypes.ChainType.EVM);
28097
- if (filteredEvmChains.length === 0) {
28098
- throw new Error("At least one chain is required");
28138
+ }
28099
28139
  }
28100
- const wagmiChains = filteredEvmChains.map((chain) => {
28101
- return viem.defineChain({
28102
- id: Number(chain.chainId),
28103
- name: chain.networkName,
28104
- nativeCurrency: {
28105
- name: chain.nativeCurrency.name,
28106
- symbol: chain.nativeCurrency.symbol,
28107
- decimals: chain.nativeCurrency.decimals,
28140
+ async getServerState() {
28141
+ return this.call("server_state", [{}]);
28142
+ }
28143
+ async getAccountInfo(address) {
28144
+ return this.call("account_info", [
28145
+ {
28146
+ account: address,
28147
+ ledger_index: "validated",
28108
28148
  },
28109
- rpcUrls: {
28110
- public: {
28111
- http: [chain.rpc],
28112
- },
28113
- default: {
28114
- http: [chain.rpc],
28115
- },
28149
+ ]);
28150
+ }
28151
+ /**
28152
+ * Returns the balance of the user in the native XRP token
28153
+ * formatted as a string
28154
+ */
28155
+ async getNativeBalance(address) {
28156
+ const [accountInfo, serverState] = await Promise.all([
28157
+ this.getAccountInfo(address),
28158
+ this.getServerState(),
28159
+ ]);
28160
+ const balance = BigInt(accountInfo.account_data.Balance);
28161
+ const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
28162
+ const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
28163
+ const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
28164
+ const reserveBalance = reserveBase + ownerCount * reserveIncrement;
28165
+ const spendableBalance = balance - reserveBalance;
28166
+ return formatBNToReadable(spendableBalance, 6);
28167
+ }
28168
+ /**
28169
+ * Returns the balance of the user in the given issued currency (e.g. RLUSD)
28170
+ * formatted as a string
28171
+ */
28172
+ async getIssuedCurrencyBalance(address, tokenAddress) {
28173
+ const response = await this.getTrustLines(address);
28174
+ const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
28175
+ return tokenBalance?.balance || "0";
28176
+ }
28177
+ async call(method, params) {
28178
+ const response = await fetch(this.rpcUrl, {
28179
+ method: "POST",
28180
+ headers: {
28181
+ "Content-Type": "application/json",
28116
28182
  },
28117
- });
28118
- });
28119
- const wcMetadata = {
28120
- url: SQUID_METADATA.url,
28121
- name: SQUID_METADATA.name,
28122
- icons: [SQUID_METADATA.icon],
28123
- description: SQUID_METADATA.description,
28124
- };
28125
- return wagmi.createConfig({
28126
- chains: wagmiChains,
28127
- transports: Object.fromEntries(wagmiChains.map((chain) => [
28128
- chain.id,
28129
- wagmi.http(chain.rpcUrls.public.http[0] ?? ""),
28130
- ])),
28131
- connectors: [
28132
- connectors.injected(),
28133
- connectors.safe({
28134
- allowedDomains: [/app.safe.global$/],
28135
- }),
28136
- connectors.metaMask({
28137
- dappMetadata: {
28138
- name: SQUID_METADATA.name,
28139
- url: SQUID_METADATA.url,
28140
- iconUrl: SQUID_METADATA.icon,
28141
- },
28142
- }),
28143
- connectors.coinbaseWallet({
28144
- appName: SQUID_METADATA.name,
28145
- appLogoUrl: SQUID_METADATA.icon,
28146
- }),
28147
- connectors.walletConnect({
28148
- projectId: WALLETCONNECT_PROJECT_ID,
28149
- metadata: wcMetadata,
28183
+ body: JSON.stringify({
28184
+ jsonrpc: "2.0",
28185
+ id: 1,
28186
+ method,
28187
+ params,
28150
28188
  }),
28151
- ...hederaExtensions.map((extension) => hederaWalletConnect({
28152
- projectId: WALLETCONNECT_PROJECT_ID,
28153
- metadata: wcMetadata,
28154
- extension,
28155
- })),
28156
- ],
28157
- });
28158
- };
28159
- // Taken from wagmi docs
28160
- // https://wagmi.sh/react/guides/ethers
28161
- function clientToSigner(client) {
28162
- const { account, chain, transport } = client;
28163
- if (!account || !chain || !transport) {
28164
- return undefined;
28189
+ });
28190
+ const data = await response.json();
28191
+ if (!data.result) {
28192
+ throw new Error(`Invalid response from RPC (${method})`);
28193
+ }
28194
+ if ("error" in data.result) {
28195
+ throw new Error(`Error from RPC (${method}): ${data.result.error}`);
28196
+ }
28197
+ return data.result;
28165
28198
  }
28166
- const network = {
28167
- chainId: chain.id,
28168
- name: chain.name,
28169
- ensAddress: chain.contracts?.ensRegistry?.address,
28170
- };
28171
- const provider = new ethers.BrowserProvider(transport, network);
28172
- const signer = new ethers.JsonRpcSigner(provider, account.address);
28173
- return signer;
28174
28199
  }
28175
28200
 
28176
- function useEvmSigner({ chainId }) {
28177
- const { connector } = wagmi.useAccount();
28178
- const { data: client } = wagmi.useWalletClient({ chainId, connector });
28179
- const signer = React.useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
28180
- return { signer };
28201
+ const clientCache = new Map();
28202
+ async function getClient(chain) {
28203
+ const key = `${chain.chainType}:${chain.chainId}`;
28204
+ if (clientCache.has(key)) {
28205
+ return clientCache.get(key);
28206
+ }
28207
+ const client = await createClient(chain);
28208
+ clientCache.set(key, client);
28209
+ return client;
28181
28210
  }
28182
- const useSigner = ({ chain }) => {
28183
- const evmChainId = chain?.chainType === squidTypes.ChainType.EVM ? Number(chain.chainId) : undefined;
28184
- // EVM and Cosmos need a different signer for each chain
28185
- // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
28186
- const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
28187
- const { signer: cosmosSigner } = useCosmosSigner({ chain });
28188
- const { signer: solanaSigner } = useSolanaContext();
28189
- const { signer: bitcoinSigner } = useBitcoinContext();
28190
- const { signer: suiSigner } = useSuiContext();
28191
- const { signer: xrplSigner } = useXrplContext();
28192
- const { signer: stellarSigner } = useStellarContext();
28193
- const isEvmSignerReady = !!evmSigner;
28194
- const isSolanaSignerReady = !!solanaSigner;
28195
- const isCosmosSignerReady = !!cosmosSigner;
28196
- const isBitcoinSignerReady = !!bitcoinSigner;
28197
- const isSuiSignerReady = !!suiSigner;
28198
- const isXrplSignerReady = !!xrplSigner;
28199
- const isStellarSignerReady = !!stellarSigner;
28200
- const isSignerReady = React.useMemo(() => {
28201
- if (!chain?.chainType)
28202
- return false;
28203
- switch (chain.chainType) {
28204
- case squidTypes.ChainType.EVM:
28205
- return isEvmSignerReady;
28206
- case squidTypes.ChainType.COSMOS:
28207
- return isCosmosSignerReady;
28208
- case squidTypes.ChainType.BTC:
28209
- return isBitcoinSignerReady;
28210
- case squidTypes.ChainType.SOLANA:
28211
- return isSolanaSignerReady;
28212
- case squidTypes.ChainType.SUI:
28213
- return isSuiSignerReady;
28214
- case squidTypes.ChainType.XRPL:
28215
- return isXrplSignerReady;
28216
- case squidTypes.ChainType.STELLAR:
28217
- return isStellarSignerReady;
28218
- }
28219
- }, [
28220
- chain?.chainType,
28221
- isEvmSignerReady,
28222
- isCosmosSignerReady,
28223
- isBitcoinSignerReady,
28224
- isSolanaSignerReady,
28225
- isSuiSignerReady,
28226
- isXrplSignerReady,
28227
- isStellarSignerReady,
28228
- ]);
28229
- return {
28230
- isSignerReady,
28231
- evmSigner,
28232
- cosmosSigner,
28233
- bitcoinSigner,
28234
- solanaSigner,
28235
- suiSigner,
28236
- xrplSigner,
28237
- stellarSigner,
28238
- };
28239
- };
28240
-
28241
- function useHederaTokenAssociations({ address, chain, token }) {
28242
- const publicClient = wagmi.usePublicClient({
28243
- chainId: Number(CHAIN_IDS.HEDERA),
28244
- });
28245
- const { evmSigner } = useSigner({ chain });
28246
- const queryClient = reactQuery.useQueryClient();
28247
- /**
28248
- * Creates a token association transaction where the destination account authorizes to receive the given token
28249
- */
28250
- const associateToken = reactQuery.useMutation({
28251
- mutationFn: async () => {
28252
- try {
28253
- if (!evmSigner) {
28254
- throw new Error("EVM signer not found");
28255
- }
28256
- if (chain?.chainId !== CHAIN_IDS.HEDERA ||
28257
- token?.chainId !== CHAIN_IDS.HEDERA) {
28258
- throw new Error("Chain and token to associate must be on Hedera");
28259
- }
28260
- const tokenAddress = parseEvmAddress(token.address);
28261
- if (!tokenAddress) {
28262
- throw new Error("Invalid token address");
28263
- }
28264
- const userAddress = parseEvmAddress(address);
28265
- if (!userAddress) {
28266
- throw new Error("Invalid user address");
28267
- }
28268
- const encodedData = viem.encodeFunctionData({
28269
- abi: hrc20,
28270
- functionName: "associate",
28271
- args: [],
28272
- });
28273
- const txRes = await evmSigner.sendTransaction({
28274
- data: encodedData,
28275
- to: tokenAddress,
28276
- chainId: chain.chainId,
28277
- });
28278
- const receipt = await txRes.wait();
28279
- if (receipt?.status !== 1) {
28280
- throw new Error(`Transaction failed with status: ${receipt?.status}`);
28281
- }
28282
- return true;
28283
- }
28284
- catch (error) {
28285
- console.error("Error associating Hedera token:", error);
28286
- return false;
28287
- }
28288
- },
28289
- async onSuccess() {
28290
- queryClient.refetchQueries({
28291
- queryKey: getPrefixKey(exports.QueryKeys.IsHederaTokenAssociated),
28292
- });
28293
- },
28294
- });
28295
- /**
28296
- * Checks if the destination account has associated the given token.
28297
- *
28298
- * Hedera requires accounts to associate a token before being able to receive it.
28299
- *
28300
- * Accounts which have max. associations set to -1 can receive any token without previous association
28301
- */
28302
- const isTokenAssociated = reactQuery.useQuery({
28303
- queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
28304
- queryFn: async () => {
28305
- if (token?.chainId !== CHAIN_IDS.HEDERA) {
28306
- return true;
28307
- }
28308
- // The native HBAR token doesn't need an association
28309
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
28310
- return true;
28311
- }
28312
- if (!chain || !address || !publicClient) {
28313
- throw new Error("Missing required parameters");
28314
- }
28315
- // TODO: update types
28316
- const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
28317
- if (!apiUrl) {
28318
- throw new Error("Missing Hedera mirror node URL in chain config");
28319
- }
28320
- const hederaApiClient = new HederaApiClient(apiUrl);
28321
- return hederaApiClient.isTokenAssociated({
28322
- address,
28323
- token,
28211
+ async function createClient(chain) {
28212
+ switch (chain.chainType) {
28213
+ case squidTypes.ChainType.EVM:
28214
+ return new ethers.JsonRpcProvider(chain.rpc);
28215
+ case squidTypes.ChainType.COSMOS:
28216
+ const rpcUrl = await getWorkingCosmosRpcUrl(chain);
28217
+ return (await stargate.StargateClient.connect(rpcUrl));
28218
+ case squidTypes.ChainType.SOLANA:
28219
+ return new web3_js.Connection(SOLANA_RPC_URL);
28220
+ case squidTypes.ChainType.BTC:
28221
+ return null;
28222
+ case squidTypes.ChainType.SUI:
28223
+ return new client.SuiClient({
28224
+ url: chain.rpc,
28324
28225
  });
28325
- },
28326
- enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
28327
- });
28328
- return {
28329
- isTokenAssociated,
28330
- associateToken,
28331
- };
28226
+ case squidTypes.ChainType.XRPL:
28227
+ return new XrplRpcClient(chain.rpc);
28228
+ case squidTypes.ChainType.STELLAR:
28229
+ return new StellarRpcClient(chain.rpc);
28230
+ }
28332
28231
  }
28333
28232
 
28334
- const useKeyboardNavigation = ({ onEscape }) => {
28335
- const onKeyDown = React.useCallback((event) => {
28336
- if (event.key === "Escape") {
28337
- onEscape?.();
28338
- return;
28339
- }
28340
- }, [onEscape]);
28341
- React.useEffect(() => {
28342
- document.addEventListener("keydown", onKeyDown, false);
28343
- return () => {
28344
- document.removeEventListener("keydown", onKeyDown, false);
28345
- };
28346
- }, [onKeyDown]);
28347
- };
28348
-
28349
- const useSquidQueryClient = () => {
28350
- const queryClient = reactQuery.useQueryClient();
28351
- const invalidateQueries = (key) => {
28352
- const prefixKey = getPrefixKey(key);
28353
- queryClient.invalidateQueries(prefixKey);
28354
- };
28355
- const refetchQueries = (key) => {
28356
- const prefixKey = getPrefixKey(key);
28357
- queryClient.refetchQueries(prefixKey);
28358
- };
28359
- const invalidateAndRefetchQueries = (key) => {
28360
- invalidateQueries(key);
28361
- refetchQueries(key);
28362
- };
28363
- return {
28364
- invalidateQueries,
28365
- refetchQueries,
28366
- invalidateAndRefetchQueries,
28367
- };
28368
- };
28369
-
28370
28233
  /**
28371
28234
  * The default multicall3 address
28372
28235
  * available on most EVM chains
@@ -29349,12 +29212,15 @@ const getAllXrplTokensBalance = async (userAddress, xrplTokens, xrplChains) => {
29349
29212
  };
29350
29213
  const getStellarTokenBalance = async (userAddress, token, chain) => {
29351
29214
  const stellarClient = await getClient(chain);
29352
- const balance = await stellarClient.getBalance(userAddress, token.address, chain.chainId);
29215
+ if (!isStellarToken(token)) {
29216
+ throw new Error("Token must be a Stellar token");
29217
+ }
29218
+ const balance = await stellarClient.getBalance(userAddress, token.chainAssetConfig.stellar.contractAddress, chain.chainId);
29353
29219
  return BigInt(balance);
29354
29220
  };
29355
29221
  const getAllStellarTokensBalance = async (userAddress, stellarTokens, stellarChains) => {
29356
29222
  const getBalancesForChain = async (chain) => {
29357
- const tokensForChain = stellarTokens.filter((t) => t.chainId === chain.chainId);
29223
+ const tokensForChain = stellarTokens.filter((t) => t.chainId === chain.chainId && isStellarToken(t));
29358
29224
  const stellarClient = await getClient(chain);
29359
29225
  const allBalances = await stellarClient.getAllBalances(userAddress, tokensForChain);
29360
29226
  return allBalances.map((token) => {
@@ -29381,372 +29247,6 @@ function timeout(ms, promise) {
29381
29247
  return Promise.race([promise, timeoutPromise]);
29382
29248
  }
29383
29249
 
29384
- class StellarRpcClient {
29385
- server;
29386
- constructor(rpcUrl) {
29387
- this.server = new stellarSdk.rpc.Server(rpcUrl);
29388
- }
29389
- /**
29390
- * Returns the balance of a Stellar Contract Token. This is different from an Issued Token.
29391
- *
29392
- * With Contract Tokens, we need to call the .balance method on the token contract
29393
- * and simulate the transaction to get the balance.
29394
- */
29395
- async getBalance(userAddress, tokenAddress, chainId) {
29396
- const account = await this.server.getAccount(userAddress);
29397
- const network = getStellarNetwork(chainId);
29398
- if (network == null) {
29399
- throw new Error(`No Stellar network found for chainId ${chainId}`);
29400
- }
29401
- const txBuilder = new stellarSdk.TransactionBuilder(account, {
29402
- fee: (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
29403
- networkPassphrase: network,
29404
- });
29405
- const contract = new stellarSdk.Contract(tokenAddress);
29406
- const tx = txBuilder
29407
- .addOperation(contract.call("balance", new stellarSdk.Address(userAddress).toScVal()))
29408
- .setTimeout(stellarSdk.TimeoutInfinite)
29409
- .build();
29410
- const simulateTxResponse = await this.server.simulateTransaction(tx);
29411
- if ("error" in simulateTxResponse) {
29412
- const isNoBalanceError = simulateTxResponse.error.includes("trying to get non-existing value for contract instance");
29413
- // If the error message indicates that the user has no balance just return 0
29414
- // We don't want to spam with this error as it's pretty common
29415
- if (isNoBalanceError) {
29416
- return "0";
29417
- }
29418
- throw new Error(`Failed to fetch balance. RPC response: ${simulateTxResponse.error}`);
29419
- }
29420
- if ("result" in simulateTxResponse && simulateTxResponse.result != null) {
29421
- const native = stellarSdk.scValToNative(simulateTxResponse.result.retval);
29422
- return native.toString();
29423
- }
29424
- throw new Error("Failed to fetch balance");
29425
- }
29426
- async getAllBalances(userAddress, tokens) {
29427
- const balancePromises = tokens.map((token) => {
29428
- return this.getBalance(userAddress, token.address, token.chainId);
29429
- });
29430
- const results = await Promise.allSettled(balancePromises);
29431
- const balances = results.map((result) => {
29432
- if (result.status === "fulfilled") {
29433
- return result.value;
29434
- }
29435
- return "0";
29436
- });
29437
- return balances.map((balance, index) => {
29438
- return {
29439
- ...tokens[index],
29440
- balance,
29441
- };
29442
- });
29443
- }
29444
- /**
29445
- * Resolves when the transaction is confirmed, or fails after a timeout.
29446
- */
29447
- async waitForTransaction(txHash, { interval = 2_000, timeout = 40_000 } = {}) {
29448
- const startTime = Date.now();
29449
- while (true) {
29450
- const result = await this.server.getTransaction(txHash);
29451
- if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.NOT_FOUND) {
29452
- if (Date.now() - startTime > timeout) {
29453
- throw new Error(`Transaction ${txHash} not found within timeout`);
29454
- }
29455
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29456
- await new Promise((res) => setTimeout(res, interval));
29457
- continue;
29458
- }
29459
- if (result.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) {
29460
- return result.status;
29461
- }
29462
- throw new Error(`Transaction failed with status: ${result.status}`);
29463
- }
29464
- }
29465
- async isAccountActivated(address) {
29466
- try {
29467
- await this.getAccount(address);
29468
- return true;
29469
- }
29470
- catch (error) {
29471
- if (error?.message && error.message.includes("Account not found")) {
29472
- return false;
29473
- }
29474
- throw error;
29475
- }
29476
- }
29477
- async getAccount(address) {
29478
- return this.server.getAccount(address);
29479
- }
29480
- async sendTransaction(transaction) {
29481
- const transactionResponse = await this.server.sendTransaction(transaction);
29482
- if (transactionResponse.status === "ERROR") {
29483
- throw new Error("Error sending transaction");
29484
- }
29485
- return transactionResponse;
29486
- }
29487
- async prepareTransaction(transaction) {
29488
- return this.server.prepareTransaction(transaction);
29489
- }
29490
- }
29491
-
29492
- class XrplRpcClient {
29493
- rpcUrl;
29494
- constructor(rpcUrl) {
29495
- this.rpcUrl = rpcUrl;
29496
- }
29497
- async getBalance(address, tokenAddress) {
29498
- if (tokenAddress.toLowerCase() === nativeXrplTokenAddress.toLowerCase()) {
29499
- return this.getNativeBalance(address);
29500
- }
29501
- return this.getIssuedCurrencyBalance(address, tokenAddress);
29502
- }
29503
- async getAllBalances(address) {
29504
- const [nativeBalance, trustLineBalances] = await Promise.all([
29505
- this.getNativeBalance(address),
29506
- this.getTrustLines(address),
29507
- ]);
29508
- return [
29509
- {
29510
- balance: nativeBalance,
29511
- address: nativeXrplTokenAddress,
29512
- },
29513
- ...trustLineBalances.lines.map((line) => ({
29514
- balance: line.balance,
29515
- address: `${line.currency}.${line.account}`,
29516
- })),
29517
- ];
29518
- }
29519
- async getTrustLines(address, issuer) {
29520
- return this.call("account_lines", [
29521
- {
29522
- account: address,
29523
- ledger_index: "validated",
29524
- peer: issuer,
29525
- },
29526
- ]);
29527
- }
29528
- async getTrustLine(address, issuer, currency) {
29529
- const response = await this.getTrustLines(address, issuer);
29530
- const trustLine = response.lines.find((line) => line.currency === currency);
29531
- return trustLine ?? null;
29532
- }
29533
- async accountActivatedInfo(address) {
29534
- const serverState = await this.getServerState();
29535
- const reserveBaseBn = BigInt(serverState.state.validated_ledger.reserve_base);
29536
- try {
29537
- const accountInfo = await this.getAccountInfo(address);
29538
- const balanceBn = BigInt(accountInfo.account_data.Balance);
29539
- return {
29540
- isActivated: balanceBn >= reserveBaseBn,
29541
- reserveBaseBn,
29542
- };
29543
- }
29544
- catch (error) {
29545
- if (error.message?.includes("actNotFound")) {
29546
- return { isActivated: false, reserveBaseBn };
29547
- }
29548
- throw error;
29549
- }
29550
- }
29551
- /**
29552
- * Waits for a transaction to be validated and returns its final status.
29553
- * Resolves to 'success' or throws an error with the failed status.
29554
- */
29555
- async waitForTransaction(txHash, { interval = 2_000, timeout = 20_000 } = {}) {
29556
- const startTime = Date.now();
29557
- while (true) {
29558
- try {
29559
- const response = await this.call("tx", [
29560
- {
29561
- transaction: txHash,
29562
- binary: false,
29563
- },
29564
- ]);
29565
- if (!response.validated) {
29566
- if (Date.now() - startTime > timeout) {
29567
- throw new Error(`Transaction ${txHash} not validated within timeout`);
29568
- }
29569
- // eslint-disable-next-line @typescript-eslint/no-loop-func
29570
- await new Promise((res) => setTimeout(res, interval));
29571
- continue;
29572
- }
29573
- const status = response.meta?.TransactionResult;
29574
- if (status === XrplTxStatus.SUCCESS) {
29575
- return status;
29576
- }
29577
- else {
29578
- throw new Error(`Transaction failed with status: ${status}`);
29579
- }
29580
- }
29581
- catch (error) {
29582
- // txnNotFound = still pending or non-existent
29583
- if (error?.message?.includes("txnNotFound")) {
29584
- if (Date.now() - startTime > timeout) {
29585
- throw new Error(`Transaction ${txHash} not found 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
- throw error;
29592
- }
29593
- }
29594
- }
29595
- async getServerState() {
29596
- return this.call("server_state", [{}]);
29597
- }
29598
- async getAccountInfo(address) {
29599
- return this.call("account_info", [
29600
- {
29601
- account: address,
29602
- ledger_index: "validated",
29603
- },
29604
- ]);
29605
- }
29606
- /**
29607
- * Returns the balance of the user in the native XRP token
29608
- * formatted as a string
29609
- */
29610
- async getNativeBalance(address) {
29611
- const [accountInfo, serverState] = await Promise.all([
29612
- this.getAccountInfo(address),
29613
- this.getServerState(),
29614
- ]);
29615
- const balance = BigInt(accountInfo.account_data.Balance);
29616
- const ownerCount = BigInt(accountInfo.account_data.OwnerCount);
29617
- const reserveBase = BigInt(serverState.state.validated_ledger.reserve_base);
29618
- const reserveIncrement = BigInt(serverState.state.validated_ledger.reserve_inc);
29619
- const reserveBalance = reserveBase + ownerCount * reserveIncrement;
29620
- const spendableBalance = balance - reserveBalance;
29621
- return formatBNToReadable(spendableBalance, 6);
29622
- }
29623
- /**
29624
- * Returns the balance of the user in the given issued currency (e.g. RLUSD)
29625
- * formatted as a string
29626
- */
29627
- async getIssuedCurrencyBalance(address, tokenAddress) {
29628
- const response = await this.getTrustLines(address);
29629
- const tokenBalance = response.lines.find((line) => `${line.currency}.${line.account}` === tokenAddress);
29630
- return tokenBalance?.balance || "0";
29631
- }
29632
- async call(method, params) {
29633
- const response = await fetch(this.rpcUrl, {
29634
- method: "POST",
29635
- headers: {
29636
- "Content-Type": "application/json",
29637
- },
29638
- body: JSON.stringify({
29639
- jsonrpc: "2.0",
29640
- id: 1,
29641
- method,
29642
- params,
29643
- }),
29644
- });
29645
- const data = await response.json();
29646
- if (!data.result) {
29647
- throw new Error(`Invalid response from RPC (${method})`);
29648
- }
29649
- if ("error" in data.result) {
29650
- throw new Error(`Error from RPC (${method}): ${data.result.error}`);
29651
- }
29652
- return data.result;
29653
- }
29654
- }
29655
-
29656
- const clientCache = new Map();
29657
- async function getClient(chain) {
29658
- const key = `${chain.chainType}:${chain.chainId}`;
29659
- if (clientCache.has(key)) {
29660
- return clientCache.get(key);
29661
- }
29662
- const client = await createClient(chain);
29663
- clientCache.set(key, client);
29664
- return client;
29665
- }
29666
- async function createClient(chain) {
29667
- switch (chain.chainType) {
29668
- case squidTypes.ChainType.EVM:
29669
- return new ethers.JsonRpcProvider(chain.rpc);
29670
- case squidTypes.ChainType.COSMOS:
29671
- const rpcUrl = await getWorkingCosmosRpcUrl(chain);
29672
- return (await stargate.StargateClient.connect(rpcUrl));
29673
- case squidTypes.ChainType.SOLANA:
29674
- return new web3_js.Connection(SOLANA_RPC_URL);
29675
- case squidTypes.ChainType.BTC:
29676
- return null;
29677
- case squidTypes.ChainType.SUI:
29678
- return new client.SuiClient({
29679
- url: chain.rpc,
29680
- });
29681
- case squidTypes.ChainType.XRPL:
29682
- return new XrplRpcClient(chain.rpc);
29683
- case squidTypes.ChainType.STELLAR:
29684
- return new StellarRpcClient(chain.rpc);
29685
- }
29686
- }
29687
-
29688
- class StellarApiClient {
29689
- apiUrl;
29690
- constructor(apiUrl) {
29691
- this.apiUrl = apiUrl;
29692
- }
29693
- async getBaseReserve() {
29694
- const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
29695
- if (!response.ok) {
29696
- throw new Error(`Failed to fetch ledgers: ${response.status}`);
29697
- }
29698
- const ledgers = await response.json();
29699
- const latestLedger = ledgers?._embedded.records?.[0];
29700
- if (latestLedger?.base_reserve_in_stroops == null) {
29701
- throw new Error("Invalid ledger data");
29702
- }
29703
- const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
29704
- return baseReserveBn;
29705
- }
29706
- }
29707
-
29708
- const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
29709
- function useStellarAccountActivation({ address, chain, token, }) {
29710
- /**
29711
- * Checks if the destination account exists on the Stellar network
29712
- * Stellar accounts need to have a minimum balance before they can receive payments
29713
- */
29714
- const accountActivatedInfo = reactQuery.useQuery({
29715
- queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
29716
- queryFn: async () => {
29717
- if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
29718
- token?.type !== squidTypes.ChainType.STELLAR) {
29719
- return null;
29720
- }
29721
- if (!address) {
29722
- throw new Error("Destination address is required");
29723
- }
29724
- // TODO: update types
29725
- const [horizonApiUrl] = chain?.horizonRpcList;
29726
- if (typeof horizonApiUrl !== "string") {
29727
- throw new Error("Invalid Horizon API URL");
29728
- }
29729
- const stellarApiClient = new StellarApiClient(horizonApiUrl);
29730
- const reserveBase = await stellarApiClient.getBaseReserve();
29731
- // Stellar accounts require two base reserves to be activated
29732
- // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
29733
- const accountReserveBase = reserveBase * BigInt(2);
29734
- const stellarRpcClient = await getClient(chain);
29735
- const isActivated = await stellarRpcClient.isAccountActivated(address);
29736
- return {
29737
- isActivated,
29738
- reserveBaseBn: accountReserveBase,
29739
- };
29740
- },
29741
- enabled: !!address && chain?.chainType === squidTypes.ChainType.STELLAR,
29742
- refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
29743
- refetchOnWindowFocus: true,
29744
- });
29745
- return {
29746
- accountActivatedInfo,
29747
- };
29748
- }
29749
-
29750
29250
  const DEFAULT_REFRESH_INTERVAL_MS = 15000;
29751
29251
  const useEvmBalance = ({ chain, token, userAddress, enabled = true, refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS, }) => {
29752
29252
  const { isChainTypeConnected } = useWallet();
@@ -29941,6 +29441,8 @@ function useNativeTokenForChain(chain) {
29941
29441
  }
29942
29442
  };
29943
29443
  const nativeTokenForChainType = React.useMemo(() => {
29444
+ if (!chain)
29445
+ return undefined;
29944
29446
  return findNativeToken(getTokensForChainType(), chain);
29945
29447
  }, [chain]);
29946
29448
  return { nativeToken: nativeTokenForChainType };
@@ -30187,47 +29689,842 @@ const useNativeBalance = (chain) => {
30187
29689
  return false;
30188
29690
  switch (chain.chainType) {
30189
29691
  case squidTypes.ChainType.EVM:
30190
- return isEvmLoading;
29692
+ return isEvmLoading;
29693
+ case squidTypes.ChainType.COSMOS:
29694
+ return isCosmosLoading;
29695
+ case squidTypes.ChainType.BTC:
29696
+ return isBitcoinLoading;
29697
+ case squidTypes.ChainType.SOLANA:
29698
+ return isSolanaLoading;
29699
+ case squidTypes.ChainType.SUI:
29700
+ return isSuiLoading;
29701
+ case squidTypes.ChainType.XRPL:
29702
+ return isXrpLoading;
29703
+ case squidTypes.ChainType.STELLAR:
29704
+ return isStellarLoading;
29705
+ }
29706
+ }, [
29707
+ chain?.chainType,
29708
+ isEvmLoading,
29709
+ isCosmosLoading,
29710
+ isBitcoinLoading,
29711
+ isSolanaLoading,
29712
+ isSuiLoading,
29713
+ isXrpLoading,
29714
+ isStellarLoading,
29715
+ ]);
29716
+ return { nativeBalance, nativeBalanceFormatted, isLoading };
29717
+ };
29718
+
29719
+ function useHederaAccountActivation({ address, chain, token }) {
29720
+ const { balance: destNativeEvmBalance } = useEvmNativeBalance({
29721
+ chain,
29722
+ address,
29723
+ });
29724
+ const isHederaAccountActivated = React.useMemo(() => {
29725
+ if (token?.chainId !== CHAIN_IDS.HEDERA)
29726
+ return true;
29727
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
29728
+ return true;
29729
+ return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
29730
+ }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
29731
+ return {
29732
+ isHederaAccountActivated,
29733
+ };
29734
+ }
29735
+
29736
+ var hrc20 = [
29737
+ {
29738
+ inputs: [
29739
+ ],
29740
+ name: "associate",
29741
+ outputs: [
29742
+ {
29743
+ internalType: "uint256",
29744
+ name: "responseCode",
29745
+ type: "uint256"
29746
+ }
29747
+ ],
29748
+ stateMutability: "nonpayable",
29749
+ type: "function"
29750
+ }
29751
+ ];
29752
+
29753
+ /**
29754
+ * Client for interacting with the Hedera Mirrornode API.
29755
+ *
29756
+ * @docs https://mainnet.mirrornode.hedera.com/api/v1/docs
29757
+ */
29758
+ class HederaApiClient {
29759
+ apiUrl;
29760
+ constructor(apiUrl) {
29761
+ this.apiUrl = apiUrl;
29762
+ }
29763
+ async isTokenAssociated({ address, token, }) {
29764
+ const accountInfo = await this.getAccountInfo(address);
29765
+ // Unlimited auto associations
29766
+ if (accountInfo.max_automatic_token_associations === -1) {
29767
+ return true;
29768
+ }
29769
+ // If there's no unlimited auto-associations, we need to check if the token is already associated.
29770
+ const { tokens: accountTokens } = await this.getAccountTokens(address);
29771
+ const tokenId = convertEvmAddressToHederaAccountId(token.address);
29772
+ if (accountTokens.some((t) => t.token_id === tokenId)) {
29773
+ // Token is already associated
29774
+ return true;
29775
+ }
29776
+ // Finally, if not auto-associated, check if there is an available auto-association slot
29777
+ const autoAssociatedTokens = accountTokens.filter((t) => t.automatic_association);
29778
+ const remainingAutoAssociations = accountInfo.max_automatic_token_associations -
29779
+ autoAssociatedTokens.length;
29780
+ return remainingAutoAssociations > 0;
29781
+ }
29782
+ async getAccountInfo(address) {
29783
+ const data = await this.fetch(`accounts/${address}`);
29784
+ if (typeof data.max_automatic_token_associations !== "number") {
29785
+ throw new Error("Invalid max_automatic_token_associations type, expected number");
29786
+ }
29787
+ if (typeof data.balance.balance !== "number") {
29788
+ throw new Error("Invalid balance type, expected number");
29789
+ }
29790
+ if (!Array.isArray(data.balance.tokens)) {
29791
+ throw new Error("Invalid tokens type, expected array");
29792
+ }
29793
+ return data;
29794
+ }
29795
+ async getAccountTokens(address) {
29796
+ const data = await this.fetch(`accounts/${address}/tokens`);
29797
+ if (!Array.isArray(data.tokens)) {
29798
+ throw new Error("Invalid tokens type, expected array");
29799
+ }
29800
+ const firstToken = data.tokens[0];
29801
+ if (typeof firstToken?.automatic_association !== "boolean") {
29802
+ throw new Error("Invalid automatic_association type, expected boolean");
29803
+ }
29804
+ if (typeof firstToken?.token_id !== "string") {
29805
+ throw new Error("Invalid token_id type, expected string");
29806
+ }
29807
+ return data;
29808
+ }
29809
+ async fetch(path) {
29810
+ const url = new URL(path, this.apiUrl);
29811
+ const response = await fetch(url);
29812
+ if (!response.ok) {
29813
+ throw new Error(`Hedera API error: ${response.status}`);
29814
+ }
29815
+ return response.json();
29816
+ }
29817
+ }
29818
+
29819
+ hederaWalletConnect.type = "hederaWalletConnect";
29820
+ /**
29821
+ * Wagmi connector to interact with the Hedera EVM network via the WalletConnect protocol.
29822
+ *
29823
+ * The connector removes the need for a WalletConnect modal, and instead
29824
+ * communicates with the provided `extension` by opening the extension popup
29825
+ * for user interaction upon request.
29826
+ */
29827
+ function hederaWalletConnect(parameters) {
29828
+ const { extension, ...restParameters } = parameters;
29829
+ let provider_;
29830
+ let providerPromise;
29831
+ let connect;
29832
+ let displayUri;
29833
+ let sessionDelete;
29834
+ let disconnect;
29835
+ return createConnector((config) => ({
29836
+ id: `hedera-wc-${extension.id}`,
29837
+ name: extension.name,
29838
+ icon: extension.icon,
29839
+ type: hederaWalletConnect.type,
29840
+ async setup() {
29841
+ const provider = await this.getProvider().catch(() => null);
29842
+ if (!provider)
29843
+ return;
29844
+ if (!connect) {
29845
+ connect = this.onConnect.bind(this);
29846
+ provider.on("connect", connect);
29847
+ }
29848
+ if (!sessionDelete) {
29849
+ sessionDelete = this.onSessionDelete.bind(this);
29850
+ provider.on("session_delete", sessionDelete);
29851
+ }
29852
+ },
29853
+ async connect(params = {}) {
29854
+ try {
29855
+ const provider = await this.getProvider();
29856
+ if (!provider)
29857
+ throw new ProviderNotFoundError();
29858
+ if (!displayUri) {
29859
+ displayUri = this.onDisplayUri;
29860
+ provider.on("display_uri", displayUri);
29861
+ }
29862
+ if (!provider.session) {
29863
+ await provider.connect({
29864
+ ...("pairingTopic" in params
29865
+ ? { pairingTopic: params.pairingTopic }
29866
+ : {}),
29867
+ });
29868
+ }
29869
+ const accounts = (await provider.enable()).map((x) => viem.getAddress(x));
29870
+ const currentChainId = await this.getChainId();
29871
+ if (displayUri) {
29872
+ provider.removeListener("display_uri", displayUri);
29873
+ displayUri = undefined;
29874
+ }
29875
+ if (connect) {
29876
+ provider.removeListener("connect", connect);
29877
+ connect = undefined;
29878
+ }
29879
+ if (!disconnect) {
29880
+ disconnect = this.onDisconnect.bind(this);
29881
+ provider.on("disconnect", disconnect);
29882
+ }
29883
+ if (!sessionDelete) {
29884
+ sessionDelete = this.onSessionDelete.bind(this);
29885
+ provider.on("session_delete", sessionDelete);
29886
+ }
29887
+ return { accounts, chainId: currentChainId };
29888
+ }
29889
+ catch (error) {
29890
+ if (/(user rejected|connection request reset)/i.test(error?.message)) {
29891
+ throw new viem.UserRejectedRequestError(error);
29892
+ }
29893
+ throw error;
29894
+ }
29895
+ },
29896
+ async disconnect() {
29897
+ const provider = await this.getProvider();
29898
+ try {
29899
+ await provider?.disconnect();
29900
+ }
29901
+ catch (error) {
29902
+ if (!/No matching key/i.test(error.message))
29903
+ throw error;
29904
+ }
29905
+ finally {
29906
+ if (disconnect) {
29907
+ provider?.removeListener("disconnect", disconnect);
29908
+ disconnect = undefined;
29909
+ }
29910
+ if (!connect) {
29911
+ connect = this.onConnect.bind(this);
29912
+ provider?.on("connect", connect);
29913
+ }
29914
+ if (sessionDelete) {
29915
+ provider?.removeListener("session_delete", sessionDelete);
29916
+ sessionDelete = undefined;
29917
+ }
29918
+ }
29919
+ },
29920
+ async getAccounts() {
29921
+ const provider = await this.getProvider();
29922
+ return provider.accounts.map((x) => viem.getAddress(x));
29923
+ },
29924
+ async getProvider() {
29925
+ async function initProvider() {
29926
+ const optionalChains = config.chains.map((x) => x.id);
29927
+ if (!optionalChains.length)
29928
+ return;
29929
+ const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-CkrP1GZJ.js'); });
29930
+ const rawProvider = await EthereumProvider.init({
29931
+ ...restParameters,
29932
+ disableProviderPing: true,
29933
+ optionalChains,
29934
+ projectId: restParameters.projectId,
29935
+ rpcMap: Object.fromEntries(config.chains.map((chain) => {
29936
+ const [url] = extractRpcUrls({
29937
+ chain,
29938
+ transports: config.transports,
29939
+ });
29940
+ return [chain.id, url];
29941
+ })),
29942
+ showQrModal: false,
29943
+ // We need to specify a custom storage prefix to avoid conflicts with Wagmi's walletConnect instance
29944
+ // https://docs.reown.com/walletkit/web/usage#core-instance-sharing
29945
+ customStoragePrefix: "squid-hedera",
29946
+ });
29947
+ const proxiedProvider = new Proxy(rawProvider, {
29948
+ get(target, prop, receiver) {
29949
+ if (prop === "request") {
29950
+ return async (args) => {
29951
+ const signingMethods = [
29952
+ "eth_sendTransaction",
29953
+ "eth_signTransaction",
29954
+ ];
29955
+ if (signingMethods.includes(args.method)) {
29956
+ try {
29957
+ HederaExtensionHelper.extensionOpen(extension.id);
29958
+ }
29959
+ catch { }
29960
+ }
29961
+ // forward request to original provider
29962
+ return target.request(args);
29963
+ };
29964
+ }
29965
+ // forward all other properties/methods
29966
+ return Reflect.get(target, prop, receiver);
29967
+ },
29968
+ });
29969
+ return proxiedProvider;
29970
+ }
29971
+ if (!provider_) {
29972
+ if (!providerPromise)
29973
+ providerPromise = initProvider();
29974
+ provider_ = await providerPromise;
29975
+ provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY);
29976
+ }
29977
+ return provider_;
29978
+ },
29979
+ async getChainId() {
29980
+ const provider = await this.getProvider();
29981
+ return provider.chainId;
29982
+ },
29983
+ async isAuthorized() {
29984
+ try {
29985
+ const accounts = await this.getAccounts();
29986
+ return accounts.length > 0;
29987
+ }
29988
+ catch {
29989
+ return false;
29990
+ }
29991
+ },
29992
+ onAccountsChanged(accounts) {
29993
+ if (accounts.length === 0)
29994
+ this.onDisconnect();
29995
+ else
29996
+ config.emitter.emit("change", {
29997
+ accounts: accounts.map((x) => viem.getAddress(x)),
29998
+ });
29999
+ },
30000
+ onChainChanged(chain) {
30001
+ const chainId = Number(chain);
30002
+ config.emitter.emit("change", { chainId });
30003
+ },
30004
+ async onConnect(connectInfo) {
30005
+ const chainId = Number(connectInfo.chainId);
30006
+ const accounts = await this.getAccounts();
30007
+ config.emitter.emit("connect", { accounts, chainId });
30008
+ },
30009
+ async onDisconnect() {
30010
+ config.emitter.emit("disconnect");
30011
+ const provider = await this.getProvider();
30012
+ if (disconnect) {
30013
+ provider.removeListener("disconnect", disconnect);
30014
+ disconnect = undefined;
30015
+ }
30016
+ if (sessionDelete) {
30017
+ provider.removeListener("session_delete", sessionDelete);
30018
+ sessionDelete = undefined;
30019
+ }
30020
+ if (!connect) {
30021
+ connect = this.onConnect.bind(this);
30022
+ provider.on("connect", connect);
30023
+ }
30024
+ },
30025
+ onDisplayUri(uri) {
30026
+ config.emitter.emit("message", { type: "display_uri", data: uri });
30027
+ HederaExtensionHelper.extensionConnect(extension.id, uri);
30028
+ },
30029
+ onSessionDelete() {
30030
+ this.onDisconnect();
30031
+ },
30032
+ }));
30033
+ }
30034
+
30035
+ const createWagmiConfig = (squidChains, hederaExtensions) => {
30036
+ const filteredEvmChains = squidChains.filter((chain) => chain.chainType === squidTypes.ChainType.EVM);
30037
+ if (filteredEvmChains.length === 0) {
30038
+ throw new Error("At least one chain is required");
30039
+ }
30040
+ const wagmiChains = filteredEvmChains.map((chain) => {
30041
+ return viem.defineChain({
30042
+ id: Number(chain.chainId),
30043
+ name: chain.networkName,
30044
+ nativeCurrency: {
30045
+ name: chain.nativeCurrency.name,
30046
+ symbol: chain.nativeCurrency.symbol,
30047
+ decimals: chain.nativeCurrency.decimals,
30048
+ },
30049
+ rpcUrls: {
30050
+ public: {
30051
+ http: [chain.rpc],
30052
+ },
30053
+ default: {
30054
+ http: [chain.rpc],
30055
+ },
30056
+ },
30057
+ });
30058
+ });
30059
+ const wcMetadata = {
30060
+ url: SQUID_METADATA.url,
30061
+ name: SQUID_METADATA.name,
30062
+ icons: [SQUID_METADATA.icon],
30063
+ description: SQUID_METADATA.description,
30064
+ };
30065
+ return wagmi.createConfig({
30066
+ chains: wagmiChains,
30067
+ transports: Object.fromEntries(wagmiChains.map((chain) => [
30068
+ chain.id,
30069
+ wagmi.http(chain.rpcUrls.public.http[0] ?? ""),
30070
+ ])),
30071
+ connectors: [
30072
+ connectors.injected(),
30073
+ connectors.safe({
30074
+ allowedDomains: [/app.safe.global$/],
30075
+ }),
30076
+ connectors.metaMask({
30077
+ dappMetadata: {
30078
+ name: SQUID_METADATA.name,
30079
+ url: SQUID_METADATA.url,
30080
+ iconUrl: SQUID_METADATA.icon,
30081
+ },
30082
+ }),
30083
+ connectors.coinbaseWallet({
30084
+ appName: SQUID_METADATA.name,
30085
+ appLogoUrl: SQUID_METADATA.icon,
30086
+ }),
30087
+ connectors.walletConnect({
30088
+ projectId: WALLETCONNECT_PROJECT_ID,
30089
+ metadata: wcMetadata,
30090
+ }),
30091
+ ...hederaExtensions.map((extension) => hederaWalletConnect({
30092
+ projectId: WALLETCONNECT_PROJECT_ID,
30093
+ metadata: wcMetadata,
30094
+ extension,
30095
+ })),
30096
+ ],
30097
+ });
30098
+ };
30099
+ // Taken from wagmi docs
30100
+ // https://wagmi.sh/react/guides/ethers
30101
+ function clientToSigner(client) {
30102
+ const { account, chain, transport } = client;
30103
+ if (!account || !chain || !transport) {
30104
+ return undefined;
30105
+ }
30106
+ const network = {
30107
+ chainId: chain.id,
30108
+ name: chain.name,
30109
+ ensAddress: chain.contracts?.ensRegistry?.address,
30110
+ };
30111
+ const provider = new ethers.BrowserProvider(transport, network);
30112
+ const signer = new ethers.JsonRpcSigner(provider, account.address);
30113
+ return signer;
30114
+ }
30115
+
30116
+ function useEvmSigner({ chainId }) {
30117
+ const { connector } = wagmi.useAccount();
30118
+ const { data: client } = wagmi.useWalletClient({ chainId, connector });
30119
+ const signer = React.useMemo(() => (client ? clientToSigner(client) : undefined), [client]);
30120
+ return { signer };
30121
+ }
30122
+ const useSigner = ({ chain }) => {
30123
+ const evmChainId = chain?.chainType === squidTypes.ChainType.EVM ? Number(chain.chainId) : undefined;
30124
+ // EVM and Cosmos need a different signer for each chain
30125
+ // This is not the case for Solana or Bitcoin as there's only one chain in those ecosystems
30126
+ const { signer: evmSigner } = useEvmSigner({ chainId: evmChainId });
30127
+ const { signer: cosmosSigner } = useCosmosSigner({ chain });
30128
+ const { signer: solanaSigner } = useSolanaContext();
30129
+ const { signer: bitcoinSigner } = useBitcoinContext();
30130
+ const { signer: suiSigner } = useSuiContext();
30131
+ const { signer: xrplSigner } = useXrplContext();
30132
+ const { signer: stellarSigner } = useStellarContext();
30133
+ const isEvmSignerReady = !!evmSigner;
30134
+ const isSolanaSignerReady = !!solanaSigner;
30135
+ const isCosmosSignerReady = !!cosmosSigner;
30136
+ const isBitcoinSignerReady = !!bitcoinSigner;
30137
+ const isSuiSignerReady = !!suiSigner;
30138
+ const isXrplSignerReady = !!xrplSigner;
30139
+ const isStellarSignerReady = !!stellarSigner;
30140
+ const isSignerReady = React.useMemo(() => {
30141
+ if (!chain?.chainType)
30142
+ return false;
30143
+ switch (chain.chainType) {
30144
+ case squidTypes.ChainType.EVM:
30145
+ return isEvmSignerReady;
30191
30146
  case squidTypes.ChainType.COSMOS:
30192
- return isCosmosLoading;
30147
+ return isCosmosSignerReady;
30193
30148
  case squidTypes.ChainType.BTC:
30194
- return isBitcoinLoading;
30149
+ return isBitcoinSignerReady;
30195
30150
  case squidTypes.ChainType.SOLANA:
30196
- return isSolanaLoading;
30151
+ return isSolanaSignerReady;
30197
30152
  case squidTypes.ChainType.SUI:
30198
- return isSuiLoading;
30153
+ return isSuiSignerReady;
30199
30154
  case squidTypes.ChainType.XRPL:
30200
- return isXrpLoading;
30155
+ return isXrplSignerReady;
30201
30156
  case squidTypes.ChainType.STELLAR:
30202
- return isStellarLoading;
30157
+ return isStellarSignerReady;
30203
30158
  }
30204
30159
  }, [
30205
30160
  chain?.chainType,
30206
- isEvmLoading,
30207
- isCosmosLoading,
30208
- isBitcoinLoading,
30209
- isSolanaLoading,
30210
- isSuiLoading,
30211
- isXrpLoading,
30212
- isStellarLoading,
30161
+ isEvmSignerReady,
30162
+ isCosmosSignerReady,
30163
+ isBitcoinSignerReady,
30164
+ isSolanaSignerReady,
30165
+ isSuiSignerReady,
30166
+ isXrplSignerReady,
30167
+ isStellarSignerReady,
30213
30168
  ]);
30214
- return { nativeBalance, nativeBalanceFormatted, isLoading };
30169
+ return {
30170
+ isSignerReady,
30171
+ evmSigner,
30172
+ cosmosSigner,
30173
+ bitcoinSigner,
30174
+ solanaSigner,
30175
+ suiSigner,
30176
+ xrplSigner,
30177
+ stellarSigner,
30178
+ };
30215
30179
  };
30216
30180
 
30217
- function useHederaAccountActivation({ address, chain, token }) {
30218
- const { balance: destNativeEvmBalance } = useEvmNativeBalance({
30219
- chain,
30220
- address,
30181
+ function useHederaTokenAssociations({ address, chain, token }) {
30182
+ const publicClient = wagmi.usePublicClient({
30183
+ chainId: Number(CHAIN_IDS.HEDERA),
30184
+ });
30185
+ const { evmSigner } = useSigner({ chain });
30186
+ const queryClient = reactQuery.useQueryClient();
30187
+ /**
30188
+ * Creates a token association transaction where the destination account authorizes to receive the given token
30189
+ */
30190
+ const associateToken = reactQuery.useMutation({
30191
+ mutationFn: async () => {
30192
+ try {
30193
+ if (!evmSigner) {
30194
+ throw new Error("EVM signer not found");
30195
+ }
30196
+ if (chain?.chainId !== CHAIN_IDS.HEDERA ||
30197
+ token?.chainId !== CHAIN_IDS.HEDERA) {
30198
+ throw new Error("Chain and token to associate must be on Hedera");
30199
+ }
30200
+ const tokenAddress = parseEvmAddress(token.address);
30201
+ if (!tokenAddress) {
30202
+ throw new Error("Invalid token address");
30203
+ }
30204
+ const userAddress = parseEvmAddress(address);
30205
+ if (!userAddress) {
30206
+ throw new Error("Invalid user address");
30207
+ }
30208
+ const encodedData = viem.encodeFunctionData({
30209
+ abi: hrc20,
30210
+ functionName: "associate",
30211
+ args: [],
30212
+ });
30213
+ const txRes = await evmSigner.sendTransaction({
30214
+ data: encodedData,
30215
+ to: tokenAddress,
30216
+ chainId: chain.chainId,
30217
+ });
30218
+ const receipt = await txRes.wait();
30219
+ if (receipt?.status !== 1) {
30220
+ throw new Error(`Transaction failed with status: ${receipt?.status}`);
30221
+ }
30222
+ return true;
30223
+ }
30224
+ catch (error) {
30225
+ console.error("Error associating Hedera token:", error);
30226
+ return false;
30227
+ }
30228
+ },
30229
+ async onSuccess() {
30230
+ queryClient.refetchQueries({
30231
+ queryKey: getPrefixKey(exports.QueryKeys.IsHederaTokenAssociated),
30232
+ });
30233
+ },
30234
+ });
30235
+ /**
30236
+ * Checks if the destination account has associated the given token.
30237
+ *
30238
+ * Hedera requires accounts to associate a token before being able to receive it.
30239
+ *
30240
+ * Accounts which have max. associations set to -1 can receive any token without previous association
30241
+ */
30242
+ const isTokenAssociated = reactQuery.useQuery({
30243
+ queryKey: keys().isHederaTokenAssociated(address, token?.chainId, token?.type, token?.address),
30244
+ queryFn: async () => {
30245
+ if (token?.chainId !== CHAIN_IDS.HEDERA) {
30246
+ return true;
30247
+ }
30248
+ // The native HBAR token doesn't need an association
30249
+ if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
30250
+ return true;
30251
+ }
30252
+ if (!chain || !address || !publicClient) {
30253
+ throw new Error("Missing required parameters");
30254
+ }
30255
+ // TODO: update types
30256
+ const apiUrl = chain?.chainConfig?.hedera?.mirrorNodeUrl;
30257
+ if (!apiUrl) {
30258
+ throw new Error("Missing Hedera mirror node URL in chain config");
30259
+ }
30260
+ const hederaApiClient = new HederaApiClient(apiUrl);
30261
+ return hederaApiClient.isTokenAssociated({
30262
+ address,
30263
+ token,
30264
+ });
30265
+ },
30266
+ enabled: !!address && !!publicClient && token?.chainId === CHAIN_IDS.HEDERA,
30221
30267
  });
30222
- const isHederaAccountActivated = React.useMemo(() => {
30223
- if (token?.chainId !== CHAIN_IDS.HEDERA)
30224
- return true;
30225
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase())
30226
- return true;
30227
- return (!!destNativeEvmBalance?.value && destNativeEvmBalance.value > BigInt(0));
30228
- }, [token?.chainId, token?.address, destNativeEvmBalance?.value]);
30229
30268
  return {
30230
- isHederaAccountActivated,
30269
+ isTokenAssociated,
30270
+ associateToken,
30271
+ };
30272
+ }
30273
+
30274
+ const useKeyboardNavigation = ({ onEscape }) => {
30275
+ const onKeyDown = React.useCallback((event) => {
30276
+ if (event.key === "Escape") {
30277
+ onEscape?.();
30278
+ return;
30279
+ }
30280
+ }, [onEscape]);
30281
+ React.useEffect(() => {
30282
+ document.addEventListener("keydown", onKeyDown, false);
30283
+ return () => {
30284
+ document.removeEventListener("keydown", onKeyDown, false);
30285
+ };
30286
+ }, [onKeyDown]);
30287
+ };
30288
+
30289
+ const useSquidQueryClient = () => {
30290
+ const queryClient = reactQuery.useQueryClient();
30291
+ const invalidateQueries = (key) => {
30292
+ const prefixKey = getPrefixKey(key);
30293
+ queryClient.invalidateQueries(prefixKey);
30294
+ };
30295
+ const refetchQueries = (key) => {
30296
+ const prefixKey = getPrefixKey(key);
30297
+ queryClient.refetchQueries(prefixKey);
30298
+ };
30299
+ const invalidateAndRefetchQueries = (key) => {
30300
+ invalidateQueries(key);
30301
+ refetchQueries(key);
30302
+ };
30303
+ return {
30304
+ invalidateQueries,
30305
+ refetchQueries,
30306
+ invalidateAndRefetchQueries,
30307
+ };
30308
+ };
30309
+
30310
+ class StellarApiClient {
30311
+ apiUrl;
30312
+ constructor(apiUrl) {
30313
+ this.apiUrl = apiUrl;
30314
+ }
30315
+ async getBaseReserve() {
30316
+ const response = await fetch(`${this.apiUrl}/ledgers?order=desc&limit=1`);
30317
+ if (!response.ok) {
30318
+ throw new Error(`Failed to fetch ledgers: ${response.status}`);
30319
+ }
30320
+ const ledgers = await response.json();
30321
+ const latestLedger = ledgers?._embedded.records?.[0];
30322
+ if (latestLedger?.base_reserve_in_stroops == null) {
30323
+ throw new Error("Invalid ledger data");
30324
+ }
30325
+ const baseReserveBn = BigInt(latestLedger.base_reserve_in_stroops);
30326
+ return baseReserveBn;
30327
+ }
30328
+ async getTrustLines(userAddress) {
30329
+ const response = await fetch(`${this.apiUrl}/accounts/${userAddress}`);
30330
+ if (!response.ok) {
30331
+ throw new Error(`Failed to fetch account data: ${response.statusText}`);
30332
+ }
30333
+ const data = await response.json();
30334
+ if (!Array.isArray(data?.balances)) {
30335
+ throw new Error("Invalid response from Horizon API");
30336
+ }
30337
+ const assets = data.balances.filter(isValidHorizonAsset);
30338
+ return assets.filter(isValidIssuedAsset);
30339
+ }
30340
+ async getTrustLine(address, asset) {
30341
+ const trustLines = await this.getTrustLines(address);
30342
+ const trustLine = trustLines.find((line) => line.asset_code === asset.code && line.asset_issuer === asset.issuer);
30343
+ return trustLine ?? null;
30344
+ }
30345
+ }
30346
+
30347
+ const DEFAULT_REFETCH_INTERVAL$2 = 20_000;
30348
+ function useStellarAccountActivation({ address, chain, token, }) {
30349
+ /**
30350
+ * Checks if the destination account exists on the Stellar network
30351
+ * Stellar accounts need to have a minimum balance before they can receive payments
30352
+ */
30353
+ const accountActivatedInfo = reactQuery.useQuery({
30354
+ queryKey: keys().stellarAccountActivatedInfo(address, chain?.chainId, chain?.chainType),
30355
+ queryFn: async () => {
30356
+ if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
30357
+ token?.type !== squidTypes.ChainType.STELLAR) {
30358
+ return null;
30359
+ }
30360
+ if (!address) {
30361
+ throw new Error("Destination address is required");
30362
+ }
30363
+ const horizonApiUrl = getStellarHorizonApiUrl(chain);
30364
+ if (!horizonApiUrl) {
30365
+ throw new Error("Invalid Horizon API URL");
30366
+ }
30367
+ const stellarApiClient = new StellarApiClient(horizonApiUrl);
30368
+ const reserveBase = await stellarApiClient.getBaseReserve();
30369
+ // Stellar accounts require two base reserves to be activated
30370
+ // https://developers.stellar.org/docs/learn/fundamentals/lumens#minimum-balance
30371
+ const accountReserveBase = reserveBase * BigInt(2);
30372
+ const stellarRpcClient = await getClient(chain);
30373
+ const isActivated = await stellarRpcClient.isAccountActivated(address);
30374
+ return {
30375
+ isActivated,
30376
+ reserveBaseBn: accountReserveBase,
30377
+ };
30378
+ },
30379
+ enabled: !!address && chain?.chainType === squidTypes.ChainType.STELLAR,
30380
+ refetchInterval: DEFAULT_REFETCH_INTERVAL$2,
30381
+ refetchOnWindowFocus: true,
30382
+ });
30383
+ return {
30384
+ accountActivatedInfo,
30385
+ };
30386
+ }
30387
+
30388
+ /**
30389
+ * Maximum asset amount on Stellar
30390
+ * @see https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/assets#amount-precision
30391
+ */
30392
+ const MAX_ASSET_AMOUNT = "922337203685.4775807";
30393
+ const DEFAULT_REFETCH_INTERVAL$1 = 20_000;
30394
+ function useStellarTrustLine({ address, chain, token, amount }) {
30395
+ const { stellarSigner } = useSigner({ chain });
30396
+ const queryClient = reactQuery.useQueryClient();
30397
+ /**
30398
+ * Retrieves the destination account's trust line data for the given token
30399
+ */
30400
+ const trustLineQuery = reactQuery.useQuery({
30401
+ queryKey: keys().stellarTrustLine(token?.address, chain?.chainId, address),
30402
+ queryFn: async () => {
30403
+ if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
30404
+ token?.type !== squidTypes.ChainType.STELLAR) {
30405
+ return null;
30406
+ }
30407
+ if (!address || !isStellarAddressValid(address)) {
30408
+ return null;
30409
+ }
30410
+ // Only issued tokens require trust lines
30411
+ // Other token types like contract tokens don't need trust lines
30412
+ if (!isStellarIssuedToken(token)) {
30413
+ return null;
30414
+ }
30415
+ const asset = getStellarTrustLineAsset(token);
30416
+ if (!asset) {
30417
+ throw new Error("Invalid asset");
30418
+ }
30419
+ const horizonApiUrl = getStellarHorizonApiUrl(chain);
30420
+ if (!horizonApiUrl) {
30421
+ throw new Error("Invalid Horizon API URL");
30422
+ }
30423
+ const stellarApiClient = new StellarApiClient(horizonApiUrl);
30424
+ return stellarApiClient.getTrustLine(address, asset);
30425
+ },
30426
+ enabled: !!address &&
30427
+ chain?.chainType === squidTypes.ChainType.STELLAR &&
30428
+ token?.type === squidTypes.ChainType.STELLAR,
30429
+ refetchInterval: DEFAULT_REFETCH_INTERVAL$1,
30430
+ });
30431
+ /**
30432
+ * Creates a trust line where the destination account authorizes to receive the given token
30433
+ */
30434
+ const createTrustLine = reactQuery.useMutation({
30435
+ mutationFn: async () => {
30436
+ try {
30437
+ if (!stellarSigner) {
30438
+ throw new Error("Stellar signer not found");
30439
+ }
30440
+ if (!address) {
30441
+ throw new Error("Destination address is required");
30442
+ }
30443
+ if (chain?.chainType !== squidTypes.ChainType.STELLAR ||
30444
+ token?.type !== squidTypes.ChainType.STELLAR) {
30445
+ throw new Error("Chain and token to approve must be a Stellar token");
30446
+ }
30447
+ if (!isStellarIssuedToken(token)) {
30448
+ throw new Error("Token must be a Stellar issued token");
30449
+ }
30450
+ const assetInfo = getStellarTrustLineAsset(token);
30451
+ if (!assetInfo) {
30452
+ throw new Error("Invalid asset");
30453
+ }
30454
+ const network = getStellarNetwork(chain.chainId);
30455
+ if (network == null) {
30456
+ throw new Error(`Stellar network not found for chain ${chain.chainId}`);
30457
+ }
30458
+ const client = await getClient(chain);
30459
+ const account = await client.getAccount(address);
30460
+ const asset = new stellarSdk.Asset(assetInfo.code, assetInfo.issuer);
30461
+ const changeTrustOperation = stellarSdk.Operation.changeTrust({
30462
+ asset,
30463
+ limit: MAX_ASSET_AMOUNT,
30464
+ });
30465
+ const builtTransaction = new stellarSdk.TransactionBuilder(account, {
30466
+ fee: (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
30467
+ networkPassphrase: network,
30468
+ })
30469
+ .addOperation(changeTrustOperation)
30470
+ .setTimeout(300)
30471
+ .build();
30472
+ const { signedTxXdr } = await stellarSigner.signTransaction(builtTransaction.toXDR(), {
30473
+ networkPassphrase: network,
30474
+ });
30475
+ const signedTransaction = new stellarSdk.Transaction(signedTxXdr, network);
30476
+ const sentTransaction = await client.sendTransaction(signedTransaction);
30477
+ const txStatus = await client.waitForTransaction(sentTransaction.hash, {
30478
+ interval: 1_000,
30479
+ });
30480
+ if (txStatus !== stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) {
30481
+ throw new Error(`Transaction failed with status: ${txStatus}`);
30482
+ }
30483
+ return true;
30484
+ }
30485
+ catch (error) {
30486
+ console.error("Error creating trust line:", error);
30487
+ return false;
30488
+ }
30489
+ },
30490
+ async onSuccess() {
30491
+ queryClient.invalidateQueries({
30492
+ queryKey: getPrefixKey(exports.QueryKeys.StellarTrustLine),
30493
+ });
30494
+ },
30495
+ });
30496
+ /**
30497
+ * Checks if the destination account has created a trust line to receive the given token.
30498
+ */
30499
+ const isTrustLineApproved = reactQuery.useQuery({
30500
+ queryKey: keys().isStellarTrustLineApproved(address, token?.chainId, token?.type, token?.address, trustLineQuery.data?.limit, amount),
30501
+ queryFn: async () => {
30502
+ if (token?.type !== squidTypes.ChainType.STELLAR) {
30503
+ return true;
30504
+ }
30505
+ // The native Stellar token doesn't need a trust line
30506
+ if (token.address.toLowerCase() === nativeStellarTokenAddress.toLowerCase()) {
30507
+ return true;
30508
+ }
30509
+ if (!amount) {
30510
+ throw new Error("Amount is required");
30511
+ }
30512
+ const limitBn = BigNumber(trustLineQuery.data?.limit || "0");
30513
+ const balanceBn = BigNumber(trustLineQuery.data?.balance || "0");
30514
+ const availableAllowanceBn = limitBn.minus(balanceBn);
30515
+ const amountBn = BigNumber(formatBNToReadable(amount, token.decimals));
30516
+ return availableAllowanceBn.gte(amountBn);
30517
+ },
30518
+ enabled: !!address &&
30519
+ !!amount &&
30520
+ !trustLineQuery?.isLoading &&
30521
+ trustLineQuery?.isFetched &&
30522
+ token?.type === squidTypes.ChainType.STELLAR,
30523
+ });
30524
+ return {
30525
+ createTrustLine,
30526
+ trustLineQuery,
30527
+ isTrustLineApproved,
30231
30528
  };
30232
30529
  }
30233
30530
 
@@ -30755,6 +31052,143 @@ const useSingleTokenPrice = (tokenData) => {
30755
31052
  return { tokenPrice, getUSDValue };
30756
31053
  };
30757
31054
 
31055
+ const TEMPO_FEE_MANAGER_ADDRESS = "0xfeec000000000000000000000000000000000000";
31056
+ const feeManagerAbi = [
31057
+ {
31058
+ name: "userTokens",
31059
+ type: "function",
31060
+ stateMutability: "view",
31061
+ inputs: [{ type: "address", name: "user" }],
31062
+ outputs: [{ type: "address" }],
31063
+ },
31064
+ ];
31065
+ const isTempoChain = (chainId) => chainId === CHAIN_IDS.TEMPO;
31066
+ /**
31067
+ * Convert a fee amount from virtual USD (18 decimals, attodollars)
31068
+ * to 6-decimal TIP-20 stablecoin units (microdollars).
31069
+ * All TIP-20 tokens on Tempo have 6 decimals.
31070
+ */
31071
+ const convertTempoFeeToStablecoinUnits = (feeIn18Dec) => feeIn18Dec / BigInt(10 ** 12);
31072
+
31073
+ const BALANCE_QUERY_STALE_TIME = 10_000;
31074
+ /**
31075
+ * Returns raw on-chain gas token data for Tempo chains, or null when the source
31076
+ * chain is not Tempo.
31077
+ */
31078
+ const useTempoFeeCheck = ({ fromChain, fromToken, }) => {
31079
+ const { connectedAddresses } = useWallet();
31080
+ const evmAddress = parseEvmAddress(connectedAddresses[squidTypes.ChainType.EVM]);
31081
+ const isTempo = isTempoChain(fromChain?.chainId);
31082
+ const chainId = Number(fromChain?.chainId);
31083
+ // Read account-level gas token preference from FeeManager precompile
31084
+ // See docs: https://docs.tempo.xyz/protocol/fees/spec-fee#account-level
31085
+ const { data: accountGasTokenAddress } = wagmi.useReadContract({
31086
+ address: TEMPO_FEE_MANAGER_ADDRESS,
31087
+ abi: feeManagerAbi,
31088
+ functionName: "userTokens",
31089
+ args: evmAddress ? [evmAddress] : undefined,
31090
+ chainId,
31091
+ query: {
31092
+ enabled: isTempo && !!evmAddress,
31093
+ staleTime: 30_000,
31094
+ },
31095
+ });
31096
+ const hasAccountFeePreference = !!accountGasTokenAddress &&
31097
+ // By default, the zero address is returned when the user has no preference set
31098
+ viem.zeroAddress.toLowerCase() !== accountGasTokenAddress.toLowerCase();
31099
+ // Fetch balance of the account-preferred gas token (if set)
31100
+ const { data: accountGasTokenBalance } = wagmi.useBalance({
31101
+ address: evmAddress,
31102
+ token: hasAccountFeePreference ? accountGasTokenAddress : undefined,
31103
+ chainId,
31104
+ query: {
31105
+ enabled: isTempo && hasAccountFeePreference && !!evmAddress,
31106
+ staleTime: BALANCE_QUERY_STALE_TIME,
31107
+ },
31108
+ });
31109
+ const fromTokenAddress = parseEvmAddress(fromToken?.address);
31110
+ const { data: fromTokenBalance } = wagmi.useBalance({
31111
+ address: evmAddress,
31112
+ token: fromTokenAddress,
31113
+ chainId,
31114
+ query: {
31115
+ enabled: isTempo && !!evmAddress,
31116
+ staleTime: BALANCE_QUERY_STALE_TIME,
31117
+ },
31118
+ });
31119
+ if (!isTempo)
31120
+ return null;
31121
+ if (hasAccountFeePreference) {
31122
+ if (!accountGasTokenAddress || !accountGasTokenBalance)
31123
+ return null;
31124
+ return {
31125
+ gasTokenAddress: accountGasTokenAddress,
31126
+ gasBalance: accountGasTokenBalance.value,
31127
+ };
31128
+ }
31129
+ if (!fromTokenAddress || !fromTokenBalance)
31130
+ return null;
31131
+ return {
31132
+ gasTokenAddress: fromTokenAddress,
31133
+ gasBalance: fromTokenBalance.value,
31134
+ };
31135
+ };
31136
+
31137
+ function useSourceChainGasToken({ fromChain, fromToken, }) {
31138
+ const { nativeToken } = useNativeTokenForChain(fromChain);
31139
+ const { evmTokens } = useSquidTokens();
31140
+ const { nativeBalance } = useNativeBalance(fromChain);
31141
+ const tempoFeeData = useTempoFeeCheck({ fromChain, fromToken });
31142
+ const chainFeeParams = React.useMemo(() => {
31143
+ if (fromToken?.address == null ||
31144
+ fromChain?.chainId == null ||
31145
+ nativeBalance?.value == null) {
31146
+ return null;
31147
+ }
31148
+ if (isTempoChain(fromChain.chainId)) {
31149
+ if (!tempoFeeData)
31150
+ return null;
31151
+ const fromTokenPaysNetworkFee = tempoFeeData.gasTokenAddress.toLowerCase() ===
31152
+ fromToken.address.toLowerCase();
31153
+ return {
31154
+ fromTokenPaysNetworkFee,
31155
+ gasTokenBalanceWei: tempoFeeData.gasBalance,
31156
+ normalizeFee: convertTempoFeeToStablecoinUnits,
31157
+ };
31158
+ }
31159
+ return {
31160
+ fromTokenPaysNetworkFee: fromToken.address.toLowerCase() ===
31161
+ chainTypeToNativeTokenAddressMap[fromToken.type].toLowerCase(),
31162
+ gasTokenBalanceWei: nativeBalance.value,
31163
+ normalizeFee: (fee) => fee,
31164
+ };
31165
+ }, [
31166
+ fromToken?.address,
31167
+ fromToken?.type,
31168
+ fromChain?.chainId,
31169
+ nativeBalance?.value,
31170
+ tempoFeeData,
31171
+ ]);
31172
+ const gasToken = React.useMemo(() => {
31173
+ if (fromChain?.chainId == null || fromToken?.address == null)
31174
+ return undefined;
31175
+ if (isTempoChain(fromChain.chainId)) {
31176
+ if (!tempoFeeData)
31177
+ return undefined;
31178
+ return evmTokens.find((t) => t.chainId === fromChain.chainId &&
31179
+ t.address.toLowerCase() === tempoFeeData.gasTokenAddress.toLowerCase());
31180
+ }
31181
+ return nativeToken;
31182
+ }, [
31183
+ fromChain?.chainId,
31184
+ fromToken?.address,
31185
+ nativeToken,
31186
+ tempoFeeData,
31187
+ evmTokens,
31188
+ ]);
31189
+ return { gasToken, chainFeeParams };
31190
+ }
31191
+
30758
31192
  const MAX_COINGECKO_QUERY_TOKENS = 100;
30759
31193
  const fetchHistoricalData = async (coingeckoId, timeFrame) => {
30760
31194
  const now = Math.floor(Date.now() / 1000);
@@ -30905,24 +31339,23 @@ const calculateTotalNativeFees = ({ expressFeeCost, firstFeeCost, firstGasCost,
30905
31339
  (sameTokenBetweenFees
30906
31340
  ? BigInt(firstFeeCost?.amount ?? "0") + BigInt(firstGasCost?.amount ?? "0")
30907
31341
  : BigInt(firstGasCost?.amount ?? "0"));
30908
- const isFromBalanceEnoughToSwap = ({ isFromTokenNative, fromAmount, totalFeesInNativeTokenPlusRatio, nativeTokenBalanceFromChainWei, }) => {
30909
- const fromAmountBigInt = BigInt(fromAmount ?? "0");
30910
- const totalFeesInNativeTokenPlusRatioBigInt = totalFeesInNativeTokenPlusRatio;
30911
- const nativeTokenBalanceFromChainWeiBigInt = nativeTokenBalanceFromChainWei;
30912
- return isFromTokenNative
30913
- ? fromAmountBigInt + totalFeesInNativeTokenPlusRatioBigInt <=
30914
- nativeTokenBalanceFromChainWeiBigInt
30915
- : totalFeesInNativeTokenPlusRatioBigInt <=
30916
- nativeTokenBalanceFromChainWeiBigInt;
30917
- };
30918
- const calculateMinAmountValueWarnMsg = ({ isFromTokenNative, nativeTokenBalanceFromChainWei, sourceChainNativeTokenDecimals, totalFeesInNativeTokenPlusRatio, }) => isFromTokenNative
30919
- ? (() => {
30920
- const minAmount = nativeTokenBalanceFromChainWei - totalFeesInNativeTokenPlusRatio;
30921
- return minAmount > BigInt(0)
30922
- ? formatBNToReadable(minAmount, sourceChainNativeTokenDecimals)
30923
- : "0";
30924
- })()
30925
- : undefined;
31342
+ const isGasBalanceEnough = ({ fromAmount, networkFeesWei, chainFeeParams, }) => {
31343
+ if (!chainFeeParams)
31344
+ return false;
31345
+ if (chainFeeParams.fromTokenPaysNetworkFee) {
31346
+ return (BigInt(fromAmount ?? "0") + networkFeesWei <=
31347
+ chainFeeParams.gasTokenBalanceWei);
31348
+ }
31349
+ return networkFeesWei <= chainFeeParams.gasTokenBalanceWei;
31350
+ };
31351
+ const calculateMinAmountValueWarnMsg = ({ networkFeesWei, chainFeeParams, fromTokenDecimals, }) => {
31352
+ if (!chainFeeParams?.fromTokenPaysNetworkFee)
31353
+ return undefined;
31354
+ const minAmount = chainFeeParams.gasTokenBalanceWei - networkFeesWei;
31355
+ if (minAmount <= BigInt(0))
31356
+ return undefined;
31357
+ return formatBNToReadable(minAmount, fromTokenDecimals);
31358
+ };
30926
31359
  /**
30927
31360
  * Calculates the estimated duration of a route
30928
31361
  *
@@ -30961,12 +31394,10 @@ const formatEstimatedRouteDuration = ({ estimatedRouteDuration, isSingleChainRou
30961
31394
  * @returns {Object} An object containing various estimate results and calculations, including token information,
30962
31395
  * amounts, fees, gas costs, and other relevant data for the transaction.
30963
31396
  */
30964
- const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, nativeTokenBalanceFromChainWei, }) => {
31397
+ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, collectFees, chainFeeParams, gasToken, }) => {
30965
31398
  const fromToken = findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken);
30966
31399
  const fromAmount = squidRoute?.estimate?.fromAmount;
30967
31400
  const fromAmountFormatted = formatAmount(fromAmount, fromToken?.decimals);
30968
- const sourceChainNativeToken = findNativeToken(tokens, fromChain);
30969
- const destChainNativeToken = findNativeToken(tokens, toChain);
30970
31401
  const toAmountUSD = squidRoute?.estimate?.toAmountUSD;
30971
31402
  const exchangeRate = squidRoute?.estimate.exchangeRate ?? "0";
30972
31403
  const toAmountMinUSD = squidRoute?.estimate.toAmountMinUSD ?? "0";
@@ -30985,10 +31416,6 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
30985
31416
  const expectedGasRefundCostUSD = convertTokenAmountToUSD(formatBNToReadable(expectedGasRefundCost, firstFeeCost?.token.decimals ?? 18), firstFeeCost?.token.usdPrice ?? "0");
30986
31417
  const sameTokenBetweenFees = firstFeeCost?.token.address === firstGasCost?.token.address &&
30987
31418
  firstFeeCost?.token.chainId === firstGasCost?.token.chainId;
30988
- const isFromTokenNative =
30989
- // TODO: temporary fix, currently nativeCurrency.symbol is not always in uppercase
30990
- fromToken?.symbol.toUpperCase() ===
30991
- fromChain?.nativeCurrency.symbol.toUpperCase();
30992
31419
  const totalNativeFees = calculateTotalNativeFees({
30993
31420
  expressFeeCost,
30994
31421
  firstFeeCost,
@@ -30996,30 +31423,32 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
30996
31423
  sameTokenBetweenFees,
30997
31424
  });
30998
31425
  const totalFeesInNativeTokenPlusRatio = (totalNativeFees * BigInt(110)) / BigInt(100);
30999
- const fromBalanceEnoughToSwap = isFromBalanceEnoughToSwap({
31000
- isFromTokenNative,
31426
+ const networkFeesWei = chainFeeParams?.normalizeFee(totalFeesInNativeTokenPlusRatio) ?? BigInt(0);
31427
+ const gasBalanceEnough = isGasBalanceEnough({
31428
+ chainFeeParams,
31001
31429
  fromAmount,
31002
- fromTokenDecimals: fromToken?.decimals,
31003
- totalFeesInNativeTokenPlusRatio,
31004
- nativeTokenBalanceFromChainWei,
31430
+ networkFeesWei,
31005
31431
  });
31006
31432
  const minAmountValueWarnMsg = calculateMinAmountValueWarnMsg({
31007
- isFromTokenNative,
31008
- nativeTokenBalanceFromChainWei,
31009
- sourceChainNativeTokenDecimals: sourceChainNativeToken?.decimals,
31010
- totalFeesInNativeTokenPlusRatio,
31433
+ chainFeeParams,
31434
+ networkFeesWei,
31435
+ fromTokenDecimals: fromToken?.decimals,
31011
31436
  });
31012
31437
  const isSingleChainRoute = fromChain?.chainId === toChain?.chainId;
31013
31438
  const estimatedRouteDuration = formatEstimatedRouteDuration({
31014
31439
  estimatedRouteDuration: squidRoute?.estimate?.estimatedRouteDuration,
31015
31440
  isSingleChainRoute,
31016
31441
  });
31442
+ // native fees + fromAmount (if native)
31443
+ const totalGasBalanceNeeded = networkFeesWei +
31444
+ BigInt(chainFeeParams?.fromTokenPaysNetworkFee ? fromAmount ?? 0 : 0);
31445
+ const gasBalanceNeeded = gasToken
31446
+ ? formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals)
31447
+ : undefined;
31017
31448
  return {
31018
31449
  fromToken,
31019
31450
  fromAmount,
31020
31451
  fromAmountFormatted,
31021
- sourceChainNativeToken,
31022
- destChainNativeToken,
31023
31452
  toAmountUSD,
31024
31453
  exchangeRate,
31025
31454
  toAmountMinUSD,
@@ -31034,12 +31463,12 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
31034
31463
  expectedGasRefundCost,
31035
31464
  expectedGasRefundCostUSD,
31036
31465
  sameTokenBetweenFees,
31037
- isFromTokenNative,
31466
+ fromTokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31038
31467
  totalNativeFees,
31039
- totalFeesInNativeTokenPlusRatio,
31040
- fromBalanceEnoughToSwap,
31468
+ gasBalanceEnough,
31041
31469
  minAmountValueWarnMsg,
31042
31470
  estimatedRouteDuration,
31471
+ gasBalanceNeeded,
31043
31472
  };
31044
31473
  };
31045
31474
  /**
@@ -31073,28 +31502,6 @@ const calculateTotalWithRefundEstimate = (allFeeCosts, expectedGasRefundCost, ge
31073
31502
  feeToken: firstFeeCost?.token,
31074
31503
  };
31075
31504
  };
31076
- /**
31077
- * Calculates the proposed gas amount for the destination chain.
31078
- *
31079
- * @param destChainNativeToken - The symbol of the native token for the destination chain.
31080
- * @returns An object containing the proposed gas amount value and the currency symbol.
31081
- */
31082
- const getProposedGasDestinationAmount = (destChainNativeToken) => {
31083
- const gasAmounts = {
31084
- GLMR: 5.289,
31085
- ETH: 0.0009,
31086
- AVAX: 0.115,
31087
- BNB: 0.00425,
31088
- FTM: 4.45,
31089
- CELO: 3.052,
31090
- KAVA: 2.339,
31091
- MATIC: 1.795,
31092
- };
31093
- return {
31094
- value: gasAmounts[destChainNativeToken ?? ""] ?? 0,
31095
- currency: destChainNativeToken,
31096
- };
31097
- };
31098
31505
 
31099
31506
  function useSendTransactionGas({ chain, token, from, }) {
31100
31507
  return reactQuery.useQuery({
@@ -31116,7 +31523,11 @@ function useSendTransactionGas({ chain, token, from, }) {
31116
31523
  // Some RPC providers require the sender to have enough balance, otherwise estimation reverts
31117
31524
  // so we'll try to use the user provided address when possible
31118
31525
  const sender = from || dummyAddress;
31119
- if (token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase()) {
31526
+ // Tempo has no native token, so `value` transfers are rejected by the EVM.
31527
+ // We can simulate an ERC20 transfer instead
31528
+ const supportsNativeTransfers = chain.chainId !== CHAIN_IDS.TEMPO;
31529
+ const isNativeToken = token.address.toLowerCase() === nativeEvmTokenAddress.toLowerCase();
31530
+ if (isNativeToken && supportsNativeTransfers) {
31120
31531
  const gas = await client.estimateGas({
31121
31532
  from: sender,
31122
31533
  to: dummyAddress,
@@ -31136,7 +31547,14 @@ function useSendTransactionGas({ chain, token, from, }) {
31136
31547
  data,
31137
31548
  chainId: chain.chainId,
31138
31549
  });
31139
- return gas * maxFeePerGas;
31550
+ const feeWei = gas * maxFeePerGas;
31551
+ // Tempo fees are denominated in virtual USD (18 dec, 1e-18 USD units).
31552
+ // Convert to 6-dec stablecoin units so all callers receive a
31553
+ // consistent unit without needing to know about the chain.
31554
+ if (chain.chainId === CHAIN_IDS.TEMPO) {
31555
+ return convertTempoFeeToStablecoinUnits(feeWei);
31556
+ }
31557
+ return feeWei;
31140
31558
  }
31141
31559
  case squidTypes.ChainType.COSMOS: {
31142
31560
  // TODO: get gas estimation from backend
@@ -31183,19 +31601,33 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31183
31601
  token,
31184
31602
  from,
31185
31603
  });
31186
- const { nativeBalance } = useNativeBalance(chain);
31187
- const isNativeBalanceEnoughToPayGasFees = React.useMemo(() => {
31188
- if (!nativeBalance?.value || !amount || !token?.decimals) {
31604
+ const { chainFeeParams, gasToken } = useSourceChainGasToken({
31605
+ fromChain: chain,
31606
+ fromToken: token,
31607
+ });
31608
+ const gasBalanceEnough = React.useMemo(() => {
31609
+ if (!amount || !token?.decimals)
31189
31610
  return false;
31190
- }
31191
- const isTokenNative = token.address.toLowerCase() ===
31192
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31193
- if (isTokenNative) {
31194
- return (parseToBigInt(amount, token.decimals) + estimatedGas <=
31195
- nativeBalance.value);
31196
- }
31197
- return estimatedGas <= nativeBalance.value;
31198
- }, [amount, estimatedGas, nativeBalance?.value, token]);
31611
+ return isGasBalanceEnough({
31612
+ fromAmount: parseToBigInt(amount, token.decimals).toString(),
31613
+ networkFeesWei: estimatedGas,
31614
+ chainFeeParams,
31615
+ });
31616
+ }, [amount, estimatedGas, token, chainFeeParams]);
31617
+ const gasBalanceNeeded = React.useMemo(() => {
31618
+ if (!amount || !token?.decimals || !gasToken?.decimals)
31619
+ return undefined;
31620
+ // native fees + fromAmount (if native)
31621
+ const totalGasBalanceNeeded = estimatedGas +
31622
+ parseToBigInt(chainFeeParams?.fromTokenPaysNetworkFee ? amount ?? "0" : "0", token.decimals);
31623
+ return formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals);
31624
+ }, [
31625
+ amount,
31626
+ chainFeeParams?.fromTokenPaysNetworkFee,
31627
+ estimatedGas,
31628
+ gasToken?.decimals,
31629
+ token?.decimals,
31630
+ ]);
31199
31631
  const isBalanceEnough = React.useMemo(() => {
31200
31632
  if (token?.decimals == null || !balance || !amount)
31201
31633
  return false;
@@ -31203,28 +31635,21 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
31203
31635
  parseToBigInt(amount, token.decimals));
31204
31636
  }, [amount, balance, token?.decimals]);
31205
31637
  const minAmountValueWarnMsg = React.useMemo(() => {
31206
- if (!token?.address || !nativeBalance?.value || !estimatedGas)
31638
+ if (!token?.address || !estimatedGas || !chainFeeParams)
31207
31639
  return undefined;
31208
- const isFromTokenNative = token.address.toLowerCase() ===
31209
- chainTypeToNativeTokenAddressMap[token.type].toLowerCase();
31210
31640
  return calculateMinAmountValueWarnMsg({
31211
- isFromTokenNative,
31212
- nativeTokenBalanceFromChainWei: nativeBalance.value,
31213
- sourceChainNativeTokenDecimals: nativeBalance.decimals,
31214
- totalFeesInNativeTokenPlusRatio: estimatedGas,
31641
+ chainFeeParams,
31642
+ networkFeesWei: estimatedGas,
31643
+ fromTokenDecimals: token.decimals,
31215
31644
  });
31216
- }, [
31217
- estimatedGas,
31218
- nativeBalance?.decimals,
31219
- nativeBalance?.value,
31220
- token?.address,
31221
- token?.type,
31222
- ]);
31645
+ }, [estimatedGas, token, chainFeeParams]);
31223
31646
  return {
31224
31647
  estimatedGas,
31648
+ gasBalanceNeeded,
31225
31649
  isBalanceEnough,
31650
+ tokenPaysNetworkFee: chainFeeParams?.fromTokenPaysNetworkFee ?? false,
31226
31651
  isLoading,
31227
- isNativeBalanceEnoughToPayGasFees,
31652
+ isGasBalanceEnough: gasBalanceEnough,
31228
31653
  minAmountValueWarnMsg,
31229
31654
  };
31230
31655
  }
@@ -31394,15 +31819,18 @@ async function sendTransactionXrpl({ amount, signer, to, token, from, }) {
31394
31819
  txHash: hash,
31395
31820
  };
31396
31821
  }
31397
- const [currency, issuer] = token.address.split(".");
31822
+ const asset = parseXrplTokenAddress(token.address);
31823
+ if (!asset) {
31824
+ throw new Error("Invalid asset");
31825
+ }
31398
31826
  const amountFormatted = formatBNToReadable(amount, token.decimals);
31399
31827
  const { hash } = await signer.signAndSubmit({
31400
31828
  network: xrplNetwork,
31401
31829
  tx: {
31402
31830
  ...baseTransaction,
31403
31831
  Amount: {
31404
- currency,
31405
- issuer,
31832
+ currency: asset.code,
31833
+ issuer: asset.issuer,
31406
31834
  value: amountFormatted,
31407
31835
  },
31408
31836
  },
@@ -32140,7 +32568,7 @@ const useApproval = ({ squidRoute, }) => {
32140
32568
  * On Error: Showing the error message if any
32141
32569
  * @returns {boolean} approved
32142
32570
  */
32143
- const routeApproved = reactQuery.useQuery(keys().routeApproved(squidRoute, allowanceInWei), async () => {
32571
+ const routeApproved = reactQuery.useQuery(keys().routeApproved(squidRoute, allowanceInWei, erc20AllowanceQueryEnabled, hasAllowance), async () => {
32144
32572
  if (erc20AllowanceQueryEnabled) {
32145
32573
  return hasAllowance;
32146
32574
  }
@@ -32283,7 +32711,7 @@ const useApproval = ({ squidRoute, }) => {
32283
32711
  // This is to ensure we're using the latest expiry timestamp
32284
32712
  if (squidRoute) {
32285
32713
  queryClient.refetchQueries({
32286
- queryKey: keys().transaction(squidRoute.params.fromChain, squidRoute.params.toChain, squidRoute.params.toToken, squidRoute.params.fromToken, fromPrice, squidRoute.params.slippage, squidRoute.params.receiveGasOnDestination, squidRoute.params.fromAddress, squidRoute.params.bypassGuardrails, squidRoute.params.toAddress, squidRoute.params.fallbackAddresses?.[0]?.address, squidRoute.params.quoteOnly, getChainType(squidRoute.params.fromChain), squidRoute.params.preHook, squidRoute.params.postHook,
32714
+ 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,
32287
32715
  // TODO: update types
32288
32716
  squidRoute.params?.overrideGasRefundAddress),
32289
32717
  });
@@ -32302,17 +32730,30 @@ const AXELAR_PROVIDER_IMAGE_URL = "https://raw.githubusercontent.com/0xsquid/ass
32302
32730
  const useEstimate = (squidRoute) => {
32303
32731
  const collectFees = useConfigStore((state) => state.config.collectFees);
32304
32732
  const { tokens } = useSquidTokens();
32305
- const { fromChain, toChain } = useSwap();
32306
- const { nativeBalance } = useNativeBalance(fromChain);
32733
+ const { fromChain, toChain, fromPrice } = useSwap();
32734
+ const fromToken = React.useMemo(() => findToken(tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken), [tokens, squidRoute?.params.fromChain, squidRoute?.params.fromToken]);
32735
+ const { chainFeeParams, gasToken } = useSourceChainGasToken({
32736
+ fromChain,
32737
+ fromToken,
32738
+ });
32307
32739
  const estimateResults = React.useMemo(() => calculateEstimateResults({
32308
32740
  squidRoute,
32309
32741
  tokens,
32310
32742
  fromChain,
32311
32743
  toChain,
32312
32744
  collectFees: !!collectFees && collectFees.fee > 0,
32313
- nativeTokenBalanceFromChainWei: nativeBalance?.value ?? BigInt("0"),
32314
- }), [squidRoute, tokens, fromChain, toChain, collectFees, nativeBalance]);
32315
- const balanceFormatted = useMultiChainBalance({
32745
+ chainFeeParams,
32746
+ gasToken,
32747
+ }), [
32748
+ squidRoute,
32749
+ tokens,
32750
+ fromChain,
32751
+ toChain,
32752
+ collectFees,
32753
+ chainFeeParams,
32754
+ gasToken,
32755
+ ]);
32756
+ const fromBalanceFormatted = useMultiChainBalance({
32316
32757
  chain: fromChain,
32317
32758
  token: estimateResults.fromToken,
32318
32759
  userAddress: squidRoute?.params.fromAddress ?? "",
@@ -32324,7 +32765,6 @@ const useEstimate = (squidRoute) => {
32324
32765
  estimateResults.expectedGasRefundCost,
32325
32766
  getUSDValue,
32326
32767
  ]);
32327
- const proposedGasDestinationAmount = React.useMemo(() => getProposedGasDestinationAmount(estimateResults.destChainNativeToken?.symbol), [estimateResults.destChainNativeToken]);
32328
32768
  const { feeCostsFormatted, totalFeeCostsUsd } = React.useMemo(() => {
32329
32769
  const feeCosts = squidRoute?.estimate.feeCosts ?? [];
32330
32770
  const feeCostsRenamed = [];
@@ -32383,21 +32823,21 @@ const useEstimate = (squidRoute) => {
32383
32823
  totalFeeCostsUsd: totalFeeCostsUsdFormatted,
32384
32824
  };
32385
32825
  }, [squidRoute?.estimate.feeCosts]);
32386
- const slippageFormatted =
32387
- // TODO: update types
32388
- Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) +
32389
- "%";
32390
- const enoughBalanceToSwap = +balanceFormatted >= 0 &&
32391
- +balanceFormatted > +estimateResults.fromAmountFormatted;
32826
+ const slippageFormatted = Number(squidRoute?.estimate?.aggregateSlippage ?? 0).toFixed(2) + "%";
32827
+ const fromBalanceEnoughToSwap = React.useMemo(() => {
32828
+ const fromBalanceNum = Number(fromBalanceFormatted ?? 0);
32829
+ const fromPriceNum = Number(fromPrice ?? 0);
32830
+ return fromBalanceNum >= fromPriceNum;
32831
+ }, [fromBalanceFormatted, fromPrice]);
32392
32832
  return {
32393
32833
  ...estimateResults,
32394
- balanceFormatted,
32834
+ fromBalanceFormatted,
32395
32835
  slippageFormatted,
32396
32836
  totalWithRefundEstimate,
32397
- proposedGasDestinationAmount,
32398
- enoughBalanceToSwap,
32837
+ fromBalanceEnoughToSwap,
32399
32838
  feeCostsFormatted,
32400
32839
  totalFeeCostsUsd,
32840
+ gasToken,
32401
32841
  };
32402
32842
  };
32403
32843
 
@@ -35857,20 +36297,42 @@ const useExecuteTransaction = (squidRoute) => {
35857
36297
  if (stellarNetwork == null) {
35858
36298
  throw new Error(`No Stellar network found for chainId ${fromChainId}`);
35859
36299
  }
35860
- const { data: xdrHex, gasPrice } = route.transactionRequest;
35861
- const { address } = await stellarSigner.getAddress();
35862
36300
  const client = await getClient(fromChain);
35863
- const account = await client.getAccount(address);
35864
- const operation = stellarSdk.xdr.Operation.fromXDR(xdrHex, "hex");
35865
- const builtTransaction = new stellarSdk.TransactionBuilder(account, {
35866
- fee: gasPrice || (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
35867
- networkPassphrase: stellarNetwork,
35868
- })
35869
- .addOperation(operation)
35870
- .setTimeout(300)
35871
- .build();
35872
- const preparedTransaction = await client.prepareTransaction(builtTransaction);
35873
- const { signedTxXdr } = await stellarSigner.signTransaction(preparedTransaction.toXDR(), {
36301
+ // TODO: at the moment we're receiving different formats depending on the route type.
36302
+ //
36303
+ // For OnChainExecution we get a single operation xdr string, so we need to build
36304
+ // a full transaction frontend-side (by adding the operation to a new transaction).
36305
+ //
36306
+ // For DepositAddressCalldata we get the full transaction xdr,
36307
+ // so we just need to send it to the wallet for signing.
36308
+ //
36309
+ // In the future this will be standardized backend-side so we have a unified flow for all route types.
36310
+ let txXdrToSign;
36311
+ if (route.transactionRequest.type === squidTypes.SquidDataType.OnChainExecution) {
36312
+ const { data: operationXdrHex, gasPrice } = route.transactionRequest;
36313
+ const { address } = await stellarSigner.getAddress();
36314
+ const account = await client.getAccount(address);
36315
+ const operation = stellarSdk.xdr.Operation.fromXDR(operationXdrHex, "hex");
36316
+ const builtTransaction = new stellarSdk.TransactionBuilder(account, {
36317
+ fee: gasPrice || (BigInt(stellarSdk.BASE_FEE) * BigInt(2)).toString(),
36318
+ networkPassphrase: stellarNetwork,
36319
+ })
36320
+ .addOperation(operation)
36321
+ .setTimeout(300)
36322
+ .build();
36323
+ // This is a smart-contract transaction, so it needs to be simulated first
36324
+ const preparedTransaction = await client.prepareTransaction(builtTransaction);
36325
+ txXdrToSign = preparedTransaction.toXDR();
36326
+ }
36327
+ else if (route.transactionRequest.type === squidTypes.SquidDataType.DepositAddressCalldata) {
36328
+ // For this route type, we get the full transaction xdr in the data field.
36329
+ // This is a payment transaction, so simulation must be skipped.
36330
+ txXdrToSign = route.transactionRequest.data;
36331
+ }
36332
+ else {
36333
+ throw new Error(`Invalid route type ${route.transactionRequest.type}`);
36334
+ }
36335
+ const { signedTxXdr } = await stellarSigner.signTransaction(txXdrToSign, {
35874
36336
  networkPassphrase: stellarNetwork,
35875
36337
  });
35876
36338
  const signedTransaction = new stellarSdk.Transaction(signedTxXdr, stellarNetwork);
@@ -36143,7 +36605,7 @@ const useGetRoute = () => {
36143
36605
  });
36144
36606
  // Cache the route data
36145
36607
  // Useful when the getRoute mutation is called from another hook
36146
- queryClient.setQueryData(keys().transaction(fromChain, toChain, toToken.address, fromToken.address, fromPrice, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, swapRoute?.fallbackAddress, quoteOnly, fromChainType, config.preHook, config.postHook, overrideGasRefundAddress), route);
36608
+ 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);
36147
36609
  return route;
36148
36610
  });
36149
36611
  };
@@ -36166,14 +36628,13 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
36166
36628
  const sourceUserAddress = isDepositAddressEnabled && isAvailableAsPaymentMethod
36167
36629
  ? depositRefundAddress ?? sourceConnectedAddress
36168
36630
  : sourceConnectedAddress;
36169
- const squidRouteQueryKeys = React.useMemo(() => keys().transaction(fromChain?.chainId, toChain?.chainId, toToken?.address, fromToken?.address, fromPrice, config.slippage, config.enableGetGasOnDestination, sourceUserAddress, config.degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChain?.chainType, config.preHook, config.postHook, config.overrideGasRefundAddress), [
36631
+ 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), [
36170
36632
  fromChain?.chainId,
36171
36633
  toChain?.chainId,
36172
36634
  toToken?.address,
36173
36635
  fromToken?.address,
36174
36636
  fromPrice,
36175
36637
  config.slippage,
36176
- config.enableGetGasOnDestination,
36177
36638
  sourceUserAddress,
36178
36639
  config.degenMode,
36179
36640
  destinationAddress,
@@ -36445,35 +36906,6 @@ const useAvatar = (seed = zeroAddress) => {
36445
36906
  return avatar || "";
36446
36907
  };
36447
36908
 
36448
- const useUserParams = () => {
36449
- const enableGetGasOnDestination = useConfigStore((state) => state.config.enableGetGasOnDestination);
36450
- const { fromToken, toToken, toChain, fromChain } = useSwap();
36451
- // =============
36452
- // GAS
36453
- // =============
36454
- const getGasOnDestSupportedForThisRoute = React.useMemo(() =>
36455
- // Not supporting get gas on dest for same chains
36456
- fromChain?.chainId !== toChain?.chainId &&
36457
- // If the destination chain is cosmos, we don't support getting gas there
36458
- toChain?.chainType !== squidTypes.ChainType.COSMOS &&
36459
- // Not supporting get gas on dest for same tokens (bridge)
36460
- ((fromToken?.subGraphIds?.some((sgi) => !!toToken?.subGraphIds?.includes(sgi)) &&
36461
- toToken?.subGraphIds?.some((sgi) => !!fromToken?.subGraphIds?.includes(sgi))) ||
36462
- // Except for uusdc -> uusdc
36463
- (fromToken?.subGraphIds?.includes("uusdc") &&
36464
- toToken?.subGraphIds?.includes("uusdc"))), [
36465
- fromChain?.chainId,
36466
- fromToken?.subGraphIds,
36467
- toChain?.chainId,
36468
- toToken?.subGraphIds,
36469
- toChain?.chainType,
36470
- ]);
36471
- return {
36472
- gasEnabled: enableGetGasOnDestination && getGasOnDestSupportedForThisRoute,
36473
- getGasOnDestSupportedForThisRoute,
36474
- };
36475
- };
36476
-
36477
36909
  const useAddToken = (chainToCompare, tokenToCompare) => {
36478
36910
  const { chain: currentEvmChain } = wagmi.useAccount();
36479
36911
  const { connector } = wagmi.useAccount();
@@ -36658,12 +37090,12 @@ function useXrplTrustLine({ address, chain, token, amount }) {
36658
37090
  if (!address || !isXrplAddressValid(address)) {
36659
37091
  return null;
36660
37092
  }
36661
- const [currency, issuer] = token.address.split(".");
36662
- if (!currency || !issuer) {
37093
+ const trustLineAsset = parseXrplTokenAddress(token.address);
37094
+ if (!trustLineAsset) {
36663
37095
  return null;
36664
37096
  }
36665
37097
  const xrplClient = await getClient(chain);
36666
- const trustLine = await xrplClient.getTrustLine(address, issuer, currency);
37098
+ const trustLine = await xrplClient.getTrustLine(address, trustLineAsset);
36667
37099
  return trustLine;
36668
37100
  },
36669
37101
  enabled: !!address &&
@@ -36991,6 +37423,9 @@ exports.getSecretNetworkBalances = getSecretNetworkBalances;
36991
37423
  exports.getSendTxStatusRefetchInterval = getSendTxStatusRefetchInterval;
36992
37424
  exports.getSourceExplorerTxUrl = getSourceExplorerTxUrl;
36993
37425
  exports.getStatusCode = getStatusCode;
37426
+ exports.getStellarHorizonApiUrl = getStellarHorizonApiUrl;
37427
+ exports.getStellarNetwork = getStellarNetwork;
37428
+ exports.getStellarTrustLineAsset = getStellarTrustLineAsset;
36994
37429
  exports.getStepStatuses = getStepStatuses;
36995
37430
  exports.getStepsInfos = getStepsInfos;
36996
37431
  exports.getSuggestedAmountsForCurrency = getSuggestedAmountsForCurrency;
@@ -37027,8 +37462,13 @@ exports.isOnChainTxData = isOnChainTxData;
37027
37462
  exports.isProblematicConnector = isProblematicConnector;
37028
37463
  exports.isSolanaAddressValid = isSolanaAddressValid;
37029
37464
  exports.isStatusError = isStatusError;
37465
+ exports.isStellarAddressValid = isStellarAddressValid;
37466
+ exports.isStellarIssuedToken = isStellarIssuedToken;
37467
+ exports.isStellarToken = isStellarToken;
37030
37468
  exports.isSwapRouteError = isSwapRouteError;
37031
37469
  exports.isUserRejectionError = isUserRejectionError;
37470
+ exports.isValidHorizonAsset = isValidHorizonAsset;
37471
+ exports.isValidIssuedAsset = isValidIssuedAsset;
37032
37472
  exports.isWalletAddressValid = isWalletAddressValid;
37033
37473
  exports.isXamanXAppContext = isXamanXAppContext;
37034
37474
  exports.isXionSmartContractAddress = isXionSmartContractAddress;
@@ -37047,6 +37487,7 @@ exports.normalizeTokenSymbol = normalizeTokenSymbol;
37047
37487
  exports.parseEvmAddress = parseEvmAddress;
37048
37488
  exports.parseToBigInt = parseToBigInt;
37049
37489
  exports.parseXrplPaymentTx = parseXrplPaymentTx;
37490
+ exports.parseXrplTokenAddress = parseXrplTokenAddress;
37050
37491
  exports.populateWallets = populateWallets;
37051
37492
  exports.randomIntFromInterval = randomIntFromInterval;
37052
37493
  exports.redirectToExtensionsStore = redirectToExtensionsStore;
@@ -37061,6 +37502,7 @@ exports.sortAddressBook = sortAddressBook;
37061
37502
  exports.sortAllTokens = sortAllTokens;
37062
37503
  exports.sortTokensBySharedSubgraphIds = sortTokensBySharedSubgraphIds;
37063
37504
  exports.sortWallets = sortWallets;
37505
+ exports.stellarAddressToScVal = stellarAddressToScVal;
37064
37506
  exports.suggestChainOrThrow = suggestChainOrThrow;
37065
37507
  exports.transactionErrorCode = transactionErrorCode;
37066
37508
  exports.trimExtraDecimals = trimExtraDecimals;
@@ -37125,6 +37567,7 @@ exports.useSendTransactionStore = useSendTransactionStore;
37125
37567
  exports.useSigner = useSigner;
37126
37568
  exports.useSingleTokenPrice = useSingleTokenPrice;
37127
37569
  exports.useSolanaNativeBalance = useSolanaNativeBalance;
37570
+ exports.useSourceChainGasToken = useSourceChainGasToken;
37128
37571
  exports.useSquid = useSquid;
37129
37572
  exports.useSquidChains = useSquidChains;
37130
37573
  exports.useSquidQueryClient = useSquidQueryClient;
@@ -37132,6 +37575,7 @@ exports.useSquidStore = useSquidStore;
37132
37575
  exports.useSquidTokens = useSquidTokens;
37133
37576
  exports.useStellarAccountActivation = useStellarAccountActivation;
37134
37577
  exports.useStellarNativeBalance = useStellarNativeBalance;
37578
+ exports.useStellarTrustLine = useStellarTrustLine;
37135
37579
  exports.useSuggestedFiatAmounts = useSuggestedFiatAmounts;
37136
37580
  exports.useSuiNativeBalance = useSuiNativeBalance;
37137
37581
  exports.useSwap = useSwap;
@@ -37140,7 +37584,6 @@ exports.useSwapTransactionStatus = useSwapTransactionStatus;
37140
37584
  exports.useTokensData = useTokensData;
37141
37585
  exports.useTrackSearchEmpty = useTrackSearchEmpty;
37142
37586
  exports.useTransactionStore = useTransactionStore;
37143
- exports.useUserParams = useUserParams;
37144
37587
  exports.useWallet = useWallet;
37145
37588
  exports.useWalletStore = useWalletStore;
37146
37589
  exports.useWallets = useWallets;
@@ -37149,4 +37592,4 @@ exports.useXrplTrustLine = useXrplTrustLine;
37149
37592
  exports.waitForReceiptWithRetry = waitForReceiptWithRetry;
37150
37593
  exports.walletIconBaseUrl = walletIconBaseUrl;
37151
37594
  exports.walletSupportsChainType = walletSupportsChainType;
37152
- //# sourceMappingURL=index-5cyMUZhY.js.map
37595
+ //# sourceMappingURL=index-BSX11dad.js.map