@0xsquid/react-hooks 8.11.1-beta-danal.2 → 8.12.1-beta-arc.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/canton/cantonConnector.d.ts +5 -2
- package/dist/core/canton/errors.d.ts +4 -0
- package/dist/core/canton/loopCantonConnector.d.ts +38 -0
- package/dist/core/client/index.d.ts +0 -1
- package/dist/core/constants.d.ts +1 -2
- package/dist/core/types/canton.d.ts +37 -3
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/tokens.d.ts +1 -8
- package/dist/core/types/wallet.d.ts +1 -8
- package/dist/hooks/chains/useSquidChains.d.ts +0 -1
- package/dist/hooks/store/useDepositAddressStore.d.ts +0 -6
- package/dist/hooks/swap/useDepositAddress.d.ts +0 -2
- package/dist/hooks/tokens/useSquidTokens.d.ts +0 -1
- package/dist/hooks/transaction/send/useEstimateSendTransactionGas.d.ts +3 -0
- package/dist/hooks/wallet/useMultiChainWallet.d.ts +0 -38
- package/dist/hooks/wallet/useWallet.d.ts +0 -42
- package/dist/{index-nTqoaD0B.js → index-BBJD55XX.js} +1110 -593
- package/dist/index-BBJD55XX.js.map +1 -0
- package/dist/{index-ByakOYXP.js → index-CLzjMzS_.js} +1109 -590
- package/dist/index-CLzjMzS_.js.map +1 -0
- package/dist/{index.es-DtFDz6qc.js → index.es-BVCh4tlV.js} +2 -2
- package/dist/{index.es-DtFDz6qc.js.map → index.es-BVCh4tlV.js.map} +1 -1
- package/dist/{index.es--AR2n7a0.js → index.es-BiK_wjSb.js} +2 -2
- package/dist/{index.es--AR2n7a0.js.map → index.es-BiK_wjSb.js.map} +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/{secretService-29Ak2qp5.js → secretService-BZ06HTOJ.js} +2 -2
- package/dist/{secretService-29Ak2qp5.js.map → secretService-BZ06HTOJ.js.map} +1 -1
- package/dist/{secretService-cgr3P1zw.js → secretService-DkE9yz1o.js} +2 -2
- package/dist/{secretService-cgr3P1zw.js.map → secretService-DkE9yz1o.js.map} +1 -1
- package/dist/server.esm.js +1 -6
- package/dist/server.esm.js.map +1 -1
- package/dist/server.js +1 -6
- package/dist/server.js.map +1 -1
- package/dist/services/external/rpcService.d.ts +2 -4
- package/dist/services/index.d.ts +0 -1
- package/dist/services/internal/arcService.d.ts +7 -0
- package/dist/services/internal/assetsService.d.ts +9 -3
- package/dist/services/internal/cantonService.d.ts +2 -0
- package/dist/services/internal/cantonTransferService.d.ts +3 -3
- package/dist/services/internal/configService.d.ts +14 -3
- package/dist/services/internal/estimateService.d.ts +13 -3
- package/dist/services/internal/searchService.d.ts +1 -2
- package/dist/services/internal/tempoService.d.ts +2 -0
- package/dist/services/internal/transactionService.d.ts +0 -8
- package/dist/services/internal/walletService.d.ts +1 -1
- package/dist/{stellarService.client-B__dTUBZ.js → stellarService.client-DtA9zkRt.js} +2 -2
- package/dist/{stellarService.client-B__dTUBZ.js.map → stellarService.client-DtA9zkRt.js.map} +1 -1
- package/dist/{stellarService.client-D-zkMI5s.js → stellarService.client-fnyXRadt.js} +2 -2
- package/dist/{stellarService.client-D-zkMI5s.js.map → stellarService.client-fnyXRadt.js.map} +1 -1
- package/dist/tests/arcConfigService.test.d.ts +1 -0
- package/dist/tests/arcGasAccounting.test.d.ts +1 -0
- package/dist/tests/arcWalletService.test.d.ts +1 -0
- package/dist/tests/fixtures/arc.d.ts +8 -0
- package/dist/tests/loopCantonConnector.test.d.ts +1 -0
- package/dist/tests/loopSdkContract.test.d.ts +1 -0
- package/dist/tests/useArcApproval.test.d.ts +1 -0
- package/dist/tests/useArcSendEstimate.test.d.ts +1 -0
- package/dist/tests/useAutoConnectCanton.test.d.ts +1 -0
- package/dist/tests/useCantonRestoration.test.d.ts +1 -0
- package/dist/tests/useNativeTokenForChain.test.d.ts +1 -0
- package/dist/tests/useSwapArcSelection.test.d.ts +1 -0
- package/package.json +3 -2
- package/dist/core/types/danal.d.ts +0 -1
- package/dist/index-ByakOYXP.js.map +0 -1
- package/dist/index-nTqoaD0B.js.map +0 -1
- package/dist/services/internal/danalService.d.ts +0 -8
- /package/dist/tests/{danalService.test.d.ts → arcAssetsService.test.d.ts} +0 -0
|
@@ -80,7 +80,6 @@ const suiZeroAddress = "0x0000000000000000000000000000000000000000";
|
|
|
80
80
|
const xrplZeroAddress = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
|
|
81
81
|
const stellarZeroAddress = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
|
|
82
82
|
const cantonZeroAddress = "none::12200000000000000000000000000000000000000000000000000000000000000000";
|
|
83
|
-
const danalZeroAddress = "PCI00000000000000000000000000000000000000000000000000";
|
|
84
83
|
const chainTypeToZeroAddressMap = {
|
|
85
84
|
[squidTypes.ChainType.EVM]: viem.zeroAddress,
|
|
86
85
|
[squidTypes.ChainType.COSMOS]: cosmosZeroAddress,
|
|
@@ -90,7 +89,6 @@ const chainTypeToZeroAddressMap = {
|
|
|
90
89
|
[squidTypes.ChainType.XRPL]: xrplZeroAddress,
|
|
91
90
|
[squidTypes.ChainType.STELLAR]: stellarZeroAddress,
|
|
92
91
|
[squidTypes.ChainType.CANTON]: cantonZeroAddress,
|
|
93
|
-
[squidTypes.ChainType.DANAL]: danalZeroAddress,
|
|
94
92
|
};
|
|
95
93
|
const nativeEvmTokenAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
96
94
|
const nativeCosmosTokenAddress = "uosmo";
|
|
@@ -100,7 +98,6 @@ const nativeSuiTokenAddress = "0x00000000000000000000000000000000000000000000000
|
|
|
100
98
|
const nativeXrplTokenAddress = "xrp";
|
|
101
99
|
const nativeStellarTokenAddress = "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA";
|
|
102
100
|
const nativeCantonTokenAddress = "DSO::1220b1431ef217342db44d516bb9befde802be7d8899637d290895fa58880f19accc";
|
|
103
|
-
const nativeDanalTokenAddress = "pci";
|
|
104
101
|
// by setting slippage to undefined, it's set to "auto"
|
|
105
102
|
const defaultSlippage = undefined;
|
|
106
103
|
const destinationAddressResetValue = "null";
|
|
@@ -156,6 +153,7 @@ const CHAIN_IDS = {
|
|
|
156
153
|
MANTRA: "5888",
|
|
157
154
|
CITREA: "4114",
|
|
158
155
|
TEMPO: "4217",
|
|
156
|
+
ARC: "5042",
|
|
159
157
|
// others
|
|
160
158
|
BITCOIN: "bitcoin",
|
|
161
159
|
SOLANA: "solana-mainnet-beta",
|
|
@@ -166,7 +164,6 @@ const CHAIN_IDS = {
|
|
|
166
164
|
STELLAR: "stellar-mainnet",
|
|
167
165
|
STELLAR_TESTNET: "stellar-testnet",
|
|
168
166
|
CANTON: "canton",
|
|
169
|
-
DANAL_TESTNET: "danal-testnet",
|
|
170
167
|
};
|
|
171
168
|
const chainTypeToDefaultChainIdMap = {
|
|
172
169
|
[squidTypes.ChainType.EVM]: CHAIN_IDS.ETHEREUM,
|
|
@@ -177,7 +174,6 @@ const chainTypeToDefaultChainIdMap = {
|
|
|
177
174
|
[squidTypes.ChainType.XRPL]: CHAIN_IDS.XRPL,
|
|
178
175
|
[squidTypes.ChainType.STELLAR]: CHAIN_IDS.STELLAR,
|
|
179
176
|
[squidTypes.ChainType.CANTON]: CHAIN_IDS.CANTON,
|
|
180
|
-
[squidTypes.ChainType.DANAL]: CHAIN_IDS.DANAL_TESTNET,
|
|
181
177
|
};
|
|
182
178
|
const chainTypeToNativeTokenAddressMap = {
|
|
183
179
|
[squidTypes.ChainType.EVM]: nativeEvmTokenAddress,
|
|
@@ -188,7 +184,6 @@ const chainTypeToNativeTokenAddressMap = {
|
|
|
188
184
|
[squidTypes.ChainType.XRPL]: nativeXrplTokenAddress,
|
|
189
185
|
[squidTypes.ChainType.STELLAR]: nativeStellarTokenAddress,
|
|
190
186
|
[squidTypes.ChainType.CANTON]: nativeCantonTokenAddress,
|
|
191
|
-
[squidTypes.ChainType.DANAL]: nativeDanalTokenAddress,
|
|
192
187
|
};
|
|
193
188
|
const defaultConfigValues = {
|
|
194
189
|
integratorId: INTEGRATOR_ID,
|
|
@@ -20970,6 +20965,27 @@ const isBitcoinAddressValid = (address) => {
|
|
|
20970
20965
|
}
|
|
20971
20966
|
};
|
|
20972
20967
|
|
|
20968
|
+
const arcUsdcTokenAddress = "0x3600000000000000000000000000000000000000";
|
|
20969
|
+
const isArcChain = (chainId) => chainId === CHAIN_IDS.ARC;
|
|
20970
|
+
const isArcUsdcToken = (token) => isArcChain(token?.chainId) &&
|
|
20971
|
+
token?.address.toLowerCase() === arcUsdcTokenAddress;
|
|
20972
|
+
const isArcNativeToken = (token) => isArcChain(token?.chainId) &&
|
|
20973
|
+
token?.address.toLowerCase() === nativeEvmTokenAddress;
|
|
20974
|
+
const normalizeArcTokenAddress = (chainId, address) => isArcChain(chainId) && address?.toLowerCase() === nativeEvmTokenAddress
|
|
20975
|
+
? arcUsdcTokenAddress
|
|
20976
|
+
: address;
|
|
20977
|
+
const deduplicateArcBalances = (tokens) => {
|
|
20978
|
+
const canonicalChains = new Set(tokens
|
|
20979
|
+
.filter((token) => isArcChain(token.chainId) &&
|
|
20980
|
+
token.address?.toLowerCase() === arcUsdcTokenAddress)
|
|
20981
|
+
.map((token) => token.chainId));
|
|
20982
|
+
return tokens.filter((token) => !(canonicalChains.has(token.chainId) &&
|
|
20983
|
+
token.address?.toLowerCase() === nativeEvmTokenAddress));
|
|
20984
|
+
};
|
|
20985
|
+
|
|
20986
|
+
function getCantonCommandId(memo) {
|
|
20987
|
+
return `canton-${memo}`;
|
|
20988
|
+
}
|
|
20973
20989
|
/**
|
|
20974
20990
|
* Format a Canton connector into a Squid {@link CantonWallet}. Shared by the
|
|
20975
20991
|
* announce-discovery and injected-provider paths.
|
|
@@ -21120,51 +21136,16 @@ function cantonInstrumentKey(instrument) {
|
|
|
21120
21136
|
* is already a Decimal, not base units).
|
|
21121
21137
|
*/
|
|
21122
21138
|
function groupCantonHoldingBalances(acsResponse) {
|
|
21139
|
+
return sumCantonHoldingBalances(parseCantonHoldings(acsResponse).filter((holding) => !holding.locked));
|
|
21140
|
+
}
|
|
21141
|
+
function sumCantonHoldingBalances(holdings) {
|
|
21123
21142
|
const totals = {};
|
|
21124
|
-
for (const holding of
|
|
21125
|
-
// Locked holdings are not spendable, skip them
|
|
21126
|
-
if (holding.locked)
|
|
21127
|
-
continue;
|
|
21143
|
+
for (const holding of holdings) {
|
|
21128
21144
|
const key = cantonInstrumentKey(holding.instrument);
|
|
21129
21145
|
const total = totals[key] ?? new BigNumber(0);
|
|
21130
21146
|
totals[key] = total.plus(new BigNumber(holding.amount));
|
|
21131
21147
|
}
|
|
21132
|
-
return Object.fromEntries(Object.entries(totals).map(([key, total]) => [key, total.
|
|
21133
|
-
}
|
|
21134
|
-
|
|
21135
|
-
// Matches the backend validation: "PCI" + 50 uppercase-hex chars, case-sensitive.
|
|
21136
|
-
const DANAL_ADDRESS_REGEX = /^PCI[0-9A-F]{50}$/;
|
|
21137
|
-
function isDanalAddressValid(address) {
|
|
21138
|
-
if (!address)
|
|
21139
|
-
return false;
|
|
21140
|
-
return DANAL_ADDRESS_REGEX.test(address);
|
|
21141
|
-
}
|
|
21142
|
-
// Fabric transaction id: 64 hex chars, no 0x prefix
|
|
21143
|
-
const DANAL_TX_HASH_REGEX = /^[0-9a-fA-F]{64}$/;
|
|
21144
|
-
function isDanalTxHashValid(hash) {
|
|
21145
|
-
if (!hash)
|
|
21146
|
-
return false;
|
|
21147
|
-
return DANAL_TX_HASH_REGEX.test(hash);
|
|
21148
|
-
}
|
|
21149
|
-
function isDanalTransferRequestBody(value) {
|
|
21150
|
-
if (typeof value !== "object" || value === null)
|
|
21151
|
-
return false;
|
|
21152
|
-
const { receiver, amount, memo } = value;
|
|
21153
|
-
return (typeof receiver === "string" &&
|
|
21154
|
-
receiver.length > 0 &&
|
|
21155
|
-
typeof amount === "string" &&
|
|
21156
|
-
amount.length > 0 &&
|
|
21157
|
-
typeof memo === "string" &&
|
|
21158
|
-
memo.length > 0);
|
|
21159
|
-
}
|
|
21160
|
-
function parseDanalTransferBody(data) {
|
|
21161
|
-
try {
|
|
21162
|
-
const parsed = JSON.parse(data);
|
|
21163
|
-
return isDanalTransferRequestBody(parsed) ? parsed : null;
|
|
21164
|
-
}
|
|
21165
|
-
catch {
|
|
21166
|
-
return null;
|
|
21167
|
-
}
|
|
21148
|
+
return Object.fromEntries(Object.entries(totals).map(([key, total]) => [key, total.toFixed()]));
|
|
21168
21149
|
}
|
|
21169
21150
|
|
|
21170
21151
|
const solanaTokenProgramIdByAddress = {
|
|
@@ -21503,15 +21484,6 @@ function isOnChainTxData(squidData) {
|
|
|
21503
21484
|
function isDepositAddressDirectTransferRoute(squidData) {
|
|
21504
21485
|
return squidData.type === squidTypes.SquidDataType.DepositAddressDirectTransfer;
|
|
21505
21486
|
}
|
|
21506
|
-
/**
|
|
21507
|
-
* Danal keeps SquidDataType.DepositAddressWithMemo, which XRPL/Stellar use for
|
|
21508
|
-
* wallet-signed transactions — so the deposit treatment is gated on the chain,
|
|
21509
|
-
* not just the type
|
|
21510
|
-
*/
|
|
21511
|
-
function isDanalDepositRoute(route) {
|
|
21512
|
-
return (route.params.fromChain === CHAIN_IDS.DANAL_TESTNET &&
|
|
21513
|
-
route.transactionRequest?.type === squidTypes.SquidDataType.DepositAddressWithMemo);
|
|
21514
|
-
}
|
|
21515
21487
|
function getHistoryTransactionId(tx) {
|
|
21516
21488
|
switch (tx.txType) {
|
|
21517
21489
|
case exports.HistoryTxType.SWAP:
|
|
@@ -22036,11 +22008,6 @@ const chainTypeToTrimLength = {
|
|
|
22036
22008
|
start: 3,
|
|
22037
22009
|
end: 3,
|
|
22038
22010
|
},
|
|
22039
|
-
// PCI0180...E77E
|
|
22040
|
-
[squidTypes.ChainType.DANAL]: {
|
|
22041
|
-
start: 7,
|
|
22042
|
-
end: 4,
|
|
22043
|
-
},
|
|
22044
22011
|
};
|
|
22045
22012
|
const truncateWithEllipsis = (value, startLength, endLength) => value.length <= startLength + endLength
|
|
22046
22013
|
? value
|
|
@@ -22089,8 +22056,6 @@ const isWalletAddressValid = (chainData, address) => {
|
|
|
22089
22056
|
return isStellarAddressValid(address);
|
|
22090
22057
|
case squidTypes.ChainType.CANTON:
|
|
22091
22058
|
return isCantonAddressValid(address);
|
|
22092
|
-
case squidTypes.ChainType.DANAL:
|
|
22093
|
-
return isDanalAddressValid(address);
|
|
22094
22059
|
}
|
|
22095
22060
|
};
|
|
22096
22061
|
const redirectToExtensionsStore = (wallet) => {
|
|
@@ -22254,7 +22219,7 @@ function sortAddressBook(a, b, selectedAddress) {
|
|
|
22254
22219
|
return 0;
|
|
22255
22220
|
}
|
|
22256
22221
|
const calculateTotalUsdBalanceUSD = (tokens) => {
|
|
22257
|
-
const totalUsd = tokens.reduce((total, token) => {
|
|
22222
|
+
const totalUsd = deduplicateArcBalances(tokens).reduce((total, token) => {
|
|
22258
22223
|
const balance = parseFloat(token.balance);
|
|
22259
22224
|
const usdPrice = token.usdPrice ?? 0;
|
|
22260
22225
|
const balanceUsd = balance * usdPrice;
|
|
@@ -22375,8 +22340,6 @@ const connectByChainType = async (chainType, wallet, defaultChain, params) => {
|
|
|
22375
22340
|
connector,
|
|
22376
22341
|
},
|
|
22377
22342
|
});
|
|
22378
|
-
case squidTypes.ChainType.DANAL:
|
|
22379
|
-
return null;
|
|
22380
22343
|
}
|
|
22381
22344
|
};
|
|
22382
22345
|
const getChainTypesToConnect = (selectedChainTypes, defaultChain, supportedChains) => {
|
|
@@ -22501,6 +22464,8 @@ const cancelConnectWallet = (wallet) => {
|
|
|
22501
22464
|
return;
|
|
22502
22465
|
}
|
|
22503
22466
|
switch (wallet.type) {
|
|
22467
|
+
case squidTypes.ChainType.CANTON:
|
|
22468
|
+
return wallet.connector.cancelConnect?.();
|
|
22504
22469
|
case squidTypes.ChainType.XRPL:
|
|
22505
22470
|
return wallet.connector.cancelConnect?.();
|
|
22506
22471
|
default:
|
|
@@ -23066,22 +23031,36 @@ const getFirstAvailableChainId = (config, direction, chains = []) => {
|
|
|
23066
23031
|
// if no initial chain ID is found in config, return the first available chain ID
|
|
23067
23032
|
return availableChains.length > 0 ? availableChains[0].chainId : undefined;
|
|
23068
23033
|
};
|
|
23069
|
-
const fetchHighestBalanceToken = (fetchCachedBalance, chainId) => {
|
|
23034
|
+
const fetchHighestBalanceToken = (fetchCachedBalance, chainId, selectableTokens, direction) => {
|
|
23070
23035
|
let address = undefined;
|
|
23071
23036
|
if (fetchCachedBalance) {
|
|
23072
23037
|
const queryCache = fetchCachedBalance?.queryClient?.getQueryCache();
|
|
23038
|
+
const directionCache = isArcChain(chainId) && direction
|
|
23039
|
+
? queryCache?.find(keys().allTokensBalance(fetchCachedBalance.address, squidTypes.ChainType.EVM, direction))
|
|
23040
|
+
: undefined;
|
|
23073
23041
|
// get cached balance from key
|
|
23074
|
-
const cache =
|
|
23075
|
-
|
|
23076
|
-
|
|
23042
|
+
const cache = directionCache?.state.data !== undefined
|
|
23043
|
+
? directionCache
|
|
23044
|
+
: queryCache?.find(keys().allTokensBalance(fetchCachedBalance?.address, !isNaN(Number(chainId)) ? squidTypes.ChainType.EVM : squidTypes.ChainType.COSMOS));
|
|
23045
|
+
const cacheData = cache?.state.data;
|
|
23046
|
+
const cacheResult = Array.isArray(cacheData)
|
|
23047
|
+
? cacheData
|
|
23048
|
+
: isArcChain(chainId)
|
|
23049
|
+
? (cacheData?.tokens ?? [])
|
|
23050
|
+
: [];
|
|
23077
23051
|
if (cacheResult.length > 0) {
|
|
23078
|
-
const tokensWithBalance = cacheResult.filter((t) => t.chainId === chainId &&
|
|
23052
|
+
const tokensWithBalance = deduplicateArcBalances(cacheResult).filter((t) => t.chainId === chainId &&
|
|
23053
|
+
Number(t.balance) > 0 &&
|
|
23054
|
+
(!selectableTokens ||
|
|
23055
|
+
selectableTokens.some((token) => token.chainId === t.chainId &&
|
|
23056
|
+
token.address.toLowerCase() ===
|
|
23057
|
+
normalizeArcTokenAddress(t.chainId, t.address)?.toLowerCase())));
|
|
23079
23058
|
if (tokensWithBalance.length > 0) {
|
|
23080
23059
|
// Return the token with the highest balance
|
|
23081
23060
|
// TODO: At the moment we're not taking into account usd price
|
|
23082
23061
|
const tokenWithHighestBalance = tokensWithBalance.reduce((prev, current) => Number(prev.balance) > Number(current.balance) ? prev : current);
|
|
23083
23062
|
if (tokenWithHighestBalance) {
|
|
23084
|
-
address = tokenWithHighestBalance.address;
|
|
23063
|
+
address = normalizeArcTokenAddress(tokenWithHighestBalance.chainId, tokenWithHighestBalance.address);
|
|
23085
23064
|
}
|
|
23086
23065
|
}
|
|
23087
23066
|
}
|
|
@@ -23092,6 +23071,21 @@ const findNativeTokenForChainType = (tokens) => {
|
|
|
23092
23071
|
return tokens.find((t) => t.address.toLowerCase() ===
|
|
23093
23072
|
chainTypeToNativeTokenAddressMap[t.type].toLowerCase());
|
|
23094
23073
|
};
|
|
23074
|
+
const getTokenAddressForSelection = ({ tokens, config, chainId, address, direction, excludeToken, }) => {
|
|
23075
|
+
if (!isArcChain(chainId))
|
|
23076
|
+
return address;
|
|
23077
|
+
const normalizedAddress = normalizeArcTokenAddress(chainId, address);
|
|
23078
|
+
const excludedAddress = normalizeArcTokenAddress(excludeToken?.chainId, excludeToken?.address);
|
|
23079
|
+
if (!chainId ||
|
|
23080
|
+
!normalizedAddress ||
|
|
23081
|
+
(excludeToken?.chainId === chainId &&
|
|
23082
|
+
normalizedAddress.toLowerCase() === excludedAddress?.toLowerCase()) ||
|
|
23083
|
+
!getSupportedChainIdsForDirection([chainId], config, direction).includes(chainId)) {
|
|
23084
|
+
return undefined;
|
|
23085
|
+
}
|
|
23086
|
+
return filterViewableTokens(tokens, config, direction).find((token) => token.chainId === chainId &&
|
|
23087
|
+
token.address.toLowerCase() === normalizedAddress.toLowerCase())?.address;
|
|
23088
|
+
};
|
|
23095
23089
|
/**
|
|
23096
23090
|
* Returns the default token address for a specific chain and direction based on config.
|
|
23097
23091
|
* If no default tokens are set in config, returns the native token for the specified chain
|
|
@@ -23109,6 +23103,25 @@ const getInitialOrDefaultTokenAddressForChain = ({ tokens, config, chainId, dire
|
|
|
23109
23103
|
const supportedChainIds = getSupportedChainIdsForDirection([chainId], config, direction);
|
|
23110
23104
|
if (!supportedChainIds.includes(chainId))
|
|
23111
23105
|
return;
|
|
23106
|
+
if (isArcChain(chainId)) {
|
|
23107
|
+
const selectAddress = (address) => getTokenAddressForSelection({
|
|
23108
|
+
tokens,
|
|
23109
|
+
config,
|
|
23110
|
+
chainId,
|
|
23111
|
+
address,
|
|
23112
|
+
direction,
|
|
23113
|
+
excludeToken,
|
|
23114
|
+
});
|
|
23115
|
+
const selectableTokens = tokens.filter((token) => token.chainId === chainId &&
|
|
23116
|
+
!isArcNativeToken(token) &&
|
|
23117
|
+
selectAddress(token.address) !== undefined);
|
|
23118
|
+
return (selectAddress(config.defaultTokensPerChain?.find((token) => token.chainId === chainId)
|
|
23119
|
+
?.address) ??
|
|
23120
|
+
selectAddress(getInitialTokenAddressForChain({ tokens, config, chainId, direction })) ??
|
|
23121
|
+
selectAddress(fetchHighestBalanceToken(fetchCachedBalance, chainId, selectableTokens, direction)) ??
|
|
23122
|
+
selectAddress(arcUsdcTokenAddress) ??
|
|
23123
|
+
selectableTokens[0]?.address);
|
|
23124
|
+
}
|
|
23112
23125
|
const configDefaultTokenForChain = config.defaultTokensPerChain?.find((token) => token.chainId == chainId);
|
|
23113
23126
|
// get default token from config
|
|
23114
23127
|
if (configDefaultTokenForChain &&
|
|
@@ -23152,6 +23165,23 @@ const getInitialTokenAddressForChain = ({ chainId, config, direction, tokens, un
|
|
|
23152
23165
|
const initialAsset = config.initialAssets?.[direction];
|
|
23153
23166
|
if (!initialAsset)
|
|
23154
23167
|
return undefined;
|
|
23168
|
+
if (isArcChain(chainId)) {
|
|
23169
|
+
const address = isNamedTokenConfig(initialAsset)
|
|
23170
|
+
? tokens.find((token) => token.chainId === chainId &&
|
|
23171
|
+
!isArcNativeToken(token) &&
|
|
23172
|
+
token.symbol.toLowerCase() === initialAsset.symbol?.toLowerCase())?.address
|
|
23173
|
+
: initialAsset.chainId === chainId
|
|
23174
|
+
? initialAsset.address
|
|
23175
|
+
: undefined;
|
|
23176
|
+
return getTokenAddressForSelection({
|
|
23177
|
+
tokens,
|
|
23178
|
+
config,
|
|
23179
|
+
chainId,
|
|
23180
|
+
address,
|
|
23181
|
+
direction,
|
|
23182
|
+
excludeToken: { chainId, address: unavailableTokenAddress },
|
|
23183
|
+
});
|
|
23184
|
+
}
|
|
23155
23185
|
if (isNamedTokenConfig(initialAsset)) {
|
|
23156
23186
|
if (!initialAsset.symbol)
|
|
23157
23187
|
return undefined;
|
|
@@ -23361,12 +23391,44 @@ const handleDestinationAddressOnSwapChange = ({ fromChainId, fromTokenAddress, t
|
|
|
23361
23391
|
});
|
|
23362
23392
|
let destinationTokenAddress = toChainId && !toTokenAddress
|
|
23363
23393
|
? defaultToAddress
|
|
23364
|
-
: toTokenAddress ?? swapRoute?.toTokenAddress;
|
|
23394
|
+
: (toTokenAddress ?? swapRoute?.toTokenAddress);
|
|
23365
23395
|
const lowerCaseDestinationTokenAddress = destinationTokenAddress?.toLowerCase();
|
|
23366
23396
|
if (destinationTokensFiltered.find((t) => t.address === lowerCaseDestinationTokenAddress) === undefined) {
|
|
23367
23397
|
destinationTokenAddress = defaultToAddress;
|
|
23368
23398
|
}
|
|
23369
|
-
return
|
|
23399
|
+
return isArcChain(toChainId ?? swapRoute?.toChainId)
|
|
23400
|
+
? destinationTokenAddress?.toLowerCase()
|
|
23401
|
+
: lowerCaseDestinationTokenAddress;
|
|
23402
|
+
};
|
|
23403
|
+
const normalizeSwapParamsForSelection = ({ swapParams, tokens, chains, config, }) => {
|
|
23404
|
+
const fromTokenAddress = getTokenAddressForSelection({
|
|
23405
|
+
tokens,
|
|
23406
|
+
config,
|
|
23407
|
+
chainId: swapParams.fromChainId,
|
|
23408
|
+
address: swapParams.fromTokenAddress,
|
|
23409
|
+
direction: "from",
|
|
23410
|
+
});
|
|
23411
|
+
const fromToken = tokens.find((token) => token.chainId === swapParams.fromChainId &&
|
|
23412
|
+
token.address.toLowerCase() === fromTokenAddress?.toLowerCase());
|
|
23413
|
+
const toTokenAddress = getTokenAddressForSelection({
|
|
23414
|
+
tokens: isArcChain(swapParams.toChainId)
|
|
23415
|
+
? filterTokensForDestination({
|
|
23416
|
+
tokens,
|
|
23417
|
+
selectedDestinationChain: chains.find((chain) => chain.chainId === swapParams.toChainId),
|
|
23418
|
+
selectedSourceToken: fromToken,
|
|
23419
|
+
removeSourceToken: true,
|
|
23420
|
+
})
|
|
23421
|
+
: tokens,
|
|
23422
|
+
config,
|
|
23423
|
+
chainId: swapParams.toChainId,
|
|
23424
|
+
address: swapParams.toTokenAddress,
|
|
23425
|
+
direction: "to",
|
|
23426
|
+
excludeToken: {
|
|
23427
|
+
chainId: swapParams.fromChainId,
|
|
23428
|
+
address: fromTokenAddress,
|
|
23429
|
+
},
|
|
23430
|
+
});
|
|
23431
|
+
return { ...swapParams, fromTokenAddress, toTokenAddress };
|
|
23370
23432
|
};
|
|
23371
23433
|
/**
|
|
23372
23434
|
* When user changes something from the SwapView
|
|
@@ -23378,10 +23440,19 @@ const handleDestinationAddressOnSwapChange = ({ fromChainId, fromTokenAddress, t
|
|
|
23378
23440
|
* If only destination token is changed but source is not, and they were the same, then source token updates to something else
|
|
23379
23441
|
*
|
|
23380
23442
|
*/
|
|
23381
|
-
const getNewSwapParamsFromInput = ({ inputParams, initialSwapRoute, tokens, chains, config, queryClient, connectedSourceAddress, }) => {
|
|
23382
|
-
const
|
|
23443
|
+
const getNewSwapParamsFromInput = ({ inputParams, initialSwapRoute: storedSwapRoute, tokens, chains, config, queryClient, connectedSourceAddress, }) => {
|
|
23444
|
+
const initialSwapRoute = storedSwapRoute
|
|
23445
|
+
? {
|
|
23446
|
+
...storedSwapRoute,
|
|
23447
|
+
fromTokenAddress: normalizeArcTokenAddress(storedSwapRoute.fromChainId, storedSwapRoute.fromTokenAddress),
|
|
23448
|
+
toTokenAddress: normalizeArcTokenAddress(storedSwapRoute.toChainId, storedSwapRoute.toTokenAddress),
|
|
23449
|
+
}
|
|
23450
|
+
: undefined;
|
|
23451
|
+
const { fromChainId, toChainId, destinationAddress, fallbackAddress, depositRefundAddress, } = inputParams;
|
|
23383
23452
|
const srcChainId = fromChainId ?? initialSwapRoute?.fromChainId;
|
|
23384
23453
|
const destinationChainId = toChainId ?? initialSwapRoute?.toChainId;
|
|
23454
|
+
const fromTokenAddress = normalizeArcTokenAddress(srcChainId, inputParams.fromTokenAddress);
|
|
23455
|
+
const toTokenAddress = normalizeArcTokenAddress(destinationChainId, inputParams.toTokenAddress);
|
|
23385
23456
|
const defaultFromAddress = getInitialOrDefaultTokenAddressForChain({
|
|
23386
23457
|
tokens,
|
|
23387
23458
|
config,
|
|
@@ -23413,7 +23484,7 @@ const getNewSwapParamsFromInput = ({ inputParams, initialSwapRoute, tokens, chai
|
|
|
23413
23484
|
toTokenAddress === initialSwapRoute?.fromTokenAddress;
|
|
23414
23485
|
const sourceTokenAddress = sameNewDestAndInitialSource || onlySourceChainIdChanged
|
|
23415
23486
|
? defaultFromAddress
|
|
23416
|
-
: fromTokenAddress ?? initialSwapRoute?.fromTokenAddress;
|
|
23487
|
+
: (fromTokenAddress ?? initialSwapRoute?.fromTokenAddress);
|
|
23417
23488
|
let destinationTokenAddress = undefined;
|
|
23418
23489
|
if (!sameNewSourceAndInitialDest) {
|
|
23419
23490
|
if (onlySourceChainIdChanged) {
|
|
@@ -23457,15 +23528,20 @@ const getNewSwapParamsFromInput = ({ inputParams, initialSwapRoute, tokens, chai
|
|
|
23457
23528
|
const newDepositRefundAddress = sourceChainChanged
|
|
23458
23529
|
? undefined
|
|
23459
23530
|
: depositRefundAddress || initialSwapRoute?.depositRefundAddress;
|
|
23460
|
-
return {
|
|
23461
|
-
|
|
23462
|
-
|
|
23463
|
-
|
|
23464
|
-
|
|
23465
|
-
|
|
23466
|
-
|
|
23467
|
-
|
|
23468
|
-
|
|
23531
|
+
return normalizeSwapParamsForSelection({
|
|
23532
|
+
swapParams: {
|
|
23533
|
+
fromChainId: srcChainId,
|
|
23534
|
+
fromTokenAddress: sourceTokenAddress,
|
|
23535
|
+
toChainId: destinationChainId,
|
|
23536
|
+
toTokenAddress: destinationTokenAddress,
|
|
23537
|
+
destinationAddress: newDestinationAddress,
|
|
23538
|
+
fallbackAddress: newFallbackAddress,
|
|
23539
|
+
depositRefundAddress: newDepositRefundAddress,
|
|
23540
|
+
},
|
|
23541
|
+
tokens,
|
|
23542
|
+
chains,
|
|
23543
|
+
config,
|
|
23544
|
+
});
|
|
23469
23545
|
};
|
|
23470
23546
|
/* Sorts tokens based on the following order:
|
|
23471
23547
|
* 1. Native token on chain ID "1" (Ethereum) comes first.
|
|
@@ -23523,8 +23599,10 @@ const sortAllTokens = (tokenA, tokenB) => {
|
|
|
23523
23599
|
return 0;
|
|
23524
23600
|
};
|
|
23525
23601
|
const findToken = (tokens, chainId, address) => tokens.find((t) => t.chainId === chainId && t.address === address);
|
|
23526
|
-
const findNativeToken = (tokens, chain) => tokens.find((t) => t.
|
|
23527
|
-
|
|
23602
|
+
const findNativeToken = (tokens, chain) => tokens.find((t) => t.chainId === chain.chainId &&
|
|
23603
|
+
(isArcChain(chain.chainId)
|
|
23604
|
+
? isArcNativeToken(t)
|
|
23605
|
+
: t.symbol.toUpperCase() === chain.nativeCurrency.symbol.toUpperCase()));
|
|
23528
23606
|
const normalizeIbcAddress = (address) => {
|
|
23529
23607
|
if (!address.toLowerCase().startsWith("ibc/")) {
|
|
23530
23608
|
return address;
|
|
@@ -23594,9 +23672,7 @@ function selectGroupDefaultInfo(tokens) {
|
|
|
23594
23672
|
const groupTokensBySymbol = (tokens, chainType) => {
|
|
23595
23673
|
const groups = {};
|
|
23596
23674
|
// Filter tokens by chain type if specified
|
|
23597
|
-
const filteredTokens = chainType
|
|
23598
|
-
? tokens.filter((token) => token.type === chainType)
|
|
23599
|
-
: tokens;
|
|
23675
|
+
const filteredTokens = deduplicateArcBalances(chainType ? tokens.filter((token) => token.type === chainType) : tokens);
|
|
23600
23676
|
// First pass: group by coingeckoId or symbol
|
|
23601
23677
|
filteredTokens.forEach((token) => {
|
|
23602
23678
|
const groupKey = token.coingeckoId || token.symbol.split(".")[0];
|
|
@@ -23672,7 +23748,7 @@ const groupTokensBySymbol = (tokens, chainType) => {
|
|
|
23672
23748
|
*/
|
|
23673
23749
|
const groupTokensByChainId = (tokens, chains) => {
|
|
23674
23750
|
const groups = {};
|
|
23675
|
-
tokens.forEach((token) => {
|
|
23751
|
+
deduplicateArcBalances(tokens).forEach((token) => {
|
|
23676
23752
|
const chainData = chains.find((chain) => chain.chainId === token.chainId);
|
|
23677
23753
|
const groupKey = token.chainId;
|
|
23678
23754
|
if (!groups[groupKey]) {
|
|
@@ -23720,6 +23796,8 @@ const filterViewableTokens = (tokens, config, direction) => {
|
|
|
23720
23796
|
{ chainId: CHAIN_IDS.DCHAIN, address: nativeEvmTokenAddress },
|
|
23721
23797
|
];
|
|
23722
23798
|
return (tokens.filter((token) => {
|
|
23799
|
+
if (isArcNativeToken(token))
|
|
23800
|
+
return false;
|
|
23723
23801
|
if (direction) {
|
|
23724
23802
|
const parsedDirection = getParsedDirection(direction);
|
|
23725
23803
|
const availableTokensForDirection = config.availableTokens?.[parsedDirection];
|
|
@@ -23735,7 +23813,7 @@ const filterViewableTokens = (tokens, config, direction) => {
|
|
|
23735
23813
|
};
|
|
23736
23814
|
const getSecretNetworkBalances = async (chainData, cosmosAddress, squidTokens, keplrTypeWallet) => {
|
|
23737
23815
|
const squidSecretTokens = squidTokens.filter((t) => t.chainId === CHAIN_IDS.SECRET);
|
|
23738
|
-
const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-
|
|
23816
|
+
const { fetchAllSecretBalances } = await Promise.resolve().then(function () { return require('./secretService-BZ06HTOJ.js'); });
|
|
23739
23817
|
return fetchAllSecretBalances(chainData, cosmosAddress, squidSecretTokens, keplrTypeWallet);
|
|
23740
23818
|
};
|
|
23741
23819
|
function getTokenAssetsKey(token) {
|
|
@@ -25316,8 +25394,7 @@ const searchTokens = (query, tokens, chains) => {
|
|
|
25316
25394
|
.filter((chain) => chain.nativeCurrency.symbol.toLowerCase() === query.toLowerCase() ||
|
|
25317
25395
|
chain.nativeCurrency.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
25318
25396
|
chain.networkName.toLowerCase().includes(query.toLowerCase()) ||
|
|
25319
|
-
|
|
25320
|
-
false))
|
|
25397
|
+
chain.axelarChainName.toLowerCase().includes(query.toLowerCase()))
|
|
25321
25398
|
.map((chain) => tokens.find((token) => token.chainId === chain.chainId &&
|
|
25322
25399
|
token.address.toLowerCase() === nativeEvmTokenAddress))
|
|
25323
25400
|
.filter((token) => token !== undefined);
|
|
@@ -25730,7 +25807,7 @@ const useSquidTokens = (direction) => {
|
|
|
25730
25807
|
config.availableChains,
|
|
25731
25808
|
direction,
|
|
25732
25809
|
]);
|
|
25733
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
25810
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = React.useMemo(() => {
|
|
25734
25811
|
return tokens?.reduce((acc, token) => {
|
|
25735
25812
|
switch (token.type) {
|
|
25736
25813
|
case squidTypes.ChainType.EVM:
|
|
@@ -25757,9 +25834,6 @@ const useSquidTokens = (direction) => {
|
|
|
25757
25834
|
case squidTypes.ChainType.CANTON:
|
|
25758
25835
|
acc.cantonTokens.push(token);
|
|
25759
25836
|
break;
|
|
25760
|
-
case squidTypes.ChainType.DANAL:
|
|
25761
|
-
acc.danalTokens.push(token);
|
|
25762
|
-
break;
|
|
25763
25837
|
}
|
|
25764
25838
|
return acc;
|
|
25765
25839
|
}, {
|
|
@@ -25771,7 +25845,6 @@ const useSquidTokens = (direction) => {
|
|
|
25771
25845
|
xrplTokens: [],
|
|
25772
25846
|
stellarTokens: [],
|
|
25773
25847
|
cantonTokens: [],
|
|
25774
|
-
danalTokens: [],
|
|
25775
25848
|
});
|
|
25776
25849
|
}, [tokens]);
|
|
25777
25850
|
const findToken = React.useCallback((address, chainId) => {
|
|
@@ -25790,7 +25863,6 @@ const useSquidTokens = (direction) => {
|
|
|
25790
25863
|
xrplTokens,
|
|
25791
25864
|
stellarTokens,
|
|
25792
25865
|
cantonTokens,
|
|
25793
|
-
danalTokens,
|
|
25794
25866
|
};
|
|
25795
25867
|
};
|
|
25796
25868
|
|
|
@@ -26327,7 +26399,6 @@ const chainTypeToRefetchInterval = {
|
|
|
26327
26399
|
[squidTypes.ChainType.XRPL]: 1_000,
|
|
26328
26400
|
[squidTypes.ChainType.STELLAR]: 1_000,
|
|
26329
26401
|
[squidTypes.ChainType.CANTON]: 1_000,
|
|
26330
|
-
[squidTypes.ChainType.DANAL]: 1_000,
|
|
26331
26402
|
};
|
|
26332
26403
|
/**
|
|
26333
26404
|
* Returns the status refetch interval of a Send transaction
|
|
@@ -26594,7 +26665,7 @@ const useSquidChains = (direction) => {
|
|
|
26594
26665
|
const chain = findChain(chainId);
|
|
26595
26666
|
return chain?.chainType;
|
|
26596
26667
|
}, [findChain]);
|
|
26597
|
-
const { evmChains, cosmosChains, suiChains, stellarChains, xrplChains, cantonChains,
|
|
26668
|
+
const { evmChains, cosmosChains, suiChains, stellarChains, xrplChains, cantonChains, } = React.useMemo(() => {
|
|
26598
26669
|
return chains.reduce((acc, chain) => {
|
|
26599
26670
|
switch (chain.chainType) {
|
|
26600
26671
|
case squidTypes.ChainType.EVM:
|
|
@@ -26615,9 +26686,6 @@ const useSquidChains = (direction) => {
|
|
|
26615
26686
|
case squidTypes.ChainType.CANTON:
|
|
26616
26687
|
acc.cantonChains.push(chain);
|
|
26617
26688
|
break;
|
|
26618
|
-
case squidTypes.ChainType.DANAL:
|
|
26619
|
-
acc.danalChains.push(chain);
|
|
26620
|
-
break;
|
|
26621
26689
|
}
|
|
26622
26690
|
return acc;
|
|
26623
26691
|
}, {
|
|
@@ -26627,7 +26695,6 @@ const useSquidChains = (direction) => {
|
|
|
26627
26695
|
xrplChains: [],
|
|
26628
26696
|
stellarChains: [],
|
|
26629
26697
|
cantonChains: [],
|
|
26630
|
-
danalChains: [],
|
|
26631
26698
|
});
|
|
26632
26699
|
}, [chains]);
|
|
26633
26700
|
const { supportedSourceChains, supportedDestinationChains } = React.useMemo(() => {
|
|
@@ -26649,7 +26716,6 @@ const useSquidChains = (direction) => {
|
|
|
26649
26716
|
xrplChains,
|
|
26650
26717
|
stellarChains,
|
|
26651
26718
|
cantonChains,
|
|
26652
|
-
danalChains,
|
|
26653
26719
|
getChainType,
|
|
26654
26720
|
findChain,
|
|
26655
26721
|
};
|
|
@@ -26963,7 +27029,343 @@ const useBitcoinContext = () => {
|
|
|
26963
27029
|
return context;
|
|
26964
27030
|
};
|
|
26965
27031
|
|
|
26966
|
-
const
|
|
27032
|
+
const vParty = (party) => ({ party });
|
|
27033
|
+
const vText = (text) => ({ text });
|
|
27034
|
+
const vNumeric = (numeric) => ({ numeric });
|
|
27035
|
+
const vContractId = (contractId) => ({
|
|
27036
|
+
contractId,
|
|
27037
|
+
});
|
|
27038
|
+
const vList = (elements) => ({
|
|
27039
|
+
list: { elements },
|
|
27040
|
+
});
|
|
27041
|
+
const vRecord = (fields) => ({
|
|
27042
|
+
record: { fields: fields.map(([label, value]) => ({ label, value })) },
|
|
27043
|
+
});
|
|
27044
|
+
const vTextMap = (entries) => ({ textMap: { entries } });
|
|
27045
|
+
const vTimestamp = (isoString) => ({
|
|
27046
|
+
timestamp: String(Math.floor(new Date(isoString).getTime() * 1000)),
|
|
27047
|
+
});
|
|
27048
|
+
function isRecord(value) {
|
|
27049
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27050
|
+
}
|
|
27051
|
+
function normalizeChoiceContextValue(value) {
|
|
27052
|
+
if (typeof value === "string")
|
|
27053
|
+
return value;
|
|
27054
|
+
if (!isRecord(value) || typeof value.tag !== "string")
|
|
27055
|
+
return null;
|
|
27056
|
+
switch (value.tag) {
|
|
27057
|
+
case "AV_Text":
|
|
27058
|
+
case "AV_ContractId":
|
|
27059
|
+
return typeof value.value === "string"
|
|
27060
|
+
? { tag: value.tag, value: value.value }
|
|
27061
|
+
: null;
|
|
27062
|
+
case "AV_Bool":
|
|
27063
|
+
return typeof value.value === "boolean"
|
|
27064
|
+
? { tag: value.tag, value: value.value }
|
|
27065
|
+
: null;
|
|
27066
|
+
case "AV_List":
|
|
27067
|
+
if (!Array.isArray(value.value))
|
|
27068
|
+
return null;
|
|
27069
|
+
return {
|
|
27070
|
+
tag: value.tag,
|
|
27071
|
+
value: value.value.flatMap((entry) => {
|
|
27072
|
+
const normalized = normalizeChoiceContextValue(entry);
|
|
27073
|
+
return normalized ? [normalized] : [];
|
|
27074
|
+
}),
|
|
27075
|
+
};
|
|
27076
|
+
default: {
|
|
27077
|
+
const inner = value.value;
|
|
27078
|
+
if (typeof inner === "string" ||
|
|
27079
|
+
typeof inner === "number" ||
|
|
27080
|
+
typeof inner === "boolean" ||
|
|
27081
|
+
inner === null) {
|
|
27082
|
+
return { tag: value.tag, value: inner };
|
|
27083
|
+
}
|
|
27084
|
+
if (Array.isArray(inner)) {
|
|
27085
|
+
return {
|
|
27086
|
+
tag: value.tag,
|
|
27087
|
+
value: inner.flatMap((entry) => {
|
|
27088
|
+
const normalized = normalizeChoiceContextValue(entry);
|
|
27089
|
+
return normalized ? [normalized] : [];
|
|
27090
|
+
}),
|
|
27091
|
+
};
|
|
27092
|
+
}
|
|
27093
|
+
if (!isRecord(inner))
|
|
27094
|
+
return null;
|
|
27095
|
+
const normalizedObject = {};
|
|
27096
|
+
for (const [key, entry] of Object.entries(inner)) {
|
|
27097
|
+
if (typeof entry !== "string" &&
|
|
27098
|
+
typeof entry !== "number" &&
|
|
27099
|
+
typeof entry !== "boolean" &&
|
|
27100
|
+
entry !== null) {
|
|
27101
|
+
return null;
|
|
27102
|
+
}
|
|
27103
|
+
normalizedObject[key] = entry;
|
|
27104
|
+
}
|
|
27105
|
+
return { tag: value.tag, value: normalizedObject };
|
|
27106
|
+
}
|
|
27107
|
+
}
|
|
27108
|
+
}
|
|
27109
|
+
function stringifyChoiceContextValue(value) {
|
|
27110
|
+
return typeof value === "string" ? value : String(value);
|
|
27111
|
+
}
|
|
27112
|
+
function choiceContextValueToAnyValue(value) {
|
|
27113
|
+
if (typeof value === "string") {
|
|
27114
|
+
return {
|
|
27115
|
+
variant: { constructor: "AV_Text", value: vText(value) },
|
|
27116
|
+
};
|
|
27117
|
+
}
|
|
27118
|
+
switch (value.tag) {
|
|
27119
|
+
case "AV_ContractId":
|
|
27120
|
+
if (typeof value.value !== "string") {
|
|
27121
|
+
throw new TypeError("AV_ContractId values must be strings");
|
|
27122
|
+
}
|
|
27123
|
+
return {
|
|
27124
|
+
variant: { constructor: value.tag, value: vContractId(value.value) },
|
|
27125
|
+
};
|
|
27126
|
+
case "AV_Bool":
|
|
27127
|
+
if (typeof value.value !== "boolean") {
|
|
27128
|
+
throw new TypeError("AV_Bool values must be booleans");
|
|
27129
|
+
}
|
|
27130
|
+
return {
|
|
27131
|
+
variant: { constructor: value.tag, value: { bool: value.value } },
|
|
27132
|
+
};
|
|
27133
|
+
case "AV_List":
|
|
27134
|
+
if (!Array.isArray(value.value)) {
|
|
27135
|
+
throw new TypeError("AV_List values must be arrays");
|
|
27136
|
+
}
|
|
27137
|
+
return {
|
|
27138
|
+
variant: {
|
|
27139
|
+
constructor: value.tag,
|
|
27140
|
+
value: vList(value.value.map(choiceContextValueToAnyValue)),
|
|
27141
|
+
},
|
|
27142
|
+
};
|
|
27143
|
+
default:
|
|
27144
|
+
return {
|
|
27145
|
+
variant: {
|
|
27146
|
+
constructor: value.tag,
|
|
27147
|
+
value: vText(stringifyChoiceContextValue(value.value)),
|
|
27148
|
+
},
|
|
27149
|
+
};
|
|
27150
|
+
}
|
|
27151
|
+
}
|
|
27152
|
+
|
|
27153
|
+
/** Metadata key used by Splice token-standard transfers to carry our Squid orderhash memo. */
|
|
27154
|
+
const CANTON_MEMO_KEY = "splice.lfdecentralizedtrust.org/reason";
|
|
27155
|
+
/** Interface id for the transfer factory contract returned by the token registry. */
|
|
27156
|
+
const TRANSFER_FACTORY_INTERFACE_ID = "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory";
|
|
27157
|
+
/** Choice exercised on the transfer factory to create the token-standard transfer. */
|
|
27158
|
+
const TRANSFER_FACTORY_CHOICE = "TransferFactory_Transfer";
|
|
27159
|
+
/** CIP-056 (non-Amulet) tokens expose a registrar parameterized by admin party. */
|
|
27160
|
+
const CIP056_REGISTRAR_BASE = "https://api.utilities.digitalasset.com/api/token-standard/v0/registrars";
|
|
27161
|
+
const TRANSFER_REQUESTED_AT_SKEW_MS = 60_000;
|
|
27162
|
+
const TRANSFER_EXECUTE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
27163
|
+
function resolveCantonRegistryUrl(instrument) {
|
|
27164
|
+
if (instrument.id === CANTON_AMULET_INSTRUMENT_ID) {
|
|
27165
|
+
return CANTON_AMULET_REGISTRY_URL;
|
|
27166
|
+
}
|
|
27167
|
+
return `${CIP056_REGISTRAR_BASE}/${encodeURIComponent(instrument.admin)}`;
|
|
27168
|
+
}
|
|
27169
|
+
/**
|
|
27170
|
+
* Build the plain JSON arguments expected by the registry transfer-factory
|
|
27171
|
+
* endpoint before it adds choice context and disclosed contracts.
|
|
27172
|
+
*/
|
|
27173
|
+
function buildTransferChoiceArgs({ sender, receiver, amount, instrument, inputHoldingCids, memo, nowMs, }) {
|
|
27174
|
+
return {
|
|
27175
|
+
expectedAdmin: instrument.admin,
|
|
27176
|
+
transfer: {
|
|
27177
|
+
sender,
|
|
27178
|
+
receiver,
|
|
27179
|
+
amount,
|
|
27180
|
+
instrumentId: { admin: instrument.admin, id: instrument.id },
|
|
27181
|
+
requestedAt: new Date(nowMs - TRANSFER_REQUESTED_AT_SKEW_MS).toISOString(),
|
|
27182
|
+
executeBefore: new Date(nowMs + TRANSFER_EXECUTE_WINDOW_MS).toISOString(),
|
|
27183
|
+
inputHoldingCids,
|
|
27184
|
+
meta: { values: { [CANTON_MEMO_KEY]: memo } },
|
|
27185
|
+
},
|
|
27186
|
+
extraArgs: { context: { values: {} }, meta: { values: {} } },
|
|
27187
|
+
};
|
|
27188
|
+
}
|
|
27189
|
+
/**
|
|
27190
|
+
* Canton's Holding contracts follow Bitcoin-style UTXOs model.
|
|
27191
|
+
*
|
|
27192
|
+
* Pick Holding contract ids covering `amount` for the instrument: prefer a
|
|
27193
|
+
* single exact-amount holding, else accumulate largest-first.
|
|
27194
|
+
*/
|
|
27195
|
+
function selectInputHoldingCids(holdings, instrument, amount) {
|
|
27196
|
+
const spendableHoldings = holdings.filter((h) => !h.locked &&
|
|
27197
|
+
h.instrument.admin === instrument.admin &&
|
|
27198
|
+
h.instrument.id === instrument.id);
|
|
27199
|
+
const target = new BigNumber(amount);
|
|
27200
|
+
const exact = spendableHoldings.find((h) => new BigNumber(h.amount).eq(target));
|
|
27201
|
+
if (exact)
|
|
27202
|
+
return [exact.contractId];
|
|
27203
|
+
const sorted = [...spendableHoldings].sort((a, b) => new BigNumber(b.amount).comparedTo(new BigNumber(a.amount)));
|
|
27204
|
+
const selected = [];
|
|
27205
|
+
let sum = new BigNumber(0);
|
|
27206
|
+
for (const holding of sorted) {
|
|
27207
|
+
selected.push(holding.contractId);
|
|
27208
|
+
sum = sum.plus(holding.amount);
|
|
27209
|
+
if (sum.gte(target))
|
|
27210
|
+
break;
|
|
27211
|
+
}
|
|
27212
|
+
if (sum.lt(target)) {
|
|
27213
|
+
throw new Error("Insufficient Canton holdings to cover transfer amount");
|
|
27214
|
+
}
|
|
27215
|
+
return selected;
|
|
27216
|
+
}
|
|
27217
|
+
async function fetchInputHoldingCids(connector, party, instrument, amount) {
|
|
27218
|
+
const ledgerEnd = await connector.ledgerApi({
|
|
27219
|
+
requestMethod: "get",
|
|
27220
|
+
resource: "/v2/state/ledger-end",
|
|
27221
|
+
});
|
|
27222
|
+
const activeContracts = await connector.ledgerApi({
|
|
27223
|
+
requestMethod: "post",
|
|
27224
|
+
resource: "/v2/state/active-contracts",
|
|
27225
|
+
body: buildHoldingsAcsRequestBody(party, String(ledgerEnd.offset)),
|
|
27226
|
+
});
|
|
27227
|
+
return selectInputHoldingCids(parseCantonHoldings(activeContracts), instrument, amount);
|
|
27228
|
+
}
|
|
27229
|
+
/**
|
|
27230
|
+
* Ask the token registry for the transfer factory contract and extra context
|
|
27231
|
+
* required to exercise its transfer choice.
|
|
27232
|
+
*/
|
|
27233
|
+
async function fetchTransferFactory(registryUrl, choiceArgs) {
|
|
27234
|
+
const response = await fetch(`${registryUrl}/registry/transfer-instruction/v1/transfer-factory`, {
|
|
27235
|
+
method: "POST",
|
|
27236
|
+
headers: { "Content-Type": "application/json" },
|
|
27237
|
+
body: JSON.stringify({
|
|
27238
|
+
choiceArguments: choiceArgs,
|
|
27239
|
+
excludeDebugFields: true,
|
|
27240
|
+
}),
|
|
27241
|
+
});
|
|
27242
|
+
if (!response.ok) {
|
|
27243
|
+
throw new Error(`Canton transfer-factory request failed (${response.status})`);
|
|
27244
|
+
}
|
|
27245
|
+
return response.json();
|
|
27246
|
+
}
|
|
27247
|
+
/**
|
|
27248
|
+
* Convert transfer choice args into strict Daml Value JSON for the wallet's
|
|
27249
|
+
* prepareExecute ExerciseCommand.
|
|
27250
|
+
*/
|
|
27251
|
+
function buildPrepareChoiceArgument(choiceArgs) {
|
|
27252
|
+
const contextValues = Object.entries(choiceArgs.extraArgs.context?.values ?? {})
|
|
27253
|
+
.map(([key, value]) => {
|
|
27254
|
+
const normalized = normalizeChoiceContextValue(value);
|
|
27255
|
+
return normalized
|
|
27256
|
+
? { key, value: choiceContextValueToAnyValue(normalized) }
|
|
27257
|
+
: undefined;
|
|
27258
|
+
})
|
|
27259
|
+
.filter((entry) => Boolean(entry));
|
|
27260
|
+
return vRecord([
|
|
27261
|
+
["expectedAdmin", vParty(choiceArgs.expectedAdmin)],
|
|
27262
|
+
[
|
|
27263
|
+
"transfer",
|
|
27264
|
+
vRecord([
|
|
27265
|
+
["sender", vParty(choiceArgs.transfer.sender)],
|
|
27266
|
+
["receiver", vParty(choiceArgs.transfer.receiver)],
|
|
27267
|
+
["amount", vNumeric(choiceArgs.transfer.amount)],
|
|
27268
|
+
[
|
|
27269
|
+
"instrumentId",
|
|
27270
|
+
vRecord([
|
|
27271
|
+
["admin", vParty(choiceArgs.transfer.instrumentId.admin)],
|
|
27272
|
+
["id", vText(choiceArgs.transfer.instrumentId.id)],
|
|
27273
|
+
]),
|
|
27274
|
+
],
|
|
27275
|
+
["requestedAt", vTimestamp(choiceArgs.transfer.requestedAt)],
|
|
27276
|
+
["executeBefore", vTimestamp(choiceArgs.transfer.executeBefore)],
|
|
27277
|
+
[
|
|
27278
|
+
"inputHoldingCids",
|
|
27279
|
+
vList(choiceArgs.transfer.inputHoldingCids.map(vContractId)),
|
|
27280
|
+
],
|
|
27281
|
+
[
|
|
27282
|
+
"meta",
|
|
27283
|
+
vRecord([
|
|
27284
|
+
[
|
|
27285
|
+
"values",
|
|
27286
|
+
vTextMap(Object.entries(choiceArgs.transfer.meta.values).map(([key, value]) => ({ key, value: vText(value) }))),
|
|
27287
|
+
],
|
|
27288
|
+
]),
|
|
27289
|
+
],
|
|
27290
|
+
]),
|
|
27291
|
+
],
|
|
27292
|
+
[
|
|
27293
|
+
"extraArgs",
|
|
27294
|
+
vRecord([
|
|
27295
|
+
["context", vRecord([["values", vTextMap(contextValues)]])],
|
|
27296
|
+
["meta", vRecord([["values", vTextMap([])]])],
|
|
27297
|
+
]),
|
|
27298
|
+
],
|
|
27299
|
+
]);
|
|
27300
|
+
}
|
|
27301
|
+
/**
|
|
27302
|
+
* Convert a registry disclosed contract into the wallet submission shape,
|
|
27303
|
+
* dropping debug fields and entries without a usable createdEventBlob.
|
|
27304
|
+
*/
|
|
27305
|
+
function toDisclosedContract(raw) {
|
|
27306
|
+
if (typeof raw.createdEventBlob !== "string")
|
|
27307
|
+
return null;
|
|
27308
|
+
return {
|
|
27309
|
+
createdEventBlob: raw.createdEventBlob,
|
|
27310
|
+
contractId: typeof raw.contractId === "string" ? raw.contractId : undefined,
|
|
27311
|
+
synchronizerId: typeof raw.synchronizerId === "string" ? raw.synchronizerId : undefined,
|
|
27312
|
+
};
|
|
27313
|
+
}
|
|
27314
|
+
/**
|
|
27315
|
+
* Build a Splice token-standard transfer of `amount` of `token` from `sender`
|
|
27316
|
+
* to `receiver`, carrying `memo`, ready to hand to a wallet's prepareExecute.
|
|
27317
|
+
* Returns the full prepareExecute params (commandId + commands + actAs +
|
|
27318
|
+
* disclosedContracts).
|
|
27319
|
+
*/
|
|
27320
|
+
async function buildCantonTransfer(connector, { sender, receiver, amount, token, memo, nowMs, }) {
|
|
27321
|
+
if (!token.originalAddress) {
|
|
27322
|
+
throw new Error("Canton token originalAddress is required");
|
|
27323
|
+
}
|
|
27324
|
+
const instrument = parseCantonInstrument(token.originalAddress);
|
|
27325
|
+
if (!instrument) {
|
|
27326
|
+
throw new Error(`Invalid Canton token address: ${token.originalAddress}`);
|
|
27327
|
+
}
|
|
27328
|
+
const registryUrl = resolveCantonRegistryUrl(instrument);
|
|
27329
|
+
const inputHoldingCids = await fetchInputHoldingCids(connector, sender, instrument, amount);
|
|
27330
|
+
const choiceArgs = buildTransferChoiceArgs({
|
|
27331
|
+
sender,
|
|
27332
|
+
receiver,
|
|
27333
|
+
amount,
|
|
27334
|
+
instrument,
|
|
27335
|
+
inputHoldingCids,
|
|
27336
|
+
memo,
|
|
27337
|
+
nowMs,
|
|
27338
|
+
});
|
|
27339
|
+
const factory = await fetchTransferFactory(registryUrl, choiceArgs);
|
|
27340
|
+
const choiceContextData = factory.choiceContext.choiceContextData;
|
|
27341
|
+
if (!choiceContextData) {
|
|
27342
|
+
throw new Error("Transfer factory choice context is missing");
|
|
27343
|
+
}
|
|
27344
|
+
choiceArgs.extraArgs.context = choiceContextData;
|
|
27345
|
+
const disclosedContracts = (factory.choiceContext?.disclosedContracts ?? []).flatMap((raw) => {
|
|
27346
|
+
const disclosedContract = toDisclosedContract(raw);
|
|
27347
|
+
return disclosedContract ? [disclosedContract] : [];
|
|
27348
|
+
});
|
|
27349
|
+
// Command ID: client-supplied identifier used to deduplicate submissions
|
|
27350
|
+
const commandId = getCantonCommandId(memo);
|
|
27351
|
+
return {
|
|
27352
|
+
commandId,
|
|
27353
|
+
commands: [
|
|
27354
|
+
{
|
|
27355
|
+
ExerciseCommand: {
|
|
27356
|
+
templateId: TRANSFER_FACTORY_INTERFACE_ID,
|
|
27357
|
+
contractId: factory.factoryId,
|
|
27358
|
+
choice: TRANSFER_FACTORY_CHOICE,
|
|
27359
|
+
choiceArgument: buildPrepareChoiceArgument(choiceArgs),
|
|
27360
|
+
},
|
|
27361
|
+
},
|
|
27362
|
+
],
|
|
27363
|
+
actAs: [sender],
|
|
27364
|
+
disclosedContracts,
|
|
27365
|
+
};
|
|
27366
|
+
}
|
|
27367
|
+
|
|
27368
|
+
const REQUEST_TIMEOUT_MS$1 = 60_000;
|
|
26967
27369
|
function pickAddress(accounts) {
|
|
26968
27370
|
return (accounts.find((a) => a.primary) ?? accounts[0])?.partyId;
|
|
26969
27371
|
}
|
|
@@ -26972,6 +27374,7 @@ function pickAddress(accounts) {
|
|
|
26972
27374
|
* (bound to its announce `target`) and exposes the lifecycle methods.
|
|
26973
27375
|
*/
|
|
26974
27376
|
class CantonBrowserConnector {
|
|
27377
|
+
kind = "cip103";
|
|
26975
27378
|
providerId;
|
|
26976
27379
|
name;
|
|
26977
27380
|
target;
|
|
@@ -26986,7 +27389,7 @@ class CantonBrowserConnector {
|
|
|
26986
27389
|
client ??
|
|
26987
27390
|
dapp.createCantonClient(new dapp.WindowTransport(win, {
|
|
26988
27391
|
target: this.target,
|
|
26989
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
27392
|
+
timeout: REQUEST_TIMEOUT_MS$1,
|
|
26990
27393
|
}));
|
|
26991
27394
|
}
|
|
26992
27395
|
get accounts() {
|
|
@@ -27032,6 +27435,28 @@ class CantonBrowserConnector {
|
|
|
27032
27435
|
this._accounts = await this.client.listAccounts();
|
|
27033
27436
|
return this._accounts;
|
|
27034
27437
|
}
|
|
27438
|
+
async getBalances(partyId) {
|
|
27439
|
+
const { offset } = await this.ledgerApi({
|
|
27440
|
+
requestMethod: "get",
|
|
27441
|
+
resource: "/v2/state/ledger-end",
|
|
27442
|
+
});
|
|
27443
|
+
const contracts = await this.ledgerApi({
|
|
27444
|
+
requestMethod: "post",
|
|
27445
|
+
resource: "/v2/state/active-contracts",
|
|
27446
|
+
body: buildHoldingsAcsRequestBody(partyId, String(offset)),
|
|
27447
|
+
});
|
|
27448
|
+
return groupCantonHoldingBalances(contracts);
|
|
27449
|
+
}
|
|
27450
|
+
async transfer(params) {
|
|
27451
|
+
const payload = await buildCantonTransfer(this, params);
|
|
27452
|
+
const result = await this.prepareExecuteAndWait(payload);
|
|
27453
|
+
const updateId = result.tx?.payload?.updateId ?? "";
|
|
27454
|
+
return {
|
|
27455
|
+
status: "completed",
|
|
27456
|
+
commandId: getCantonCommandId(params.memo),
|
|
27457
|
+
updateId,
|
|
27458
|
+
};
|
|
27459
|
+
}
|
|
27035
27460
|
on(event, handler) {
|
|
27036
27461
|
this.listeners[event].add(handler);
|
|
27037
27462
|
}
|
|
@@ -27138,6 +27563,313 @@ function discoverInjectedCantonWallets(win) {
|
|
|
27138
27563
|
});
|
|
27139
27564
|
}
|
|
27140
27565
|
|
|
27566
|
+
class CantonConnectionCancelledError extends Error {
|
|
27567
|
+
code = 4001;
|
|
27568
|
+
constructor() {
|
|
27569
|
+
super("Wallet connection cancelled");
|
|
27570
|
+
}
|
|
27571
|
+
}
|
|
27572
|
+
|
|
27573
|
+
const REQUEST_TIMEOUT_MS = 5 * 60_000;
|
|
27574
|
+
const OVERLAY_ID = "loop-sdk-connect-overlay";
|
|
27575
|
+
class LoopSubmissionUnknownError extends Error {
|
|
27576
|
+
constructor(cause) {
|
|
27577
|
+
super("Loop transfer outcome is unknown. Check Loop before trying again.", {
|
|
27578
|
+
cause,
|
|
27579
|
+
});
|
|
27580
|
+
}
|
|
27581
|
+
}
|
|
27582
|
+
function parseLoopTransferUpdate(value, commandId) {
|
|
27583
|
+
if (!value || typeof value !== "object")
|
|
27584
|
+
return undefined;
|
|
27585
|
+
const response = value;
|
|
27586
|
+
if (response.command_id !== commandId)
|
|
27587
|
+
return undefined;
|
|
27588
|
+
if (response.status === "failed" || response.error) {
|
|
27589
|
+
const error = response.error;
|
|
27590
|
+
return {
|
|
27591
|
+
status: "failed",
|
|
27592
|
+
commandId,
|
|
27593
|
+
message: typeof error?.error_message === "string"
|
|
27594
|
+
? error.error_message
|
|
27595
|
+
: "Loop transaction failed",
|
|
27596
|
+
};
|
|
27597
|
+
}
|
|
27598
|
+
if (typeof response.update_id === "string" && response.update_id) {
|
|
27599
|
+
return { status: "completed", commandId, updateId: response.update_id };
|
|
27600
|
+
}
|
|
27601
|
+
return undefined;
|
|
27602
|
+
}
|
|
27603
|
+
class LoopCantonConnector {
|
|
27604
|
+
loadSdk;
|
|
27605
|
+
kind = "loop";
|
|
27606
|
+
providerId = "canton-loop";
|
|
27607
|
+
name = "Loop";
|
|
27608
|
+
target = undefined;
|
|
27609
|
+
module;
|
|
27610
|
+
initialization;
|
|
27611
|
+
provider;
|
|
27612
|
+
pendingConnection;
|
|
27613
|
+
connectionStart = Promise.resolve();
|
|
27614
|
+
transfers = new Map();
|
|
27615
|
+
listeners = { connect: new Set(), disconnect: new Set() };
|
|
27616
|
+
constructor(loadSdk = () => import('@fivenorth/loop-sdk')) {
|
|
27617
|
+
this.loadSdk = loadSdk;
|
|
27618
|
+
}
|
|
27619
|
+
get accounts() {
|
|
27620
|
+
return this.provider
|
|
27621
|
+
? [{ partyId: this.provider.party_id, primary: true }]
|
|
27622
|
+
: [];
|
|
27623
|
+
}
|
|
27624
|
+
get address() {
|
|
27625
|
+
return this.provider?.party_id;
|
|
27626
|
+
}
|
|
27627
|
+
connect() {
|
|
27628
|
+
return this.startConnection(true);
|
|
27629
|
+
}
|
|
27630
|
+
autoConnect() {
|
|
27631
|
+
return this.startConnection(false);
|
|
27632
|
+
}
|
|
27633
|
+
cancelConnect() {
|
|
27634
|
+
if (!this.pendingConnection)
|
|
27635
|
+
return;
|
|
27636
|
+
this.rejectConnection(new CantonConnectionCancelledError());
|
|
27637
|
+
this.module?.loop.logout();
|
|
27638
|
+
}
|
|
27639
|
+
async disconnect() {
|
|
27640
|
+
this.cancelConnect();
|
|
27641
|
+
this.module?.loop.logout();
|
|
27642
|
+
this.provider = undefined;
|
|
27643
|
+
this.emit("disconnect");
|
|
27644
|
+
}
|
|
27645
|
+
async getAccounts() {
|
|
27646
|
+
const provider = this.requireProvider();
|
|
27647
|
+
try {
|
|
27648
|
+
const account = await provider.getAccount();
|
|
27649
|
+
if (account.party_id !== provider.party_id) {
|
|
27650
|
+
await this.disconnect();
|
|
27651
|
+
throw new Error("Loop account changed. Connect again.");
|
|
27652
|
+
}
|
|
27653
|
+
return this.accounts;
|
|
27654
|
+
}
|
|
27655
|
+
catch (error) {
|
|
27656
|
+
if (this.module && error instanceof this.module.UnauthorizedError) {
|
|
27657
|
+
await this.disconnect();
|
|
27658
|
+
}
|
|
27659
|
+
throw error;
|
|
27660
|
+
}
|
|
27661
|
+
}
|
|
27662
|
+
async getBalances(partyId) {
|
|
27663
|
+
const provider = this.requireProvider();
|
|
27664
|
+
if (partyId !== provider.party_id) {
|
|
27665
|
+
throw new Error("Loop balances require the connected account");
|
|
27666
|
+
}
|
|
27667
|
+
const holdings = await provider.getHolding();
|
|
27668
|
+
return sumCantonHoldingBalances(holdings.map((holding) => {
|
|
27669
|
+
const amount = new BigNumber(holding.total_unlocked_coin);
|
|
27670
|
+
if (!amount.isFinite() || amount.isNegative()) {
|
|
27671
|
+
throw new Error("Loop returned an invalid balance");
|
|
27672
|
+
}
|
|
27673
|
+
return {
|
|
27674
|
+
instrument: holding.instrument_id,
|
|
27675
|
+
amount: holding.total_unlocked_coin,
|
|
27676
|
+
};
|
|
27677
|
+
}));
|
|
27678
|
+
}
|
|
27679
|
+
transfer(params) {
|
|
27680
|
+
const commandId = getCantonCommandId(params.memo);
|
|
27681
|
+
const existing = this.transfers.get(commandId);
|
|
27682
|
+
if (existing)
|
|
27683
|
+
return existing;
|
|
27684
|
+
const request = this.submitTransfer(params, commandId).catch((error) => {
|
|
27685
|
+
if (!(error instanceof LoopSubmissionUnknownError))
|
|
27686
|
+
this.transfers.delete(commandId);
|
|
27687
|
+
throw error;
|
|
27688
|
+
});
|
|
27689
|
+
this.transfers.set(commandId, request);
|
|
27690
|
+
return request;
|
|
27691
|
+
}
|
|
27692
|
+
on(event, handler) {
|
|
27693
|
+
this.listeners[event].add(handler);
|
|
27694
|
+
}
|
|
27695
|
+
off(event, handler) {
|
|
27696
|
+
this.listeners[event].delete(handler);
|
|
27697
|
+
}
|
|
27698
|
+
emit(event, ...args) {
|
|
27699
|
+
this.listeners[event].forEach((handler) => handler(...args));
|
|
27700
|
+
}
|
|
27701
|
+
initialize() {
|
|
27702
|
+
if (!this.initialization) {
|
|
27703
|
+
this.initialization = this.loadSdk()
|
|
27704
|
+
.then((module) => {
|
|
27705
|
+
module.loop.init({
|
|
27706
|
+
appName: SQUID_METADATA.name,
|
|
27707
|
+
network: "mainnet",
|
|
27708
|
+
onAccept: (provider) => this.acceptConnection(provider),
|
|
27709
|
+
onReject: () => {
|
|
27710
|
+
this.rejectConnection(new CantonConnectionCancelledError());
|
|
27711
|
+
this.provider = undefined;
|
|
27712
|
+
this.emit("disconnect");
|
|
27713
|
+
},
|
|
27714
|
+
});
|
|
27715
|
+
this.module = module;
|
|
27716
|
+
})
|
|
27717
|
+
.catch((error) => {
|
|
27718
|
+
this.initialization = undefined;
|
|
27719
|
+
throw error;
|
|
27720
|
+
});
|
|
27721
|
+
}
|
|
27722
|
+
return this.initialization;
|
|
27723
|
+
}
|
|
27724
|
+
startConnection(interactive) {
|
|
27725
|
+
if (this.pendingConnection)
|
|
27726
|
+
return this.pendingConnection.promise;
|
|
27727
|
+
let resolve;
|
|
27728
|
+
let reject;
|
|
27729
|
+
const promise = new Promise((accept, decline) => {
|
|
27730
|
+
resolve = accept;
|
|
27731
|
+
reject = decline;
|
|
27732
|
+
});
|
|
27733
|
+
const timer = setTimeout(() => {
|
|
27734
|
+
this.rejectConnection(new Error("Loop connection timed out"));
|
|
27735
|
+
this.module?.loop.logout();
|
|
27736
|
+
}, REQUEST_TIMEOUT_MS);
|
|
27737
|
+
let observer;
|
|
27738
|
+
const pending = {
|
|
27739
|
+
promise,
|
|
27740
|
+
resolve,
|
|
27741
|
+
reject,
|
|
27742
|
+
cleanup: () => {
|
|
27743
|
+
clearTimeout(timer);
|
|
27744
|
+
observer?.disconnect();
|
|
27745
|
+
},
|
|
27746
|
+
};
|
|
27747
|
+
this.pendingConnection = pending;
|
|
27748
|
+
this.connectionStart = this.connectionStart
|
|
27749
|
+
.then(async () => {
|
|
27750
|
+
await this.initialize();
|
|
27751
|
+
if (this.pendingConnection !== pending)
|
|
27752
|
+
return;
|
|
27753
|
+
if (interactive && typeof MutationObserver !== "undefined") {
|
|
27754
|
+
let sawOverlay = false;
|
|
27755
|
+
observer = new MutationObserver(() => {
|
|
27756
|
+
const overlay = document.getElementById(OVERLAY_ID);
|
|
27757
|
+
if (overlay)
|
|
27758
|
+
sawOverlay = true;
|
|
27759
|
+
if (sawOverlay && !overlay && this.pendingConnection === pending) {
|
|
27760
|
+
this.cancelConnect();
|
|
27761
|
+
}
|
|
27762
|
+
});
|
|
27763
|
+
observer.observe(document.body, { childList: true });
|
|
27764
|
+
}
|
|
27765
|
+
const sdk = this.module.loop;
|
|
27766
|
+
if (interactive)
|
|
27767
|
+
await sdk.connect();
|
|
27768
|
+
else
|
|
27769
|
+
await sdk.autoConnect();
|
|
27770
|
+
if (!interactive && this.pendingConnection === pending) {
|
|
27771
|
+
this.rejectConnection(new Error("No saved Loop connection"));
|
|
27772
|
+
}
|
|
27773
|
+
else if (interactive &&
|
|
27774
|
+
this.pendingConnection !== pending &&
|
|
27775
|
+
!this.provider) {
|
|
27776
|
+
sdk.logout();
|
|
27777
|
+
}
|
|
27778
|
+
})
|
|
27779
|
+
.catch((error) => {
|
|
27780
|
+
if (this.pendingConnection === pending)
|
|
27781
|
+
this.rejectConnection(error);
|
|
27782
|
+
});
|
|
27783
|
+
return promise;
|
|
27784
|
+
}
|
|
27785
|
+
acceptConnection(provider) {
|
|
27786
|
+
const pending = this.pendingConnection;
|
|
27787
|
+
if (!pending)
|
|
27788
|
+
return;
|
|
27789
|
+
if (!provider.party_id) {
|
|
27790
|
+
this.rejectConnection(new Error("Loop returned no account"));
|
|
27791
|
+
return;
|
|
27792
|
+
}
|
|
27793
|
+
this.provider = provider;
|
|
27794
|
+
this.pendingConnection = undefined;
|
|
27795
|
+
pending.cleanup();
|
|
27796
|
+
pending.resolve({ address: provider.party_id, accounts: this.accounts });
|
|
27797
|
+
this.emit("connect", provider.party_id);
|
|
27798
|
+
}
|
|
27799
|
+
rejectConnection(error) {
|
|
27800
|
+
const pending = this.pendingConnection;
|
|
27801
|
+
if (!pending)
|
|
27802
|
+
return;
|
|
27803
|
+
this.pendingConnection = undefined;
|
|
27804
|
+
pending.cleanup();
|
|
27805
|
+
pending.reject(error);
|
|
27806
|
+
}
|
|
27807
|
+
requireProvider() {
|
|
27808
|
+
if (!this.provider)
|
|
27809
|
+
throw new Error("Loop wallet is not connected");
|
|
27810
|
+
return this.provider;
|
|
27811
|
+
}
|
|
27812
|
+
async submitTransfer(params, commandId) {
|
|
27813
|
+
const provider = this.requireProvider();
|
|
27814
|
+
if (params.sender !== provider.party_id)
|
|
27815
|
+
throw new Error("Loop account does not match the sender");
|
|
27816
|
+
const instrument = params.token.originalAddress
|
|
27817
|
+
? parseCantonInstrument(params.token.originalAddress)
|
|
27818
|
+
: null;
|
|
27819
|
+
if (!instrument)
|
|
27820
|
+
throw new Error("Canton token originalAddress is required");
|
|
27821
|
+
if (!new BigNumber(params.amount).isFinite() ||
|
|
27822
|
+
!new BigNumber(params.amount).isPositive()) {
|
|
27823
|
+
throw new Error("Canton transfer amount must be positive");
|
|
27824
|
+
}
|
|
27825
|
+
const payload = await provider.connection.prepareTransfer(provider.getAuthToken(), {
|
|
27826
|
+
recipient: params.receiver,
|
|
27827
|
+
amount: params.amount,
|
|
27828
|
+
instrument: {
|
|
27829
|
+
instrument_admin: instrument.admin,
|
|
27830
|
+
instrument_id: instrument.id,
|
|
27831
|
+
},
|
|
27832
|
+
memo: params.memo,
|
|
27833
|
+
requested_at: new Date(params.nowMs - 60_000).toISOString(),
|
|
27834
|
+
execute_before: new Date(params.nowMs + 24 * 60 * 60_000).toISOString(),
|
|
27835
|
+
});
|
|
27836
|
+
if (this.provider !== provider)
|
|
27837
|
+
throw new Error("Loop account disconnected before submission");
|
|
27838
|
+
let response;
|
|
27839
|
+
let timer;
|
|
27840
|
+
try {
|
|
27841
|
+
const submission = { ...payload, commandId };
|
|
27842
|
+
const submitted = provider.submitAndWaitForTransaction(submission, {
|
|
27843
|
+
requestTimeout: REQUEST_TIMEOUT_MS,
|
|
27844
|
+
});
|
|
27845
|
+
response = await Promise.race([
|
|
27846
|
+
submitted,
|
|
27847
|
+
new Promise((_, reject) => {
|
|
27848
|
+
timer = setTimeout(() => reject(new Error("Loop submission timed out")), REQUEST_TIMEOUT_MS);
|
|
27849
|
+
}),
|
|
27850
|
+
]);
|
|
27851
|
+
}
|
|
27852
|
+
catch (error) {
|
|
27853
|
+
if (this.module && error instanceof this.module.RejectRequestError) {
|
|
27854
|
+
throw Object.assign(new Error("Loop transaction rejected"), {
|
|
27855
|
+
code: 4001,
|
|
27856
|
+
});
|
|
27857
|
+
}
|
|
27858
|
+
throw new LoopSubmissionUnknownError(error);
|
|
27859
|
+
}
|
|
27860
|
+
finally {
|
|
27861
|
+
clearTimeout(timer);
|
|
27862
|
+
}
|
|
27863
|
+
const update = parseLoopTransferUpdate(response, commandId);
|
|
27864
|
+
if (update?.status === "failed")
|
|
27865
|
+
throw new Error(update.message);
|
|
27866
|
+
if (!update)
|
|
27867
|
+
throw new LoopSubmissionUnknownError();
|
|
27868
|
+
return update;
|
|
27869
|
+
}
|
|
27870
|
+
}
|
|
27871
|
+
const loopCantonConnector = new LoopCantonConnector();
|
|
27872
|
+
|
|
27141
27873
|
/**
|
|
27142
27874
|
* Discovers Canton browser-extension wallets and maps each to a
|
|
27143
27875
|
* {@link CantonWallet}: CIP-103 announce-protocol wallets (on mount and on
|
|
@@ -27184,7 +27916,19 @@ function useCantonWallets() {
|
|
|
27184
27916
|
return;
|
|
27185
27917
|
const announced = (await discoverCantonProviders()).map(toAnnouncedWallet);
|
|
27186
27918
|
const injected = discoverInjectedCantonWallets().map(toInjectedWallet);
|
|
27187
|
-
|
|
27919
|
+
const loopWallet = {
|
|
27920
|
+
...formatCantonWallet({
|
|
27921
|
+
connectorId: "canton-loop",
|
|
27922
|
+
name: "Loop",
|
|
27923
|
+
icon: "https://raw.githubusercontent.com/0xsquid/assets/main/images/master/wallets/canton-loop.svg",
|
|
27924
|
+
connector: loopCantonConnector,
|
|
27925
|
+
}),
|
|
27926
|
+
links: { website: "https://cantonloop.com" },
|
|
27927
|
+
isInstalled: () => false,
|
|
27928
|
+
skipInstallCheck: true,
|
|
27929
|
+
isMobile: true,
|
|
27930
|
+
};
|
|
27931
|
+
setWallets(mergeInjectedCantonWallets(announced, [...injected, loopWallet]));
|
|
27188
27932
|
}, [toAnnouncedWallet, toInjectedWallet]);
|
|
27189
27933
|
React.useEffect(() => {
|
|
27190
27934
|
refresh();
|
|
@@ -27215,7 +27959,9 @@ function useCanton() {
|
|
|
27215
27959
|
// Try to reconnect silently first (existing session, no popup)
|
|
27216
27960
|
result = await wallet.connector.autoConnect();
|
|
27217
27961
|
}
|
|
27218
|
-
catch {
|
|
27962
|
+
catch (error) {
|
|
27963
|
+
if (error instanceof CantonConnectionCancelledError)
|
|
27964
|
+
throw error;
|
|
27219
27965
|
// Otherwise prompt the wallet to connect
|
|
27220
27966
|
result = await wallet.connector.connect();
|
|
27221
27967
|
}
|
|
@@ -27237,8 +27983,7 @@ function useCanton() {
|
|
|
27237
27983
|
wallet: recentCantonWallet,
|
|
27238
27984
|
address,
|
|
27239
27985
|
});
|
|
27240
|
-
|
|
27241
|
-
}, [wallets.map((w) => w.connectorId), recentCantonWalletId]);
|
|
27986
|
+
}, [wallets, recentCantonWalletId, setConnectedWallet]);
|
|
27242
27987
|
React.useEffect(() => {
|
|
27243
27988
|
const wallet = connectedCantonWallet.wallet;
|
|
27244
27989
|
if (!wallet) {
|
|
@@ -27495,7 +28240,7 @@ function useStellarWallets() {
|
|
|
27495
28240
|
try {
|
|
27496
28241
|
const { allowAllModules: initializeAllModules } = await import('@creit.tech/stellar-wallets-kit');
|
|
27497
28242
|
const { LedgerModule } = await import('@creit.tech/stellar-wallets-kit/modules/ledger.module.mjs');
|
|
27498
|
-
const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-
|
|
28243
|
+
const { formatStellarWallet } = await Promise.resolve().then(function () { return require('./stellarService.client-fnyXRadt.js'); });
|
|
27499
28244
|
const modules = [...initializeAllModules(), new LedgerModule()];
|
|
27500
28245
|
const promises = modules.map(async (module) => {
|
|
27501
28246
|
const isAvailable = await module.isAvailable();
|
|
@@ -28074,6 +28819,11 @@ const useWallet = () => {
|
|
|
28074
28819
|
}
|
|
28075
28820
|
}
|
|
28076
28821
|
catch (error) {
|
|
28822
|
+
const connecting = useWalletStore.getState().connectingWalletState;
|
|
28823
|
+
if (connecting.wallet !== wallet ||
|
|
28824
|
+
connecting.status !== exports.ConnectingWalletStatus.CONNECTING) {
|
|
28825
|
+
return false;
|
|
28826
|
+
}
|
|
28077
28827
|
console.error("Failed to connect wallet", error);
|
|
28078
28828
|
walletConnectionError(wallet, error);
|
|
28079
28829
|
return false;
|
|
@@ -28305,8 +29055,6 @@ const useMultiChainWallet = (chain) => {
|
|
|
28305
29055
|
chainType: chain.chainType,
|
|
28306
29056
|
}),
|
|
28307
29057
|
};
|
|
28308
|
-
case squidTypes.ChainType.DANAL:
|
|
28309
|
-
return {};
|
|
28310
29058
|
}
|
|
28311
29059
|
}, [
|
|
28312
29060
|
chain?.chainType,
|
|
@@ -28420,12 +29168,27 @@ const useMultiChainWallet = (chain) => {
|
|
|
28420
29168
|
};
|
|
28421
29169
|
|
|
28422
29170
|
const useSwap = () => {
|
|
28423
|
-
const { initialAssets, defaultTokensPerChain, disabledChains, availableChains, } = useConfigStore((state) => state.config);
|
|
29171
|
+
const { initialAssets, defaultTokensPerChain, disabledChains, availableChains, availableTokens, } = useConfigStore((state) => state.config);
|
|
28424
29172
|
const fromPrice = useTransactionStore((state) => state.fromPrice);
|
|
28425
|
-
const { swapRoute } = useSwapRoutePersistStore();
|
|
29173
|
+
const { swapRoute: storedSwapRoute } = useSwapRoutePersistStore();
|
|
28426
29174
|
const { tokens } = useSquidTokens();
|
|
28427
29175
|
const queryClient = reactQuery.useQueryClient();
|
|
28428
29176
|
const { chains, supportedDestinationChains, supportedSourceChains } = useSquidChains();
|
|
29177
|
+
const swapRoute = React.useMemo(() => storedSwapRoute
|
|
29178
|
+
? normalizeSwapParamsForSelection({
|
|
29179
|
+
swapParams: storedSwapRoute,
|
|
29180
|
+
tokens,
|
|
29181
|
+
chains,
|
|
29182
|
+
config: { disabledChains, availableChains, availableTokens },
|
|
29183
|
+
})
|
|
29184
|
+
: undefined, [
|
|
29185
|
+
storedSwapRoute,
|
|
29186
|
+
tokens,
|
|
29187
|
+
chains,
|
|
29188
|
+
disabledChains,
|
|
29189
|
+
availableChains,
|
|
29190
|
+
availableTokens,
|
|
29191
|
+
]);
|
|
28429
29192
|
const sourceChainId = React.useMemo(() => swapRoute?.fromChainId ??
|
|
28430
29193
|
(initialAssets?.from
|
|
28431
29194
|
? resolveChainIdFromAsset(initialAssets.from, supportedSourceChains)
|
|
@@ -28436,13 +29199,23 @@ const useSwap = () => {
|
|
|
28436
29199
|
: undefined), [swapRoute?.toChainId, supportedDestinationChains, initialAssets?.to]);
|
|
28437
29200
|
const fromChain = React.useMemo(() => supportedSourceChains.find((c) => c.chainId === swapRoute?.fromChainId), [swapRoute?.fromChainId, supportedSourceChains]);
|
|
28438
29201
|
const toChain = React.useMemo(() => supportedDestinationChains.find((c) => c.chainId === swapRoute?.toChainId), [swapRoute?.toChainId, supportedDestinationChains]);
|
|
28439
|
-
const fromTokens = React.useMemo(() =>
|
|
29202
|
+
const fromTokens = React.useMemo(() => {
|
|
29203
|
+
const chainTokens = getTokensForChain(tokens, sourceChainId);
|
|
29204
|
+
return isArcChain(sourceChainId)
|
|
29205
|
+
? filterViewableTokens(chainTokens, { availableTokens }, "from")
|
|
29206
|
+
: chainTokens;
|
|
29207
|
+
}, [tokens, sourceChainId, availableTokens]);
|
|
28440
29208
|
const fromToken = React.useMemo(() => fromTokens.find((t) => t.address.toLowerCase() === swapRoute?.fromTokenAddress?.toLowerCase()), [fromTokens, swapRoute?.fromTokenAddress]);
|
|
28441
|
-
const toTokens = React.useMemo(() =>
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28445
|
-
|
|
29209
|
+
const toTokens = React.useMemo(() => {
|
|
29210
|
+
const destinationTokens = filterTokensForDestination({
|
|
29211
|
+
tokens: getTokensForChain(tokens, destChainId),
|
|
29212
|
+
selectedDestinationChain: toChain,
|
|
29213
|
+
selectedSourceToken: fromToken,
|
|
29214
|
+
});
|
|
29215
|
+
return isArcChain(destChainId)
|
|
29216
|
+
? filterViewableTokens(destinationTokens, { availableTokens }, "to")
|
|
29217
|
+
: destinationTokens;
|
|
29218
|
+
}, [destChainId, toChain, fromToken, tokens, availableTokens]);
|
|
28446
29219
|
const toToken = React.useMemo(() => toTokens.find((t) => t.address.toLowerCase() === swapRoute?.toTokenAddress?.toLowerCase()), [toTokens, swapRoute?.toTokenAddress]);
|
|
28447
29220
|
const isSameChain = sourceChainId === destChainId;
|
|
28448
29221
|
const tokenItems = React.useMemo(() => ({ from: fromTokens, to: toTokens }), [fromTokens, toTokens]);
|
|
@@ -28490,6 +29263,7 @@ const useSwap = () => {
|
|
|
28490
29263
|
defaultTokensPerChain,
|
|
28491
29264
|
disabledChains,
|
|
28492
29265
|
availableChains,
|
|
29266
|
+
availableTokens,
|
|
28493
29267
|
},
|
|
28494
29268
|
queryClient,
|
|
28495
29269
|
connectedSourceAddress: connectedSourceAddress.address,
|
|
@@ -28529,6 +29303,7 @@ const useSwap = () => {
|
|
|
28529
29303
|
defaultTokensPerChain,
|
|
28530
29304
|
disabledChains,
|
|
28531
29305
|
availableChains,
|
|
29306
|
+
availableTokens,
|
|
28532
29307
|
initialAssets,
|
|
28533
29308
|
queryClient,
|
|
28534
29309
|
swapRoute,
|
|
@@ -28584,6 +29359,10 @@ const useSwap = () => {
|
|
|
28584
29359
|
},
|
|
28585
29360
|
direction: "to",
|
|
28586
29361
|
});
|
|
29362
|
+
if ([defaultFromChainId, defaultToChainId].some((chainId) => isArcChain(chainId) &&
|
|
29363
|
+
!tokens.some((token) => token.chainId === chainId && !isArcNativeToken(token)))) {
|
|
29364
|
+
return;
|
|
29365
|
+
}
|
|
28587
29366
|
const fromTokenAddress = getInitialOrDefaultTokenAddressForChain({
|
|
28588
29367
|
tokens,
|
|
28589
29368
|
config: {
|
|
@@ -28591,6 +29370,7 @@ const useSwap = () => {
|
|
|
28591
29370
|
defaultTokensPerChain,
|
|
28592
29371
|
disabledChains,
|
|
28593
29372
|
availableChains,
|
|
29373
|
+
availableTokens,
|
|
28594
29374
|
},
|
|
28595
29375
|
chainId: defaultFromChainId,
|
|
28596
29376
|
direction: "from",
|
|
@@ -28602,6 +29382,7 @@ const useSwap = () => {
|
|
|
28602
29382
|
defaultTokensPerChain,
|
|
28603
29383
|
disabledChains,
|
|
28604
29384
|
availableChains,
|
|
29385
|
+
availableTokens,
|
|
28605
29386
|
},
|
|
28606
29387
|
chainId: defaultToChainId,
|
|
28607
29388
|
direction: "to",
|
|
@@ -28626,6 +29407,7 @@ const useSwap = () => {
|
|
|
28626
29407
|
initialAssets,
|
|
28627
29408
|
disabledChains,
|
|
28628
29409
|
availableChains,
|
|
29410
|
+
availableTokens,
|
|
28629
29411
|
defaultTokensPerChain,
|
|
28630
29412
|
tokens,
|
|
28631
29413
|
]);
|
|
@@ -29145,8 +29927,6 @@ async function createClient(chain) {
|
|
|
29145
29927
|
return new StellarRpcClient(chain.rpc);
|
|
29146
29928
|
case squidTypes.ChainType.CANTON:
|
|
29147
29929
|
return null;
|
|
29148
|
-
case squidTypes.ChainType.DANAL:
|
|
29149
|
-
return null;
|
|
29150
29930
|
}
|
|
29151
29931
|
}
|
|
29152
29932
|
|
|
@@ -30161,8 +30941,7 @@ const getAllStellarTokensBalance = async (userAddress, stellarTokens, stellarCha
|
|
|
30161
30941
|
};
|
|
30162
30942
|
/**
|
|
30163
30943
|
* Canton balances are private: they can only be read with a wallet-authorized
|
|
30164
|
-
*
|
|
30165
|
-
* party). So balance is fetchable only when a Canton wallet is connected, and
|
|
30944
|
+
* session. Balance is fetchable only when a Canton wallet is connected, and
|
|
30166
30945
|
* only for that wallet's party — there's no address-only read.
|
|
30167
30946
|
*/
|
|
30168
30947
|
const getCantonTokenBalance = async (userAddress, token, cantonConnector) => {
|
|
@@ -30171,30 +30950,14 @@ const getCantonTokenBalance = async (userAddress, token, cantonConnector) => {
|
|
|
30171
30950
|
const instrument = parseCantonInstrument(token.originalAddress);
|
|
30172
30951
|
if (!instrument)
|
|
30173
30952
|
return "0";
|
|
30174
|
-
const
|
|
30175
|
-
const activeContracts = await cantonConnector.ledgerApi({
|
|
30176
|
-
requestMethod: "post",
|
|
30177
|
-
resource: "/v2/state/active-contracts",
|
|
30178
|
-
body: buildHoldingsAcsRequestBody(userAddress, String(ledgerEnd.offset)),
|
|
30179
|
-
});
|
|
30180
|
-
const grouped = groupCantonHoldingBalances(activeContracts);
|
|
30953
|
+
const grouped = await cantonConnector.getBalances(userAddress);
|
|
30181
30954
|
return grouped[cantonInstrumentKey(instrument)] ?? "0";
|
|
30182
30955
|
};
|
|
30183
30956
|
/**
|
|
30184
|
-
* Fetch balances for all Canton tokens
|
|
30185
|
-
* private, so this requires a connected wallet's authorized `ledgerApi`.
|
|
30957
|
+
* Fetch balances for all Canton tokens through the connected wallet.
|
|
30186
30958
|
*/
|
|
30187
30959
|
async function getAllCantonTokensBalance({ connector, partyId, tokens, }) {
|
|
30188
|
-
const
|
|
30189
|
-
requestMethod: "get",
|
|
30190
|
-
resource: "/v2/state/ledger-end",
|
|
30191
|
-
});
|
|
30192
|
-
const activeContracts = await connector.ledgerApi({
|
|
30193
|
-
requestMethod: "post",
|
|
30194
|
-
resource: "/v2/state/active-contracts",
|
|
30195
|
-
body: buildHoldingsAcsRequestBody(partyId, String(ledgerEnd.offset)),
|
|
30196
|
-
});
|
|
30197
|
-
const grouped = groupCantonHoldingBalances(activeContracts);
|
|
30960
|
+
const grouped = await connector.getBalances(partyId);
|
|
30198
30961
|
return tokens.map((token) => {
|
|
30199
30962
|
const instrument = token.originalAddress
|
|
30200
30963
|
? parseCantonInstrument(token.originalAddress)
|
|
@@ -30413,7 +31176,7 @@ const useCantonBalance = ({ chain, token, userAddress, enabled = true, refreshIn
|
|
|
30413
31176
|
};
|
|
30414
31177
|
|
|
30415
31178
|
function useNativeTokenForChain(chain) {
|
|
30416
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
31179
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = useSquidTokens();
|
|
30417
31180
|
const getTokensForChainType = () => {
|
|
30418
31181
|
if (!chain?.chainType)
|
|
30419
31182
|
return [];
|
|
@@ -30434,21 +31197,20 @@ function useNativeTokenForChain(chain) {
|
|
|
30434
31197
|
return stellarTokens;
|
|
30435
31198
|
case squidTypes.ChainType.CANTON:
|
|
30436
31199
|
return cantonTokens;
|
|
30437
|
-
case squidTypes.ChainType.DANAL:
|
|
30438
|
-
return danalTokens;
|
|
30439
31200
|
}
|
|
30440
31201
|
};
|
|
31202
|
+
const tokens = getTokensForChainType();
|
|
30441
31203
|
const nativeTokenForChainType = React.useMemo(() => {
|
|
30442
31204
|
if (!chain)
|
|
30443
31205
|
return undefined;
|
|
30444
|
-
return findNativeToken(
|
|
30445
|
-
}, [chain]);
|
|
31206
|
+
return findNativeToken(tokens, chain);
|
|
31207
|
+
}, [chain, tokens]);
|
|
30446
31208
|
return { nativeToken: nativeTokenForChainType };
|
|
30447
31209
|
}
|
|
30448
31210
|
|
|
30449
31211
|
const useEvmNativeBalance = ({ address, chain, }) => {
|
|
30450
31212
|
const { isChainTypeConnected } = useWallet();
|
|
30451
|
-
const { data: nativeEvmBalance, isLoading } = wagmi.useBalance({
|
|
31213
|
+
const { data: nativeEvmBalance, isLoading, isError, } = wagmi.useBalance({
|
|
30452
31214
|
address: address,
|
|
30453
31215
|
chainId: Number(chain?.chainId),
|
|
30454
31216
|
query: {
|
|
@@ -30460,6 +31222,8 @@ const useEvmNativeBalance = ({ address, chain, }) => {
|
|
|
30460
31222
|
},
|
|
30461
31223
|
});
|
|
30462
31224
|
const balance = React.useMemo(() => {
|
|
31225
|
+
if (isArcChain(chain?.chainId) && isError)
|
|
31226
|
+
return undefined;
|
|
30463
31227
|
if (nativeEvmBalance?.decimals != null && nativeEvmBalance?.value != null) {
|
|
30464
31228
|
if (chain?.chainId === CHAIN_IDS.HEDERA) {
|
|
30465
31229
|
return {
|
|
@@ -30473,7 +31237,12 @@ const useEvmNativeBalance = ({ address, chain, }) => {
|
|
|
30473
31237
|
};
|
|
30474
31238
|
}
|
|
30475
31239
|
return;
|
|
30476
|
-
}, [
|
|
31240
|
+
}, [
|
|
31241
|
+
nativeEvmBalance?.decimals,
|
|
31242
|
+
nativeEvmBalance?.value,
|
|
31243
|
+
chain?.chainId,
|
|
31244
|
+
isError,
|
|
31245
|
+
]);
|
|
30477
31246
|
return {
|
|
30478
31247
|
balance,
|
|
30479
31248
|
isLoading,
|
|
@@ -30696,9 +31465,6 @@ const useNativeBalance = (chain) => {
|
|
|
30696
31465
|
case squidTypes.ChainType.CANTON:
|
|
30697
31466
|
balance = nativeCantonBalance;
|
|
30698
31467
|
break;
|
|
30699
|
-
case squidTypes.ChainType.DANAL:
|
|
30700
|
-
balance = undefined;
|
|
30701
|
-
break;
|
|
30702
31468
|
}
|
|
30703
31469
|
const balanceFormatted = !!balance
|
|
30704
31470
|
? formatBNToReadable(balance.value, balance.decimals)
|
|
@@ -30738,8 +31504,6 @@ const useNativeBalance = (chain) => {
|
|
|
30738
31504
|
return isStellarLoading;
|
|
30739
31505
|
case squidTypes.ChainType.CANTON:
|
|
30740
31506
|
return isCantonLoading;
|
|
30741
|
-
case squidTypes.ChainType.DANAL:
|
|
30742
|
-
return false;
|
|
30743
31507
|
}
|
|
30744
31508
|
}, [
|
|
30745
31509
|
chain?.chainType,
|
|
@@ -30965,7 +31729,7 @@ function hederaWalletConnect(parameters) {
|
|
|
30965
31729
|
const optionalChains = config.chains.map((x) => x.id);
|
|
30966
31730
|
if (!optionalChains.length)
|
|
30967
31731
|
return;
|
|
30968
|
-
const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-
|
|
31732
|
+
const { EthereumProvider } = await Promise.resolve().then(function () { return require('./index.es-BVCh4tlV.js'); });
|
|
30969
31733
|
const rawProvider = await EthereumProvider.init({
|
|
30970
31734
|
...restParameters,
|
|
30971
31735
|
disableProviderPing: true,
|
|
@@ -31199,8 +31963,6 @@ const useSigner = ({ chain }) => {
|
|
|
31199
31963
|
return isStellarSignerReady;
|
|
31200
31964
|
case squidTypes.ChainType.CANTON:
|
|
31201
31965
|
return isCantonSignerReady;
|
|
31202
|
-
case squidTypes.ChainType.DANAL:
|
|
31203
|
-
return false;
|
|
31204
31966
|
}
|
|
31205
31967
|
}, [
|
|
31206
31968
|
chain?.chainType,
|
|
@@ -31653,12 +32415,8 @@ const useSendTransactionStore = zustand.create((set, get) => ({
|
|
|
31653
32415
|
const useDepositAddressStore = zustand.create((set) => ({
|
|
31654
32416
|
deposit: null,
|
|
31655
32417
|
selectedPaymentMethod: "connectedWallet",
|
|
31656
|
-
userDepositTxId: null,
|
|
31657
32418
|
setDeposit: (data) => {
|
|
31658
|
-
set({ deposit: data
|
|
31659
|
-
},
|
|
31660
|
-
setUserDepositTxId: (txId) => {
|
|
31661
|
-
set({ userDepositTxId: txId });
|
|
32419
|
+
set({ deposit: data });
|
|
31662
32420
|
},
|
|
31663
32421
|
setPaymentMethod: (method) => {
|
|
31664
32422
|
set({ selectedPaymentMethod: method });
|
|
@@ -31670,7 +32428,7 @@ function useDepositAddress(squidRoute) {
|
|
|
31670
32428
|
selectedPaymentMethod: state.selectedPaymentMethod,
|
|
31671
32429
|
depositAddress: state.deposit?.depositAddress,
|
|
31672
32430
|
}));
|
|
31673
|
-
const { setDeposit, setPaymentMethod, deposit
|
|
32431
|
+
const { setDeposit, setPaymentMethod, deposit } = useDepositAddressStore();
|
|
31674
32432
|
const { squid } = useSquidStore();
|
|
31675
32433
|
const { fromChain } = useSwap();
|
|
31676
32434
|
const isAvailableAsPaymentMethod = React.useMemo(() => {
|
|
@@ -31680,13 +32438,11 @@ function useDepositAddress(squidRoute) {
|
|
|
31680
32438
|
CHAIN_IDS.BITCOIN,
|
|
31681
32439
|
CHAIN_IDS.SOLANA,
|
|
31682
32440
|
CHAIN_IDS.CANTON,
|
|
31683
|
-
CHAIN_IDS.DANAL_TESTNET,
|
|
31684
32441
|
];
|
|
31685
32442
|
return chainsSupportingDepositAddress.includes(fromChain.chainId);
|
|
31686
32443
|
}, [fromChain?.chainId]);
|
|
31687
|
-
const routeProvidesDirectDepositAddress =
|
|
31688
|
-
isDepositAddressDirectTransferRoute(squidRoute.transactionRequest)
|
|
31689
|
-
(!!squidRoute && isDanalDepositRoute(squidRoute));
|
|
32444
|
+
const routeProvidesDirectDepositAddress = !!squidRoute?.transactionRequest &&
|
|
32445
|
+
isDepositAddressDirectTransferRoute(squidRoute.transactionRequest);
|
|
31690
32446
|
const swapWillGenerateDepositAddress = isChainflipDepositRoute(squidRoute) || routeProvidesDirectDepositAddress;
|
|
31691
32447
|
const paymentMethod = isAvailableAsPaymentMethod
|
|
31692
32448
|
? selectedPaymentMethod
|
|
@@ -31712,19 +32468,6 @@ function useDepositAddress(squidRoute) {
|
|
|
31712
32468
|
});
|
|
31713
32469
|
return;
|
|
31714
32470
|
}
|
|
31715
|
-
else if (isDanalDepositRoute(route)) {
|
|
31716
|
-
const transferBody = parseDanalTransferBody(route.transactionRequest.data);
|
|
31717
|
-
if (!transferBody) {
|
|
31718
|
-
throw new Error("Could not parse Danal transfer request body");
|
|
31719
|
-
}
|
|
31720
|
-
setDeposit({
|
|
31721
|
-
amount: transferBody.amount,
|
|
31722
|
-
depositAddress: route.transactionRequest.target,
|
|
31723
|
-
statusTrackingId: transferBody.memo,
|
|
31724
|
-
memo: transferBody.memo,
|
|
31725
|
-
});
|
|
31726
|
-
return;
|
|
31727
|
-
}
|
|
31728
32471
|
else if (isDepositAddressDirectTransferRoute(route.transactionRequest)) {
|
|
31729
32472
|
// Canton case
|
|
31730
32473
|
const orderHash = route.transactionRequest.data;
|
|
@@ -31747,8 +32490,6 @@ function useDepositAddress(squidRoute) {
|
|
|
31747
32490
|
depositAddress,
|
|
31748
32491
|
closeDepositChannel,
|
|
31749
32492
|
depositData: deposit,
|
|
31750
|
-
userDepositTxId,
|
|
31751
|
-
setUserDepositTxId,
|
|
31752
32493
|
};
|
|
31753
32494
|
}
|
|
31754
32495
|
|
|
@@ -31776,7 +32517,7 @@ const useUrlSwapParams = () => {
|
|
|
31776
32517
|
|
|
31777
32518
|
const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, queryOptions, }) => {
|
|
31778
32519
|
const { evmChains, cosmosChains, suiChains, xrplChains, stellarChains } = useSquidChains(direction);
|
|
31779
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
32520
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = useSquidTokens(direction);
|
|
31780
32521
|
const { keplrTypeWallet } = useCosmosContext();
|
|
31781
32522
|
const cantonConnector = useWalletStore((store) => store.connectedWalletsByChainType[squidTypes.ChainType.CANTON]?.wallet?.connector);
|
|
31782
32523
|
const placeholderData = React.useMemo(() => {
|
|
@@ -31789,7 +32530,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31789
32530
|
[squidTypes.ChainType.XRPL]: xrplTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31790
32531
|
[squidTypes.ChainType.STELLAR]: stellarTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31791
32532
|
[squidTypes.ChainType.CANTON]: cantonTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31792
|
-
[squidTypes.ChainType.DANAL]: danalTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31793
32533
|
};
|
|
31794
32534
|
if (!chainType) {
|
|
31795
32535
|
// Return all tokens with zero balance
|
|
@@ -31812,7 +32552,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31812
32552
|
xrplTokens,
|
|
31813
32553
|
stellarTokens,
|
|
31814
32554
|
cantonTokens,
|
|
31815
|
-
danalTokens,
|
|
31816
32555
|
]);
|
|
31817
32556
|
const isQueryEnabled = React.useMemo(() => {
|
|
31818
32557
|
// Respect the queryOptions.enabled override if provided
|
|
@@ -31837,8 +32576,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31837
32576
|
return stellarTokens.length > 0;
|
|
31838
32577
|
case squidTypes.ChainType.CANTON:
|
|
31839
32578
|
return cantonTokens.length > 0;
|
|
31840
|
-
case squidTypes.ChainType.DANAL:
|
|
31841
|
-
return danalTokens.length > 0;
|
|
31842
32579
|
}
|
|
31843
32580
|
}, [
|
|
31844
32581
|
chainType,
|
|
@@ -31852,7 +32589,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31852
32589
|
xrplTokens.length,
|
|
31853
32590
|
stellarTokens.length,
|
|
31854
32591
|
cantonTokens.length,
|
|
31855
|
-
danalTokens.length,
|
|
31856
32592
|
]);
|
|
31857
32593
|
const query = reactQuery.useQuery(keys().allTokensBalance(address, chainType, direction), async () => {
|
|
31858
32594
|
// Return zero balances if no address
|
|
@@ -31977,12 +32713,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
31977
32713
|
direction,
|
|
31978
32714
|
queryOptions,
|
|
31979
32715
|
});
|
|
31980
|
-
const danalBalancesQuery = useAllTokensWithBalanceForChainType({
|
|
31981
|
-
chainType: squidTypes.ChainType.DANAL,
|
|
31982
|
-
address: connectedAddresses?.[squidTypes.ChainType.DANAL],
|
|
31983
|
-
direction,
|
|
31984
|
-
queryOptions,
|
|
31985
|
-
});
|
|
31986
32716
|
// Create a map of chain type to balance query results
|
|
31987
32717
|
const balanceQueries = React.useMemo(() => ({
|
|
31988
32718
|
[squidTypes.ChainType.EVM]: evmBalancesQuery,
|
|
@@ -31993,7 +32723,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
31993
32723
|
[squidTypes.ChainType.XRPL]: xrplBalancesQuery,
|
|
31994
32724
|
[squidTypes.ChainType.STELLAR]: stellarBalancesQuery,
|
|
31995
32725
|
[squidTypes.ChainType.CANTON]: cantonBalancesQuery,
|
|
31996
|
-
[squidTypes.ChainType.DANAL]: danalBalancesQuery,
|
|
31997
32726
|
}), [
|
|
31998
32727
|
evmBalancesQuery,
|
|
31999
32728
|
cosmosBalancesQuery,
|
|
@@ -32003,7 +32732,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
32003
32732
|
xrplBalancesQuery,
|
|
32004
32733
|
stellarBalancesQuery,
|
|
32005
32734
|
cantonBalancesQuery,
|
|
32006
|
-
danalBalancesQuery,
|
|
32007
32735
|
]);
|
|
32008
32736
|
// Combine all tokens from different chains
|
|
32009
32737
|
const allTokens = React.useMemo(() => Object.values(balanceQueries).flatMap((query) => query.data?.tokens ?? []), [balanceQueries]);
|
|
@@ -32111,8 +32839,6 @@ const useMultiChainBalance = ({ chain, token, userAddress, enabled = true, }) =>
|
|
|
32111
32839
|
return stellarBalance;
|
|
32112
32840
|
case squidTypes.ChainType.CANTON:
|
|
32113
32841
|
return cantonBalance;
|
|
32114
|
-
case squidTypes.ChainType.DANAL:
|
|
32115
|
-
return "0";
|
|
32116
32842
|
}
|
|
32117
32843
|
}, [
|
|
32118
32844
|
chain?.chainType,
|
|
@@ -32318,6 +33044,27 @@ function useSourceChainGasToken({ fromChain, fromToken, }) {
|
|
|
32318
33044
|
normalizeFee: convertTempoFeeToStablecoinUnits,
|
|
32319
33045
|
};
|
|
32320
33046
|
}
|
|
33047
|
+
if (isArcChain(fromChain.chainId)) {
|
|
33048
|
+
const isUsdc = isArcUsdcToken(fromToken);
|
|
33049
|
+
const isNative = isArcNativeToken(fromToken);
|
|
33050
|
+
if (fromToken.chainId !== fromChain.chainId ||
|
|
33051
|
+
nativeBalance.decimals !== 18 ||
|
|
33052
|
+
(isUsdc && fromToken.decimals !== 6) ||
|
|
33053
|
+
(isNative && fromToken.decimals !== 18)) {
|
|
33054
|
+
return null;
|
|
33055
|
+
}
|
|
33056
|
+
return {
|
|
33057
|
+
fromTokenPaysGasFees: isUsdc || isNative,
|
|
33058
|
+
gasTokenBalanceWei: nativeBalance.value,
|
|
33059
|
+
normalizeFee: (fee) => fee,
|
|
33060
|
+
fromAmountToGasUnits: isUsdc
|
|
33061
|
+
? (amount) => amount * 10n ** 12n
|
|
33062
|
+
: undefined,
|
|
33063
|
+
gasUnitsToFromAmount: isUsdc
|
|
33064
|
+
? (amount) => amount / 10n ** 12n
|
|
33065
|
+
: undefined,
|
|
33066
|
+
};
|
|
33067
|
+
}
|
|
32321
33068
|
return {
|
|
32322
33069
|
fromTokenPaysGasFees: fromToken.address.toLowerCase() ===
|
|
32323
33070
|
chainTypeToNativeTokenAddressMap[fromToken.type].toLowerCase(),
|
|
@@ -32325,10 +33072,10 @@ function useSourceChainGasToken({ fromChain, fromToken, }) {
|
|
|
32325
33072
|
normalizeFee: (fee) => fee,
|
|
32326
33073
|
};
|
|
32327
33074
|
}, [
|
|
32328
|
-
fromToken
|
|
32329
|
-
fromToken?.type,
|
|
33075
|
+
fromToken,
|
|
32330
33076
|
fromChain?.chainId,
|
|
32331
33077
|
nativeBalance?.value,
|
|
33078
|
+
nativeBalance?.decimals,
|
|
32332
33079
|
tempoFeeData,
|
|
32333
33080
|
]);
|
|
32334
33081
|
/**
|
|
@@ -32343,9 +33090,20 @@ function useSourceChainGasToken({ fromChain, fromToken, }) {
|
|
|
32343
33090
|
return evmTokens.find((t) => t.chainId === fromChain.chainId &&
|
|
32344
33091
|
t.address.toLowerCase() === tempoFeeData.gasTokenAddress.toLowerCase());
|
|
32345
33092
|
}
|
|
33093
|
+
if (isArcChain(fromChain.chainId)) {
|
|
33094
|
+
return nativeToken?.decimals === 18
|
|
33095
|
+
? {
|
|
33096
|
+
...nativeToken,
|
|
33097
|
+
symbol: fromChain.nativeCurrency.symbol,
|
|
33098
|
+
name: fromChain.nativeCurrency.name,
|
|
33099
|
+
}
|
|
33100
|
+
: undefined;
|
|
33101
|
+
}
|
|
32346
33102
|
return nativeToken;
|
|
32347
33103
|
}, [
|
|
32348
33104
|
fromChain?.chainId,
|
|
33105
|
+
fromChain?.nativeCurrency.symbol,
|
|
33106
|
+
fromChain?.nativeCurrency.name,
|
|
32349
33107
|
fromToken?.address,
|
|
32350
33108
|
nativeToken,
|
|
32351
33109
|
tempoFeeData,
|
|
@@ -32504,22 +33262,57 @@ const calculateTotalNativeFees = ({ expressFeeCost, firstFeeCost, firstGasCost,
|
|
|
32504
33262
|
(sameTokenBetweenFees
|
|
32505
33263
|
? BigInt(firstFeeCost?.amount ?? "0") + BigInt(firstGasCost?.amount ?? "0")
|
|
32506
33264
|
: BigInt(firstGasCost?.amount ?? "0"));
|
|
33265
|
+
const addGasReserve = (fee) => (fee * 110n + 99n) / 100n;
|
|
33266
|
+
const calculateArcGasCosts = (gasCosts) => {
|
|
33267
|
+
const sourceGasCosts = gasCosts.filter((cost) => isArcChain(cost.token.chainId));
|
|
33268
|
+
if (sourceGasCosts.length === 0)
|
|
33269
|
+
return undefined;
|
|
33270
|
+
let total = 0n;
|
|
33271
|
+
for (const { amount, token } of sourceGasCosts) {
|
|
33272
|
+
const fee = BigInt(amount);
|
|
33273
|
+
if (fee <= 0n)
|
|
33274
|
+
return undefined;
|
|
33275
|
+
if (isArcUsdcToken(token) && token.decimals === 6) {
|
|
33276
|
+
total += fee * 10n ** 12n;
|
|
33277
|
+
}
|
|
33278
|
+
else if (isArcNativeToken(token) && token.decimals === 18) {
|
|
33279
|
+
total += fee;
|
|
33280
|
+
}
|
|
33281
|
+
else {
|
|
33282
|
+
return undefined;
|
|
33283
|
+
}
|
|
33284
|
+
}
|
|
33285
|
+
return total;
|
|
33286
|
+
};
|
|
33287
|
+
const calculateTotalGasBalanceNeeded = ({ fromAmount, networkFeesWei, chainFeeParams, }) => {
|
|
33288
|
+
if (!chainFeeParams || networkFeesWei == null)
|
|
33289
|
+
return undefined;
|
|
33290
|
+
if (!chainFeeParams.fromTokenPaysGasFees)
|
|
33291
|
+
return networkFeesWei;
|
|
33292
|
+
const amount = BigInt(fromAmount ?? "0");
|
|
33293
|
+
const amountInGasUnits = chainFeeParams.fromAmountToGasUnits?.(amount) ?? amount;
|
|
33294
|
+
return amountInGasUnits + networkFeesWei;
|
|
33295
|
+
};
|
|
32507
33296
|
const checkIsGasBalanceEnough = ({ fromAmount, networkFeesWei, chainFeeParams, }) => {
|
|
32508
33297
|
if (!chainFeeParams)
|
|
32509
33298
|
return false;
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
|
|
32513
|
-
|
|
32514
|
-
|
|
33299
|
+
const total = calculateTotalGasBalanceNeeded({
|
|
33300
|
+
fromAmount,
|
|
33301
|
+
networkFeesWei,
|
|
33302
|
+
chainFeeParams,
|
|
33303
|
+
});
|
|
33304
|
+
return total != null && total <= chainFeeParams.gasTokenBalanceWei;
|
|
32515
33305
|
};
|
|
32516
33306
|
const calculateMinAmountValueWarnMsg = ({ networkFeesWei, chainFeeParams, fromTokenDecimals, }) => {
|
|
32517
|
-
if (!chainFeeParams?.fromTokenPaysGasFees)
|
|
33307
|
+
if (!chainFeeParams?.fromTokenPaysGasFees || networkFeesWei == null)
|
|
32518
33308
|
return undefined;
|
|
32519
33309
|
const minAmount = chainFeeParams.gasTokenBalanceWei - networkFeesWei;
|
|
32520
33310
|
if (minAmount <= BigInt(0))
|
|
32521
33311
|
return undefined;
|
|
32522
|
-
|
|
33312
|
+
const amountInTokenUnits = chainFeeParams.gasUnitsToFromAmount?.(minAmount) ?? minAmount;
|
|
33313
|
+
if (amountInTokenUnits <= 0n)
|
|
33314
|
+
return undefined;
|
|
33315
|
+
return formatBNToReadable(amountInTokenUnits, fromTokenDecimals);
|
|
32523
33316
|
};
|
|
32524
33317
|
/**
|
|
32525
33318
|
* Calculates the estimated duration of a route
|
|
@@ -32568,14 +33361,22 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
|
|
|
32568
33361
|
const expectedGasRefundCostUSD = convertTokenAmountToUSD(formatBNToReadable(expectedGasRefundCost, firstFeeCost?.token.decimals ?? 18), firstFeeCost?.token.usdPrice ?? "0");
|
|
32569
33362
|
const sameTokenBetweenFees = firstFeeCost?.token.address === firstGasCost?.token.address &&
|
|
32570
33363
|
firstFeeCost?.token.chainId === firstGasCost?.token.chainId;
|
|
32571
|
-
const totalNativeFees =
|
|
32572
|
-
|
|
32573
|
-
|
|
32574
|
-
|
|
32575
|
-
|
|
32576
|
-
|
|
32577
|
-
|
|
32578
|
-
|
|
33364
|
+
const totalNativeFees = isArcChain(fromChain?.chainId)
|
|
33365
|
+
? calculateArcGasCosts(allGasCosts)
|
|
33366
|
+
: calculateTotalNativeFees({
|
|
33367
|
+
expressFeeCost,
|
|
33368
|
+
firstFeeCost,
|
|
33369
|
+
firstGasCost,
|
|
33370
|
+
sameTokenBetweenFees,
|
|
33371
|
+
});
|
|
33372
|
+
const totalFeesInNativeTokenPlusRatio = totalNativeFees == null
|
|
33373
|
+
? undefined
|
|
33374
|
+
: isArcChain(fromChain?.chainId)
|
|
33375
|
+
? addGasReserve(totalNativeFees)
|
|
33376
|
+
: (totalNativeFees * BigInt(110)) / BigInt(100);
|
|
33377
|
+
const networkFeesWei = totalFeesInNativeTokenPlusRatio == null
|
|
33378
|
+
? undefined
|
|
33379
|
+
: chainFeeParams?.normalizeFee(totalFeesInNativeTokenPlusRatio);
|
|
32579
33380
|
const isGasBalanceEnough = checkIsGasBalanceEnough({
|
|
32580
33381
|
chainFeeParams,
|
|
32581
33382
|
fromAmount,
|
|
@@ -32591,10 +33392,12 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
|
|
|
32591
33392
|
estimatedRouteDuration: squidRoute?.estimate?.estimatedRouteDuration,
|
|
32592
33393
|
isSingleChainRoute,
|
|
32593
33394
|
});
|
|
32594
|
-
|
|
32595
|
-
|
|
32596
|
-
|
|
32597
|
-
|
|
33395
|
+
const totalGasBalanceNeeded = calculateTotalGasBalanceNeeded({
|
|
33396
|
+
fromAmount,
|
|
33397
|
+
networkFeesWei,
|
|
33398
|
+
chainFeeParams,
|
|
33399
|
+
});
|
|
33400
|
+
const gasBalanceNeeded = gasToken && totalGasBalanceNeeded != null
|
|
32598
33401
|
? formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals)
|
|
32599
33402
|
: undefined;
|
|
32600
33403
|
return {
|
|
@@ -32616,7 +33419,7 @@ const calculateEstimateResults = ({ squidRoute, tokens, fromChain, toChain, coll
|
|
|
32616
33419
|
expectedGasRefundCostUSD,
|
|
32617
33420
|
sameTokenBetweenFees,
|
|
32618
33421
|
fromTokenPaysGasFees: chainFeeParams?.fromTokenPaysGasFees ?? false,
|
|
32619
|
-
totalNativeFees,
|
|
33422
|
+
totalNativeFees: totalNativeFees ?? 0n,
|
|
32620
33423
|
isGasBalanceEnough,
|
|
32621
33424
|
minAmountValueWarnMsg,
|
|
32622
33425
|
estimatedRouteDuration,
|
|
@@ -32665,8 +33468,12 @@ function useSendTransactionGas({ chain, token, from, }) {
|
|
|
32665
33468
|
case squidTypes.ChainType.EVM: {
|
|
32666
33469
|
const client = await getClient(chain);
|
|
32667
33470
|
const feeData = await client.getFeeData();
|
|
32668
|
-
if (!feeData.maxFeePerGas)
|
|
33471
|
+
if (!feeData.maxFeePerGas) {
|
|
33472
|
+
if (isArcChain(chain.chainId)) {
|
|
33473
|
+
throw new Error("Gas price is unavailable");
|
|
33474
|
+
}
|
|
32669
33475
|
return BigInt(0);
|
|
33476
|
+
}
|
|
32670
33477
|
let maxFeePerGas = feeData.maxFeePerGas;
|
|
32671
33478
|
if (token.chainId === CHAIN_IDS.HEDERA) {
|
|
32672
33479
|
maxFeePerGas = scaleWeiToHbar(BigInt(feeData.maxFeePerGas));
|
|
@@ -32686,6 +33493,9 @@ function useSendTransactionGas({ chain, token, from, }) {
|
|
|
32686
33493
|
value: 1,
|
|
32687
33494
|
chainId: chain.chainId,
|
|
32688
33495
|
});
|
|
33496
|
+
if (isArcChain(chain.chainId) && gas <= 0n) {
|
|
33497
|
+
throw new Error("Gas estimate is unavailable");
|
|
33498
|
+
}
|
|
32689
33499
|
return gas * maxFeePerGas;
|
|
32690
33500
|
}
|
|
32691
33501
|
const erc20Interface = new ethers.Interface(viem.erc20Abi);
|
|
@@ -32700,6 +33510,9 @@ function useSendTransactionGas({ chain, token, from, }) {
|
|
|
32700
33510
|
chainId: chain.chainId,
|
|
32701
33511
|
});
|
|
32702
33512
|
const feeWei = gas * maxFeePerGas;
|
|
33513
|
+
if (isArcChain(chain.chainId) && feeWei <= 0n) {
|
|
33514
|
+
throw new Error("Gas estimate is unavailable");
|
|
33515
|
+
}
|
|
32703
33516
|
// Tempo gas fees are denominated 18 units,
|
|
32704
33517
|
// this value needs to be scaled to 6 decimals
|
|
32705
33518
|
if (chain.chainId === CHAIN_IDS.TEMPO) {
|
|
@@ -32747,35 +33560,45 @@ function useSendTransactionGas({ chain, token, from, }) {
|
|
|
32747
33560
|
}
|
|
32748
33561
|
|
|
32749
33562
|
function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
|
|
32750
|
-
const { data
|
|
33563
|
+
const { data, isLoading, isFetching, isError, refetch } = useSendTransactionGas({
|
|
32751
33564
|
chain,
|
|
32752
33565
|
token,
|
|
32753
33566
|
from,
|
|
32754
33567
|
});
|
|
33568
|
+
const estimatedGas = isError || isFetching ? undefined : data;
|
|
33569
|
+
const networkFeesWei = estimatedGas == null
|
|
33570
|
+
? undefined
|
|
33571
|
+
: isArcChain(chain?.chainId)
|
|
33572
|
+
? addGasReserve(estimatedGas)
|
|
33573
|
+
: estimatedGas;
|
|
32755
33574
|
const { chainFeeParams, gasToken } = useSourceChainGasToken({
|
|
32756
33575
|
fromChain: chain,
|
|
32757
33576
|
fromToken: token,
|
|
32758
33577
|
});
|
|
32759
33578
|
const isGasBalanceEnough = React.useMemo(() => {
|
|
32760
|
-
if (!amount ||
|
|
33579
|
+
if (!amount || token?.decimals == null || !gasToken)
|
|
32761
33580
|
return false;
|
|
32762
33581
|
return checkIsGasBalanceEnough({
|
|
32763
33582
|
fromAmount: parseToBigInt(amount, token.decimals).toString(),
|
|
32764
|
-
networkFeesWei
|
|
33583
|
+
networkFeesWei,
|
|
32765
33584
|
chainFeeParams,
|
|
32766
33585
|
});
|
|
32767
|
-
}, [amount,
|
|
33586
|
+
}, [amount, networkFeesWei, token, chainFeeParams, gasToken]);
|
|
32768
33587
|
const gasBalanceNeeded = React.useMemo(() => {
|
|
32769
|
-
if (!amount ||
|
|
33588
|
+
if (!amount || token?.decimals == null || gasToken?.decimals == null)
|
|
33589
|
+
return undefined;
|
|
33590
|
+
const totalGasBalanceNeeded = calculateTotalGasBalanceNeeded({
|
|
33591
|
+
fromAmount: parseToBigInt(amount, token.decimals).toString(),
|
|
33592
|
+
networkFeesWei,
|
|
33593
|
+
chainFeeParams,
|
|
33594
|
+
});
|
|
33595
|
+
if (totalGasBalanceNeeded == null)
|
|
32770
33596
|
return undefined;
|
|
32771
|
-
// gas fees + fromAmount (if fromToken is gas token)
|
|
32772
|
-
const totalGasBalanceNeeded = estimatedGas +
|
|
32773
|
-
parseToBigInt(chainFeeParams?.fromTokenPaysGasFees ? (amount ?? "0") : "0", token.decimals);
|
|
32774
33597
|
return formatBNToReadable(totalGasBalanceNeeded, gasToken.decimals);
|
|
32775
33598
|
}, [
|
|
32776
33599
|
amount,
|
|
32777
|
-
chainFeeParams
|
|
32778
|
-
|
|
33600
|
+
chainFeeParams,
|
|
33601
|
+
networkFeesWei,
|
|
32779
33602
|
gasToken?.decimals,
|
|
32780
33603
|
token?.decimals,
|
|
32781
33604
|
]);
|
|
@@ -32786,20 +33609,22 @@ function useEstimateSendTransaction({ chain, token, amount, balance, from, }) {
|
|
|
32786
33609
|
parseToBigInt(amount, token.decimals));
|
|
32787
33610
|
}, [amount, balance, token?.decimals]);
|
|
32788
33611
|
const minAmountValueWarnMsg = React.useMemo(() => {
|
|
32789
|
-
if (!token?.address || !
|
|
33612
|
+
if (!token?.address || !gasToken || !chainFeeParams)
|
|
32790
33613
|
return undefined;
|
|
32791
33614
|
return calculateMinAmountValueWarnMsg({
|
|
32792
33615
|
chainFeeParams,
|
|
32793
|
-
networkFeesWei
|
|
33616
|
+
networkFeesWei,
|
|
32794
33617
|
fromTokenDecimals: token.decimals,
|
|
32795
33618
|
});
|
|
32796
|
-
}, [
|
|
33619
|
+
}, [networkFeesWei, token, chainFeeParams, gasToken]);
|
|
32797
33620
|
return {
|
|
32798
33621
|
estimatedGas,
|
|
32799
33622
|
gasBalanceNeeded,
|
|
32800
33623
|
isBalanceEnough,
|
|
32801
33624
|
tokenPaysGasFees: chainFeeParams?.fromTokenPaysGasFees ?? false,
|
|
32802
|
-
isLoading,
|
|
33625
|
+
isLoading: isLoading || isFetching,
|
|
33626
|
+
isError,
|
|
33627
|
+
refetchGasEstimate: refetch,
|
|
32803
33628
|
isGasBalanceEnough,
|
|
32804
33629
|
minAmountValueWarnMsg,
|
|
32805
33630
|
};
|
|
@@ -33222,9 +34047,8 @@ function useSendTransaction({ to, amount, token, chain, }) {
|
|
|
33222
34047
|
chain,
|
|
33223
34048
|
});
|
|
33224
34049
|
break;
|
|
33225
|
-
case squidTypes.ChainType.CANTON:
|
|
33226
|
-
|
|
33227
|
-
throw new Error(`Send not implemented for chain type ${chain.chainType}`);
|
|
34050
|
+
case squidTypes.ChainType.CANTON: {
|
|
34051
|
+
throw new Error("Not implemented");
|
|
33228
34052
|
}
|
|
33229
34053
|
}
|
|
33230
34054
|
return {
|
|
@@ -33430,8 +34254,7 @@ async function getSendTransactionStatus({ chain, txHash, }) {
|
|
|
33430
34254
|
chain,
|
|
33431
34255
|
});
|
|
33432
34256
|
case squidTypes.ChainType.CANTON:
|
|
33433
|
-
|
|
33434
|
-
throw new Error(`Send not implemented for chain type ${chain.chainType}`);
|
|
34257
|
+
throw new Error("Not implemented");
|
|
33435
34258
|
}
|
|
33436
34259
|
}
|
|
33437
34260
|
|
|
@@ -33702,6 +34525,19 @@ const useApproval = ({ squidRoute, }) => {
|
|
|
33702
34525
|
const { getChainType } = useSquidChains();
|
|
33703
34526
|
const { switchChainAsync } = wagmi.useSwitchChain();
|
|
33704
34527
|
const { connectedAddress: { address: sourceUserAddress }, } = useMultiChainWallet(fromChain);
|
|
34528
|
+
const arcNativeBalanceQuery = wagmi.useBalance({
|
|
34529
|
+
address: sourceUserAddress,
|
|
34530
|
+
chainId: Number(fromChain?.chainId),
|
|
34531
|
+
query: { enabled: false },
|
|
34532
|
+
});
|
|
34533
|
+
const arcTokenBalanceQuery = wagmi.useReadContract({
|
|
34534
|
+
address: fromToken?.address,
|
|
34535
|
+
abi: viem.erc20Abi,
|
|
34536
|
+
functionName: "balanceOf",
|
|
34537
|
+
args: [sourceUserAddress],
|
|
34538
|
+
chainId: Number(fromChain?.chainId),
|
|
34539
|
+
query: { enabled: false },
|
|
34540
|
+
});
|
|
33705
34541
|
const isSagaEvmSameChainSwap = isSameChain &&
|
|
33706
34542
|
fromChain?.chainId.toString() === CHAIN_IDS.SAGA_EVM.toString();
|
|
33707
34543
|
// Swaps on SagaEVM are regular EVM transactions, following the standard ERC20 approve/allowance mechanism.
|
|
@@ -33862,13 +34698,20 @@ const useApproval = ({ squidRoute, }) => {
|
|
|
33862
34698
|
return false;
|
|
33863
34699
|
}
|
|
33864
34700
|
}, {
|
|
33865
|
-
onSuccess: async () => {
|
|
34701
|
+
onSuccess: async (approved) => {
|
|
34702
|
+
if (approved && isArcChain(fromChain?.chainId)) {
|
|
34703
|
+
await Promise.all([
|
|
34704
|
+
arcNativeBalanceQuery.refetch({ throwOnError: true }),
|
|
34705
|
+
arcTokenBalanceQuery.refetch({ throwOnError: true }),
|
|
34706
|
+
]);
|
|
34707
|
+
queryClient.invalidateQueries(getPrefixKey(exports.QueryKeys.AllTokensBalance));
|
|
34708
|
+
}
|
|
33866
34709
|
await allowanceQuery?.refetch();
|
|
33867
34710
|
queryClient.invalidateQueries(getPrefixKey(exports.QueryKeys.RouteApproved));
|
|
33868
34711
|
// After an approval, we refetch the transaction query with all required parameters
|
|
33869
34712
|
// This is to ensure we're using the latest expiry timestamp
|
|
33870
34713
|
if (squidRoute) {
|
|
33871
|
-
queryClient.refetchQueries({
|
|
34714
|
+
await queryClient.refetchQueries({
|
|
33872
34715
|
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,
|
|
33873
34716
|
// TODO: update types
|
|
33874
34717
|
squidRoute.params?.overrideGasRefundAddress),
|
|
@@ -37052,342 +37895,6 @@ coin.DecProto = {
|
|
|
37052
37895
|
|
|
37053
37896
|
} (tx));
|
|
37054
37897
|
|
|
37055
|
-
const vParty = (party) => ({ party });
|
|
37056
|
-
const vText = (text) => ({ text });
|
|
37057
|
-
const vNumeric = (numeric) => ({ numeric });
|
|
37058
|
-
const vContractId = (contractId) => ({
|
|
37059
|
-
contractId,
|
|
37060
|
-
});
|
|
37061
|
-
const vList = (elements) => ({
|
|
37062
|
-
list: { elements },
|
|
37063
|
-
});
|
|
37064
|
-
const vRecord = (fields) => ({
|
|
37065
|
-
record: { fields: fields.map(([label, value]) => ({ label, value })) },
|
|
37066
|
-
});
|
|
37067
|
-
const vTextMap = (entries) => ({ textMap: { entries } });
|
|
37068
|
-
const vTimestamp = (isoString) => ({
|
|
37069
|
-
timestamp: String(Math.floor(new Date(isoString).getTime() * 1000)),
|
|
37070
|
-
});
|
|
37071
|
-
function isRecord(value) {
|
|
37072
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37073
|
-
}
|
|
37074
|
-
function normalizeChoiceContextValue(value) {
|
|
37075
|
-
if (typeof value === "string")
|
|
37076
|
-
return value;
|
|
37077
|
-
if (!isRecord(value) || typeof value.tag !== "string")
|
|
37078
|
-
return null;
|
|
37079
|
-
switch (value.tag) {
|
|
37080
|
-
case "AV_Text":
|
|
37081
|
-
case "AV_ContractId":
|
|
37082
|
-
return typeof value.value === "string"
|
|
37083
|
-
? { tag: value.tag, value: value.value }
|
|
37084
|
-
: null;
|
|
37085
|
-
case "AV_Bool":
|
|
37086
|
-
return typeof value.value === "boolean"
|
|
37087
|
-
? { tag: value.tag, value: value.value }
|
|
37088
|
-
: null;
|
|
37089
|
-
case "AV_List":
|
|
37090
|
-
if (!Array.isArray(value.value))
|
|
37091
|
-
return null;
|
|
37092
|
-
return {
|
|
37093
|
-
tag: value.tag,
|
|
37094
|
-
value: value.value.flatMap((entry) => {
|
|
37095
|
-
const normalized = normalizeChoiceContextValue(entry);
|
|
37096
|
-
return normalized ? [normalized] : [];
|
|
37097
|
-
}),
|
|
37098
|
-
};
|
|
37099
|
-
default: {
|
|
37100
|
-
const inner = value.value;
|
|
37101
|
-
if (typeof inner === "string" ||
|
|
37102
|
-
typeof inner === "number" ||
|
|
37103
|
-
typeof inner === "boolean" ||
|
|
37104
|
-
inner === null) {
|
|
37105
|
-
return { tag: value.tag, value: inner };
|
|
37106
|
-
}
|
|
37107
|
-
if (Array.isArray(inner)) {
|
|
37108
|
-
return {
|
|
37109
|
-
tag: value.tag,
|
|
37110
|
-
value: inner.flatMap((entry) => {
|
|
37111
|
-
const normalized = normalizeChoiceContextValue(entry);
|
|
37112
|
-
return normalized ? [normalized] : [];
|
|
37113
|
-
}),
|
|
37114
|
-
};
|
|
37115
|
-
}
|
|
37116
|
-
if (!isRecord(inner))
|
|
37117
|
-
return null;
|
|
37118
|
-
const normalizedObject = {};
|
|
37119
|
-
for (const [key, entry] of Object.entries(inner)) {
|
|
37120
|
-
if (typeof entry !== "string" &&
|
|
37121
|
-
typeof entry !== "number" &&
|
|
37122
|
-
typeof entry !== "boolean" &&
|
|
37123
|
-
entry !== null) {
|
|
37124
|
-
return null;
|
|
37125
|
-
}
|
|
37126
|
-
normalizedObject[key] = entry;
|
|
37127
|
-
}
|
|
37128
|
-
return { tag: value.tag, value: normalizedObject };
|
|
37129
|
-
}
|
|
37130
|
-
}
|
|
37131
|
-
}
|
|
37132
|
-
function stringifyChoiceContextValue(value) {
|
|
37133
|
-
return typeof value === "string" ? value : String(value);
|
|
37134
|
-
}
|
|
37135
|
-
function choiceContextValueToAnyValue(value) {
|
|
37136
|
-
if (typeof value === "string") {
|
|
37137
|
-
return {
|
|
37138
|
-
variant: { constructor: "AV_Text", value: vText(value) },
|
|
37139
|
-
};
|
|
37140
|
-
}
|
|
37141
|
-
switch (value.tag) {
|
|
37142
|
-
case "AV_ContractId":
|
|
37143
|
-
if (typeof value.value !== "string") {
|
|
37144
|
-
throw new TypeError("AV_ContractId values must be strings");
|
|
37145
|
-
}
|
|
37146
|
-
return {
|
|
37147
|
-
variant: { constructor: value.tag, value: vContractId(value.value) },
|
|
37148
|
-
};
|
|
37149
|
-
case "AV_Bool":
|
|
37150
|
-
if (typeof value.value !== "boolean") {
|
|
37151
|
-
throw new TypeError("AV_Bool values must be booleans");
|
|
37152
|
-
}
|
|
37153
|
-
return {
|
|
37154
|
-
variant: { constructor: value.tag, value: { bool: value.value } },
|
|
37155
|
-
};
|
|
37156
|
-
case "AV_List":
|
|
37157
|
-
if (!Array.isArray(value.value)) {
|
|
37158
|
-
throw new TypeError("AV_List values must be arrays");
|
|
37159
|
-
}
|
|
37160
|
-
return {
|
|
37161
|
-
variant: {
|
|
37162
|
-
constructor: value.tag,
|
|
37163
|
-
value: vList(value.value.map(choiceContextValueToAnyValue)),
|
|
37164
|
-
},
|
|
37165
|
-
};
|
|
37166
|
-
default:
|
|
37167
|
-
return {
|
|
37168
|
-
variant: {
|
|
37169
|
-
constructor: value.tag,
|
|
37170
|
-
value: vText(stringifyChoiceContextValue(value.value)),
|
|
37171
|
-
},
|
|
37172
|
-
};
|
|
37173
|
-
}
|
|
37174
|
-
}
|
|
37175
|
-
|
|
37176
|
-
/** Metadata key used by Splice token-standard transfers to carry our Squid orderhash memo. */
|
|
37177
|
-
const CANTON_MEMO_KEY = "splice.lfdecentralizedtrust.org/reason";
|
|
37178
|
-
/** Interface id for the transfer factory contract returned by the token registry. */
|
|
37179
|
-
const TRANSFER_FACTORY_INTERFACE_ID = "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory";
|
|
37180
|
-
/** Choice exercised on the transfer factory to create the token-standard transfer. */
|
|
37181
|
-
const TRANSFER_FACTORY_CHOICE = "TransferFactory_Transfer";
|
|
37182
|
-
/** CIP-056 (non-Amulet) tokens expose a registrar parameterized by admin party. */
|
|
37183
|
-
const CIP056_REGISTRAR_BASE = "https://api.utilities.digitalasset.com/api/token-standard/v0/registrars";
|
|
37184
|
-
const TRANSFER_REQUESTED_AT_SKEW_MS = 60_000;
|
|
37185
|
-
const TRANSFER_EXECUTE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
37186
|
-
function resolveCantonRegistryUrl(instrument) {
|
|
37187
|
-
if (instrument.id === CANTON_AMULET_INSTRUMENT_ID) {
|
|
37188
|
-
return CANTON_AMULET_REGISTRY_URL;
|
|
37189
|
-
}
|
|
37190
|
-
return `${CIP056_REGISTRAR_BASE}/${encodeURIComponent(instrument.admin)}`;
|
|
37191
|
-
}
|
|
37192
|
-
/**
|
|
37193
|
-
* Build the plain JSON arguments expected by the registry transfer-factory
|
|
37194
|
-
* endpoint before it adds choice context and disclosed contracts.
|
|
37195
|
-
*/
|
|
37196
|
-
function buildTransferChoiceArgs({ sender, receiver, amount, instrument, inputHoldingCids, memo, nowMs, }) {
|
|
37197
|
-
return {
|
|
37198
|
-
expectedAdmin: instrument.admin,
|
|
37199
|
-
transfer: {
|
|
37200
|
-
sender,
|
|
37201
|
-
receiver,
|
|
37202
|
-
amount,
|
|
37203
|
-
instrumentId: { admin: instrument.admin, id: instrument.id },
|
|
37204
|
-
requestedAt: new Date(nowMs - TRANSFER_REQUESTED_AT_SKEW_MS).toISOString(),
|
|
37205
|
-
executeBefore: new Date(nowMs + TRANSFER_EXECUTE_WINDOW_MS).toISOString(),
|
|
37206
|
-
inputHoldingCids,
|
|
37207
|
-
meta: { values: { [CANTON_MEMO_KEY]: memo } },
|
|
37208
|
-
},
|
|
37209
|
-
extraArgs: { context: { values: {} }, meta: { values: {} } },
|
|
37210
|
-
};
|
|
37211
|
-
}
|
|
37212
|
-
/**
|
|
37213
|
-
* Canton's Holding contracts follow Bitcoin-style UTXOs model.
|
|
37214
|
-
*
|
|
37215
|
-
* Pick Holding contract ids covering `amount` for the instrument: prefer a
|
|
37216
|
-
* single exact-amount holding, else accumulate largest-first.
|
|
37217
|
-
*/
|
|
37218
|
-
function selectInputHoldingCids(holdings, instrument, amount) {
|
|
37219
|
-
const spendableHoldings = holdings.filter((h) => !h.locked &&
|
|
37220
|
-
h.instrument.admin === instrument.admin &&
|
|
37221
|
-
h.instrument.id === instrument.id);
|
|
37222
|
-
const target = new BigNumber(amount);
|
|
37223
|
-
const exact = spendableHoldings.find((h) => new BigNumber(h.amount).eq(target));
|
|
37224
|
-
if (exact)
|
|
37225
|
-
return [exact.contractId];
|
|
37226
|
-
const sorted = [...spendableHoldings].sort((a, b) => new BigNumber(b.amount).comparedTo(new BigNumber(a.amount)));
|
|
37227
|
-
const selected = [];
|
|
37228
|
-
let sum = new BigNumber(0);
|
|
37229
|
-
for (const holding of sorted) {
|
|
37230
|
-
selected.push(holding.contractId);
|
|
37231
|
-
sum = sum.plus(holding.amount);
|
|
37232
|
-
if (sum.gte(target))
|
|
37233
|
-
break;
|
|
37234
|
-
}
|
|
37235
|
-
if (sum.lt(target)) {
|
|
37236
|
-
throw new Error("Insufficient Canton holdings to cover transfer amount");
|
|
37237
|
-
}
|
|
37238
|
-
return selected;
|
|
37239
|
-
}
|
|
37240
|
-
async function fetchInputHoldingCids(connector, party, instrument, amount) {
|
|
37241
|
-
const ledgerEnd = await connector.ledgerApi({
|
|
37242
|
-
requestMethod: "get",
|
|
37243
|
-
resource: "/v2/state/ledger-end",
|
|
37244
|
-
});
|
|
37245
|
-
const activeContracts = await connector.ledgerApi({
|
|
37246
|
-
requestMethod: "post",
|
|
37247
|
-
resource: "/v2/state/active-contracts",
|
|
37248
|
-
body: buildHoldingsAcsRequestBody(party, String(ledgerEnd.offset)),
|
|
37249
|
-
});
|
|
37250
|
-
return selectInputHoldingCids(parseCantonHoldings(activeContracts), instrument, amount);
|
|
37251
|
-
}
|
|
37252
|
-
/**
|
|
37253
|
-
* Ask the token registry for the transfer factory contract and extra context
|
|
37254
|
-
* required to exercise its transfer choice.
|
|
37255
|
-
*/
|
|
37256
|
-
async function fetchTransferFactory(registryUrl, choiceArgs) {
|
|
37257
|
-
const response = await fetch(`${registryUrl}/registry/transfer-instruction/v1/transfer-factory`, {
|
|
37258
|
-
method: "POST",
|
|
37259
|
-
headers: { "Content-Type": "application/json" },
|
|
37260
|
-
body: JSON.stringify({
|
|
37261
|
-
choiceArguments: choiceArgs,
|
|
37262
|
-
excludeDebugFields: true,
|
|
37263
|
-
}),
|
|
37264
|
-
});
|
|
37265
|
-
if (!response.ok) {
|
|
37266
|
-
throw new Error(`Canton transfer-factory request failed (${response.status})`);
|
|
37267
|
-
}
|
|
37268
|
-
return response.json();
|
|
37269
|
-
}
|
|
37270
|
-
/**
|
|
37271
|
-
* Convert transfer choice args into strict Daml Value JSON for the wallet's
|
|
37272
|
-
* prepareExecute ExerciseCommand.
|
|
37273
|
-
*/
|
|
37274
|
-
function buildPrepareChoiceArgument(choiceArgs) {
|
|
37275
|
-
const contextValues = Object.entries(choiceArgs.extraArgs.context?.values ?? {})
|
|
37276
|
-
.map(([key, value]) => {
|
|
37277
|
-
const normalized = normalizeChoiceContextValue(value);
|
|
37278
|
-
return normalized
|
|
37279
|
-
? { key, value: choiceContextValueToAnyValue(normalized) }
|
|
37280
|
-
: undefined;
|
|
37281
|
-
})
|
|
37282
|
-
.filter((entry) => Boolean(entry));
|
|
37283
|
-
return vRecord([
|
|
37284
|
-
["expectedAdmin", vParty(choiceArgs.expectedAdmin)],
|
|
37285
|
-
[
|
|
37286
|
-
"transfer",
|
|
37287
|
-
vRecord([
|
|
37288
|
-
["sender", vParty(choiceArgs.transfer.sender)],
|
|
37289
|
-
["receiver", vParty(choiceArgs.transfer.receiver)],
|
|
37290
|
-
["amount", vNumeric(choiceArgs.transfer.amount)],
|
|
37291
|
-
[
|
|
37292
|
-
"instrumentId",
|
|
37293
|
-
vRecord([
|
|
37294
|
-
["admin", vParty(choiceArgs.transfer.instrumentId.admin)],
|
|
37295
|
-
["id", vText(choiceArgs.transfer.instrumentId.id)],
|
|
37296
|
-
]),
|
|
37297
|
-
],
|
|
37298
|
-
["requestedAt", vTimestamp(choiceArgs.transfer.requestedAt)],
|
|
37299
|
-
["executeBefore", vTimestamp(choiceArgs.transfer.executeBefore)],
|
|
37300
|
-
[
|
|
37301
|
-
"inputHoldingCids",
|
|
37302
|
-
vList(choiceArgs.transfer.inputHoldingCids.map(vContractId)),
|
|
37303
|
-
],
|
|
37304
|
-
[
|
|
37305
|
-
"meta",
|
|
37306
|
-
vRecord([
|
|
37307
|
-
[
|
|
37308
|
-
"values",
|
|
37309
|
-
vTextMap(Object.entries(choiceArgs.transfer.meta.values).map(([key, value]) => ({ key, value: vText(value) }))),
|
|
37310
|
-
],
|
|
37311
|
-
]),
|
|
37312
|
-
],
|
|
37313
|
-
]),
|
|
37314
|
-
],
|
|
37315
|
-
[
|
|
37316
|
-
"extraArgs",
|
|
37317
|
-
vRecord([
|
|
37318
|
-
["context", vRecord([["values", vTextMap(contextValues)]])],
|
|
37319
|
-
["meta", vRecord([["values", vTextMap([])]])],
|
|
37320
|
-
]),
|
|
37321
|
-
],
|
|
37322
|
-
]);
|
|
37323
|
-
}
|
|
37324
|
-
/**
|
|
37325
|
-
* Convert a registry disclosed contract into the wallet submission shape,
|
|
37326
|
-
* dropping debug fields and entries without a usable createdEventBlob.
|
|
37327
|
-
*/
|
|
37328
|
-
function toDisclosedContract(raw) {
|
|
37329
|
-
if (typeof raw.createdEventBlob !== "string")
|
|
37330
|
-
return null;
|
|
37331
|
-
return {
|
|
37332
|
-
createdEventBlob: raw.createdEventBlob,
|
|
37333
|
-
contractId: typeof raw.contractId === "string" ? raw.contractId : undefined,
|
|
37334
|
-
synchronizerId: typeof raw.synchronizerId === "string" ? raw.synchronizerId : undefined,
|
|
37335
|
-
};
|
|
37336
|
-
}
|
|
37337
|
-
/**
|
|
37338
|
-
* Build a Splice token-standard transfer of `amount` of `token` from `sender`
|
|
37339
|
-
* to `receiver`, carrying `memo`, ready to hand to a wallet's prepareExecute.
|
|
37340
|
-
* Returns the full prepareExecute params (commandId + commands + actAs +
|
|
37341
|
-
* disclosedContracts).
|
|
37342
|
-
*/
|
|
37343
|
-
async function buildCantonTransfer(connector, { sender, receiver, amount, token, memo, nowMs, }) {
|
|
37344
|
-
if (!token.originalAddress) {
|
|
37345
|
-
throw new Error("Canton token originalAddress is required");
|
|
37346
|
-
}
|
|
37347
|
-
const instrument = parseCantonInstrument(token.originalAddress);
|
|
37348
|
-
if (!instrument) {
|
|
37349
|
-
throw new Error(`Invalid Canton token address: ${token.originalAddress}`);
|
|
37350
|
-
}
|
|
37351
|
-
const registryUrl = resolveCantonRegistryUrl(instrument);
|
|
37352
|
-
const inputHoldingCids = await fetchInputHoldingCids(connector, sender, instrument, amount);
|
|
37353
|
-
const choiceArgs = buildTransferChoiceArgs({
|
|
37354
|
-
sender,
|
|
37355
|
-
receiver,
|
|
37356
|
-
amount,
|
|
37357
|
-
instrument,
|
|
37358
|
-
inputHoldingCids,
|
|
37359
|
-
memo,
|
|
37360
|
-
nowMs,
|
|
37361
|
-
});
|
|
37362
|
-
const factory = await fetchTransferFactory(registryUrl, choiceArgs);
|
|
37363
|
-
const choiceContextData = factory.choiceContext.choiceContextData;
|
|
37364
|
-
if (!choiceContextData) {
|
|
37365
|
-
throw new Error("Transfer factory choice context is missing");
|
|
37366
|
-
}
|
|
37367
|
-
choiceArgs.extraArgs.context = choiceContextData;
|
|
37368
|
-
const disclosedContracts = (factory.choiceContext?.disclosedContracts ?? []).flatMap((raw) => {
|
|
37369
|
-
const disclosedContract = toDisclosedContract(raw);
|
|
37370
|
-
return disclosedContract ? [disclosedContract] : [];
|
|
37371
|
-
});
|
|
37372
|
-
// Command ID: client-supplied identifier used to deduplicate submissions
|
|
37373
|
-
const commandId = `canton-${memo}`;
|
|
37374
|
-
return {
|
|
37375
|
-
commandId,
|
|
37376
|
-
commands: [
|
|
37377
|
-
{
|
|
37378
|
-
ExerciseCommand: {
|
|
37379
|
-
templateId: TRANSFER_FACTORY_INTERFACE_ID,
|
|
37380
|
-
contractId: factory.factoryId,
|
|
37381
|
-
choice: TRANSFER_FACTORY_CHOICE,
|
|
37382
|
-
choiceArgument: buildPrepareChoiceArgument(choiceArgs),
|
|
37383
|
-
},
|
|
37384
|
-
},
|
|
37385
|
-
],
|
|
37386
|
-
actAs: [sender],
|
|
37387
|
-
disclosedContracts,
|
|
37388
|
-
};
|
|
37389
|
-
}
|
|
37390
|
-
|
|
37391
37898
|
const useExecuteTransaction = (squidRoute) => {
|
|
37392
37899
|
const { fromChain, toChain, fromToken, toToken, isSameChain } = useSwap();
|
|
37393
37900
|
// A route completes on source tx only if it's same-chain AND
|
|
@@ -38002,7 +38509,7 @@ const useExecuteTransaction = (squidRoute) => {
|
|
|
38002
38509
|
}
|
|
38003
38510
|
const amount = formatBNToReadable(fromAmount, token.decimals);
|
|
38004
38511
|
dispatchSignatureRequestEvent(route);
|
|
38005
|
-
const
|
|
38512
|
+
const result = await cantonSigner.transfer({
|
|
38006
38513
|
sender: sourceUserAddress,
|
|
38007
38514
|
receiver,
|
|
38008
38515
|
amount,
|
|
@@ -38010,15 +38517,14 @@ const useExecuteTransaction = (squidRoute) => {
|
|
|
38010
38517
|
memo: orderHash,
|
|
38011
38518
|
nowMs: Date.now(),
|
|
38012
38519
|
});
|
|
38013
|
-
const
|
|
38014
|
-
const txHash = result.tx?.payload?.updateId ?? "";
|
|
38520
|
+
const txHash = result.updateId;
|
|
38015
38521
|
if (txHash) {
|
|
38016
38522
|
resetQueriesAfterTxSigned();
|
|
38017
38523
|
}
|
|
38018
38524
|
WidgetEvents.getInstance().dispatchSwapExecuteCall(route, txHash);
|
|
38019
38525
|
const txParams = setTransactionState({
|
|
38020
38526
|
route,
|
|
38021
|
-
txHash
|
|
38527
|
+
txHash,
|
|
38022
38528
|
userAddress: sourceUserAddress,
|
|
38023
38529
|
status: exports.TransactionStatus.INITIAL_LOADING,
|
|
38024
38530
|
sourceStatus: exports.TransactionStatus.ONGOING,
|
|
@@ -38301,7 +38807,7 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
|
|
|
38301
38807
|
//
|
|
38302
38808
|
// If deposit address is not selected, we use the connected address as the source address instead
|
|
38303
38809
|
const sourceUserAddress = isDepositAddressActive
|
|
38304
|
-
? depositRefundAddress ?? sourceConnectedAddress
|
|
38810
|
+
? (depositRefundAddress ?? sourceConnectedAddress)
|
|
38305
38811
|
: sourceConnectedAddress;
|
|
38306
38812
|
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), [
|
|
38307
38813
|
fromChain?.chainId,
|
|
@@ -38593,6 +39099,10 @@ const useAutoConnect = () => {
|
|
|
38593
39099
|
const { chains } = useSquidChains();
|
|
38594
39100
|
const { isInitialized } = useConfigStore();
|
|
38595
39101
|
const hasAttemptedReconnect = React.useRef(false);
|
|
39102
|
+
const hasAttemptedCantonReconnect = React.useRef(false);
|
|
39103
|
+
const { autoConnectCanton, wallets: cantonWallets } = useCantonContext();
|
|
39104
|
+
const recentCantonWalletId = useWalletStore((state) => state.recentConnectorIds[squidTypes.ChainType.CANTON]);
|
|
39105
|
+
const hasRecentCantonWallet = cantonWallets.some((wallet) => wallet.connectorId === recentCantonWalletId);
|
|
38596
39106
|
const { autoConnectEvm } = useEvmContext();
|
|
38597
39107
|
const { autoConnectSolana } = useSolanaContext();
|
|
38598
39108
|
const { autoConnectSui } = useSuiContext();
|
|
@@ -38660,6 +39170,15 @@ const useAutoConnect = () => {
|
|
|
38660
39170
|
React.useEffect(() => {
|
|
38661
39171
|
autoConnect();
|
|
38662
39172
|
}, [autoConnect]);
|
|
39173
|
+
React.useEffect(() => {
|
|
39174
|
+
if (!isInitialized ||
|
|
39175
|
+
chains.length === 0 ||
|
|
39176
|
+
!hasRecentCantonWallet ||
|
|
39177
|
+
hasAttemptedCantonReconnect.current)
|
|
39178
|
+
return;
|
|
39179
|
+
hasAttemptedCantonReconnect.current = true;
|
|
39180
|
+
autoConnectCanton().catch(() => undefined);
|
|
39181
|
+
}, [autoConnectCanton, hasRecentCantonWallet, isInitialized, chains.length]);
|
|
38663
39182
|
return { autoConnect };
|
|
38664
39183
|
};
|
|
38665
39184
|
|
|
@@ -39067,6 +39586,7 @@ exports.getStepsInfos = getStepsInfos;
|
|
|
39067
39586
|
exports.getSuggestedAmountsForCurrency = getSuggestedAmountsForCurrency;
|
|
39068
39587
|
exports.getSupportedChainIdsForDirection = getSupportedChainIdsForDirection;
|
|
39069
39588
|
exports.getSwapTxStatusRefetchInterval = getSwapTxStatusRefetchInterval;
|
|
39589
|
+
exports.getTokenAddressForSelection = getTokenAddressForSelection;
|
|
39070
39590
|
exports.getTokenAssetsKey = getTokenAssetsKey;
|
|
39071
39591
|
exports.getTokenImage = getTokenImage;
|
|
39072
39592
|
exports.getTokensForChain = getTokensForChain;
|
|
@@ -39087,9 +39607,6 @@ exports.isChainflipBridgeTransaction = isChainflipBridgeTransaction;
|
|
|
39087
39607
|
exports.isChainflipDepositRoute = isChainflipDepositRoute;
|
|
39088
39608
|
exports.isCoralBridgeAction = isCoralBridgeAction;
|
|
39089
39609
|
exports.isCosmosAddressValid = isCosmosAddressValid;
|
|
39090
|
-
exports.isDanalAddressValid = isDanalAddressValid;
|
|
39091
|
-
exports.isDanalDepositRoute = isDanalDepositRoute;
|
|
39092
|
-
exports.isDanalTxHashValid = isDanalTxHashValid;
|
|
39093
39610
|
exports.isDepositAddressDirectTransferRoute = isDepositAddressDirectTransferRoute;
|
|
39094
39611
|
exports.isEmptyObject = isEmptyObject;
|
|
39095
39612
|
exports.isEvmChainNotSupportedError = isEvmChainNotSupportedError;
|
|
@@ -39126,8 +39643,8 @@ exports.nativeSuiTokenAddress = nativeSuiTokenAddress;
|
|
|
39126
39643
|
exports.nativeXrplTokenAddress = nativeXrplTokenAddress;
|
|
39127
39644
|
exports.normalizeError = normalizeError;
|
|
39128
39645
|
exports.normalizeIbcAddress = normalizeIbcAddress;
|
|
39646
|
+
exports.normalizeSwapParamsForSelection = normalizeSwapParamsForSelection;
|
|
39129
39647
|
exports.normalizeTokenSymbol = normalizeTokenSymbol;
|
|
39130
|
-
exports.parseDanalTransferBody = parseDanalTransferBody;
|
|
39131
39648
|
exports.parseEvmAddress = parseEvmAddress;
|
|
39132
39649
|
exports.parseInitialAssetsFromUrl = parseInitialAssetsFromUrl;
|
|
39133
39650
|
exports.parseToBigInt = parseToBigInt;
|
|
@@ -39240,4 +39757,4 @@ exports.useXrplTrustLine = useXrplTrustLine;
|
|
|
39240
39757
|
exports.waitForReceiptWithRetry = waitForReceiptWithRetry;
|
|
39241
39758
|
exports.walletIconBaseUrl = walletIconBaseUrl;
|
|
39242
39759
|
exports.walletSupportsChainType = walletSupportsChainType;
|
|
39243
|
-
//# sourceMappingURL=index-
|
|
39760
|
+
//# sourceMappingURL=index-BBJD55XX.js.map
|