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