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