@hyperlane-xyz/sdk 25.5.0 → 26.0.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/app/MultiProtocolApp.d.ts +5 -1
- package/dist/app/MultiProtocolApp.d.ts.map +1 -1
- package/dist/app/MultiProtocolApp.js +6 -0
- package/dist/app/MultiProtocolApp.js.map +1 -1
- package/dist/app/MultiProtocolApp.test.js +2 -2
- package/dist/app/MultiProtocolApp.test.js.map +1 -1
- package/dist/consts/igp.d.ts.map +1 -1
- package/dist/consts/igp.js +2 -0
- package/dist/consts/igp.js.map +1 -1
- package/dist/contracts/contracts.d.ts.map +1 -1
- package/dist/contracts/contracts.js +13 -6
- package/dist/contracts/contracts.js.map +1 -1
- package/dist/core/EvmCoreModule.d.ts +1 -0
- package/dist/core/EvmCoreModule.d.ts.map +1 -1
- package/dist/core/EvmCoreModule.js +2 -1
- package/dist/core/EvmCoreModule.js.map +1 -1
- package/dist/core/MultiProtocolCore.d.ts.map +1 -1
- package/dist/core/MultiProtocolCore.js +2 -2
- package/dist/core/MultiProtocolCore.js.map +1 -1
- package/dist/core/adapters/EvmCoreAdapter.d.ts.map +1 -1
- package/dist/core/adapters/EvmCoreAdapter.js +2 -1
- package/dist/core/adapters/EvmCoreAdapter.js.map +1 -1
- package/dist/deploy/HyperlaneDeployer.d.ts.map +1 -1
- package/dist/deploy/HyperlaneDeployer.js +3 -4
- package/dist/deploy/HyperlaneDeployer.js.map +1 -1
- package/dist/deploy/warp.d.ts.map +1 -1
- package/dist/deploy/warp.js +4 -0
- package/dist/deploy/warp.js.map +1 -1
- package/dist/fee/EvmTokenFeeModule.d.ts +1 -0
- package/dist/fee/EvmTokenFeeModule.d.ts.map +1 -1
- package/dist/fee/EvmTokenFeeModule.js +2 -1
- package/dist/fee/EvmTokenFeeModule.js.map +1 -1
- package/dist/gas/utils.d.ts.map +1 -1
- package/dist/gas/utils.js +1 -0
- package/dist/gas/utils.js.map +1 -1
- package/dist/hook/EvmHookModule.d.ts +1 -0
- package/dist/hook/EvmHookModule.d.ts.map +1 -1
- package/dist/hook/EvmHookModule.js +2 -1
- package/dist/hook/EvmHookModule.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ism/EvmIsmModule.d.ts +1 -0
- package/dist/ism/EvmIsmModule.d.ts.map +1 -1
- package/dist/ism/EvmIsmModule.js +2 -1
- package/dist/ism/EvmIsmModule.js.map +1 -1
- package/dist/metadata/ChainMetadataManager.d.ts.map +1 -1
- package/dist/metadata/ChainMetadataManager.js +3 -3
- package/dist/metadata/ChainMetadataManager.js.map +1 -1
- package/dist/metadata/agentConfig.d.ts.map +1 -1
- package/dist/metadata/agentConfig.js +1 -0
- package/dist/metadata/agentConfig.js.map +1 -1
- package/dist/metadata/blockExplorer.js +2 -2
- package/dist/metadata/blockExplorer.js.map +1 -1
- package/dist/metadata/chainMetadataTypes.d.ts +0 -1
- package/dist/metadata/chainMetadataTypes.d.ts.map +1 -1
- package/dist/metadata/chainMetadataTypes.js +0 -1
- package/dist/metadata/chainMetadataTypes.js.map +1 -1
- package/dist/metadata/warpRouteConfig.d.ts +10 -8
- package/dist/metadata/warpRouteConfig.d.ts.map +1 -1
- package/dist/providers/MultiProtocolProvider.d.ts +2 -1
- package/dist/providers/MultiProtocolProvider.d.ts.map +1 -1
- package/dist/providers/MultiProtocolProvider.js +3 -0
- package/dist/providers/MultiProtocolProvider.js.map +1 -1
- package/dist/providers/MultiProvider.d.ts.map +1 -1
- package/dist/providers/MultiProvider.js +5 -5
- package/dist/providers/MultiProvider.js.map +1 -1
- package/dist/providers/ProviderType.d.ts +25 -5
- package/dist/providers/ProviderType.d.ts.map +1 -1
- package/dist/providers/ProviderType.js +2 -0
- package/dist/providers/ProviderType.js.map +1 -1
- package/dist/providers/explorerHealthTest.d.ts.map +1 -1
- package/dist/providers/explorerHealthTest.js +1 -0
- package/dist/providers/explorerHealthTest.js.map +1 -1
- package/dist/providers/providerBuilders.d.ts +2 -1
- package/dist/providers/providerBuilders.d.ts.map +1 -1
- package/dist/providers/providerBuilders.js +9 -0
- package/dist/providers/providerBuilders.js.map +1 -1
- package/dist/providers/rpcHealthTest.d.ts.map +1 -1
- package/dist/providers/rpcHealthTest.js +2 -0
- package/dist/providers/rpcHealthTest.js.map +1 -1
- package/dist/providers/transactionFeeEstimators.d.ts +4 -3
- package/dist/providers/transactionFeeEstimators.d.ts.map +1 -1
- package/dist/providers/transactionFeeEstimators.js +19 -6
- package/dist/providers/transactionFeeEstimators.js.map +1 -1
- package/dist/providers/transactions/submitter/submitterBuilderGetter.d.ts.map +1 -1
- package/dist/providers/transactions/submitter/submitterBuilderGetter.js +1 -0
- package/dist/providers/transactions/submitter/submitterBuilderGetter.js.map +1 -1
- package/dist/router/MultiProtocolRouterApps.d.ts.map +1 -1
- package/dist/router/MultiProtocolRouterApps.js +3 -3
- package/dist/router/MultiProtocolRouterApps.js.map +1 -1
- package/dist/signers/cosmos/cosmjs.d.ts +2 -1
- package/dist/signers/cosmos/cosmjs.d.ts.map +1 -1
- package/dist/signers/cosmos/cosmjs.js +1 -1
- package/dist/signers/cosmos/cosmjs.js.map +1 -1
- package/dist/signers/evm/ethersv5.d.ts +2 -1
- package/dist/signers/evm/ethersv5.d.ts.map +1 -1
- package/dist/signers/evm/ethersv5.js +5 -5
- package/dist/signers/evm/ethersv5.js.map +1 -1
- package/dist/signers/radix/radix-toolkit.d.ts +2 -1
- package/dist/signers/radix/radix-toolkit.d.ts.map +1 -1
- package/dist/signers/radix/radix-toolkit.js +1 -1
- package/dist/signers/radix/radix-toolkit.js.map +1 -1
- package/dist/signers/signers.d.ts.map +1 -1
- package/dist/signers/signers.js +1 -0
- package/dist/signers/signers.js.map +1 -1
- package/dist/signers/starknet/starknetjs.d.ts +2 -1
- package/dist/signers/starknet/starknetjs.d.ts.map +1 -1
- package/dist/signers/starknet/starknetjs.js +1 -1
- package/dist/signers/starknet/starknetjs.js.map +1 -1
- package/dist/signers/svm/solana-web3js.d.ts +2 -1
- package/dist/signers/svm/solana-web3js.d.ts.map +1 -1
- package/dist/signers/svm/solana-web3js.js +1 -1
- package/dist/signers/svm/solana-web3js.js.map +1 -1
- package/dist/signers/types.d.ts +2 -1
- package/dist/signers/types.d.ts.map +1 -1
- package/dist/token/EvmWarpModule.d.ts +9 -0
- package/dist/token/EvmWarpModule.d.ts.map +1 -1
- package/dist/token/EvmWarpModule.hardhat-test.js +56 -1
- package/dist/token/EvmWarpModule.hardhat-test.js.map +1 -1
- package/dist/token/EvmWarpModule.js +92 -3
- package/dist/token/EvmWarpModule.js.map +1 -1
- package/dist/token/EvmWarpRouteReader.d.ts +4 -0
- package/dist/token/EvmWarpRouteReader.d.ts.map +1 -1
- package/dist/token/EvmWarpRouteReader.hardhat-test.js +56 -0
- package/dist/token/EvmWarpRouteReader.hardhat-test.js.map +1 -1
- package/dist/token/EvmWarpRouteReader.js +41 -0
- package/dist/token/EvmWarpRouteReader.js.map +1 -1
- package/dist/token/IToken.d.ts +1 -0
- package/dist/token/IToken.d.ts.map +1 -1
- package/dist/token/Token.d.ts +1 -0
- package/dist/token/Token.d.ts.map +1 -1
- package/dist/token/Token.js +36 -13
- package/dist/token/Token.js.map +1 -1
- package/dist/token/Token.test.js +19 -0
- package/dist/token/Token.test.js.map +1 -1
- package/dist/token/TokenStandard.d.ts +21 -1
- package/dist/token/TokenStandard.d.ts.map +1 -1
- package/dist/token/TokenStandard.js +98 -0
- package/dist/token/TokenStandard.js.map +1 -1
- package/dist/token/adapters/EvmMultiCollateralAdapter.d.ts +45 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.d.ts.map +1 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.js +66 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.js.map +1 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.test.d.ts +2 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.test.d.ts.map +1 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.test.js +147 -0
- package/dist/token/adapters/EvmMultiCollateralAdapter.test.js.map +1 -0
- package/dist/token/adapters/EvmTokenAdapter.d.ts.map +1 -1
- package/dist/token/adapters/EvmTokenAdapter.js +24 -22
- package/dist/token/adapters/EvmTokenAdapter.js.map +1 -1
- package/dist/token/config.d.ts +2 -0
- package/dist/token/config.d.ts.map +1 -1
- package/dist/token/config.js +3 -0
- package/dist/token/config.js.map +1 -1
- package/dist/token/configUtils.d.ts.map +1 -1
- package/dist/token/configUtils.js +5 -3
- package/dist/token/configUtils.js.map +1 -1
- package/dist/token/contracts.d.ts +3 -0
- package/dist/token/contracts.d.ts.map +1 -1
- package/dist/token/contracts.js +3 -0
- package/dist/token/contracts.js.map +1 -1
- package/dist/token/deploy.d.ts +1 -0
- package/dist/token/deploy.d.ts.map +1 -1
- package/dist/token/deploy.js +36 -5
- package/dist/token/deploy.js.map +1 -1
- package/dist/token/nativeTokenMetadata.d.ts.map +1 -1
- package/dist/token/nativeTokenMetadata.js +6 -0
- package/dist/token/nativeTokenMetadata.js.map +1 -1
- package/dist/token/tokenMetadataUtils.d.ts.map +1 -1
- package/dist/token/tokenMetadataUtils.js +4 -3
- package/dist/token/tokenMetadataUtils.js.map +1 -1
- package/dist/token/types.d.ts +1268 -94
- package/dist/token/types.d.ts.map +1 -1
- package/dist/token/types.js +18 -1
- package/dist/token/types.js.map +1 -1
- package/dist/warp/WarpCore.d.ts +53 -12
- package/dist/warp/WarpCore.d.ts.map +1 -1
- package/dist/warp/WarpCore.js +262 -57
- package/dist/warp/WarpCore.js.map +1 -1
- package/dist/warp/WarpCore.test.js +422 -0
- package/dist/warp/WarpCore.test.js.map +1 -1
- package/package.json +12 -11
package/dist/warp/WarpCore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProtocolType, assert, convertDecimalsToIntegerString, convertToProtocolAddress, convertToScaledAmount, isValidAddress, isZeroishAddress, rootLogger, } from '@hyperlane-xyz/utils';
|
|
1
|
+
import { ProtocolType, assert, convertDecimalsToIntegerString, convertToProtocolAddress, convertToScaledAmount, isEVMLike, isValidAddress, isZeroishAddress, rootLogger, } from '@hyperlane-xyz/utils';
|
|
2
2
|
import { estimateTransactionFeeEthersV5ForGasUnits, } from '../providers/transactionFeeEstimators.js';
|
|
3
3
|
import { Token } from '../token/Token.js';
|
|
4
4
|
import { TokenAmount } from '../token/TokenAmount.js';
|
|
@@ -60,7 +60,7 @@ export class WarpCore {
|
|
|
60
60
|
* and for token fee quote if it exists.
|
|
61
61
|
* Sender is only required for Sealevel origins.
|
|
62
62
|
*/
|
|
63
|
-
async getInterchainTransferFee({ originTokenAmount, destination, sender, recipient, }) {
|
|
63
|
+
async getInterchainTransferFee({ originTokenAmount, destination, sender, recipient, destinationToken, }) {
|
|
64
64
|
this.logger.debug(`Fetching interchain transfer quote to ${destination}`);
|
|
65
65
|
const { amount, token: originToken } = originTokenAmount;
|
|
66
66
|
const originName = originToken.chainName;
|
|
@@ -77,15 +77,33 @@ export class WarpCore {
|
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
79
|
// Otherwise, compute IGP quote via the adapter
|
|
80
|
-
|
|
80
|
+
let quote;
|
|
81
81
|
const destinationDomainId = this.multiProvider.getDomainId(destination);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
if (this.isMultiCollateralTransfer(originToken, destinationToken)) {
|
|
83
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
84
|
+
originToken,
|
|
85
|
+
destination,
|
|
86
|
+
destinationToken,
|
|
87
|
+
});
|
|
88
|
+
assert(resolvedDestinationToken.addressOrDenom, 'Destination token missing addressOrDenom');
|
|
89
|
+
const multiCollateralAdapter = originToken.getHypAdapter(this.multiProvider, destinationName);
|
|
90
|
+
quote = await multiCollateralAdapter.quoteTransferRemoteToGas({
|
|
91
|
+
destination: destinationDomainId,
|
|
92
|
+
recipient,
|
|
93
|
+
amount,
|
|
94
|
+
targetRouter: resolvedDestinationToken.addressOrDenom,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const hypAdapter = originToken.getHypAdapter(this.multiProvider, destinationName);
|
|
99
|
+
quote = await hypAdapter.quoteTransferRemoteGas({
|
|
100
|
+
destination: destinationDomainId,
|
|
101
|
+
sender,
|
|
102
|
+
customHook: originToken.igpTokenAddressOrDenom,
|
|
103
|
+
recipient,
|
|
104
|
+
amount,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
89
107
|
gasAmount = BigInt(quote.igpQuote.amount);
|
|
90
108
|
gasAddressOrDenom = quote.igpQuote.addressOrDenom;
|
|
91
109
|
feeAmount = quote.tokenFeeQuote?.amount;
|
|
@@ -122,7 +140,7 @@ export class WarpCore {
|
|
|
122
140
|
/**
|
|
123
141
|
* Simulates a transfer to estimate 'local' gas fees on the origin chain
|
|
124
142
|
*/
|
|
125
|
-
async getLocalTransferFee({ originToken, destination, sender, senderPubKey, interchainFee, tokenFeeQuote, }) {
|
|
143
|
+
async getLocalTransferFee({ originToken, destination, sender, senderPubKey, interchainFee, tokenFeeQuote, destinationToken, }) {
|
|
126
144
|
this.logger.debug(`Estimating local transfer gas to ${destination}`);
|
|
127
145
|
const originMetadata = this.multiProvider.getChainMetadata(originToken.chainName);
|
|
128
146
|
const destinationMetadata = this.multiProvider.getChainMetadata(destination);
|
|
@@ -150,6 +168,7 @@ export class WarpCore {
|
|
|
150
168
|
recipient,
|
|
151
169
|
interchainFee,
|
|
152
170
|
tokenFeeQuote,
|
|
171
|
+
destinationToken,
|
|
153
172
|
});
|
|
154
173
|
// Starknet does not support gas estimation without starknet account
|
|
155
174
|
if (originToken.protocol === ProtocolType.Starknet) {
|
|
@@ -173,8 +192,7 @@ export class WarpCore {
|
|
|
173
192
|
}
|
|
174
193
|
}
|
|
175
194
|
// On ethereum, sometimes 2 txs are required (one approve, one transferRemote)
|
|
176
|
-
else if (txs.length === 2 &&
|
|
177
|
-
originToken.protocol === ProtocolType.Ethereum) {
|
|
195
|
+
else if (txs.length === 2 && isEVMLike(originToken.protocol)) {
|
|
178
196
|
const provider = this.multiProvider.getEthersV5Provider(originMetadata.name);
|
|
179
197
|
// We use a hard-coded const as an estimate for the transferRemote because we
|
|
180
198
|
// cannot reliably simulate the tx when an approval tx is required first
|
|
@@ -192,7 +210,7 @@ export class WarpCore {
|
|
|
192
210
|
* but it also resolves the native token and returns a TokenAmount
|
|
193
211
|
* @todo: rename to getLocalTransferFee for consistency (requires breaking change)
|
|
194
212
|
*/
|
|
195
|
-
async getLocalTransferFeeAmount({ originToken, destination, sender, senderPubKey, interchainFee, tokenFeeQuote, }) {
|
|
213
|
+
async getLocalTransferFeeAmount({ originToken, destination, sender, senderPubKey, interchainFee, tokenFeeQuote, destinationToken, }) {
|
|
196
214
|
const originMetadata = this.multiProvider.getChainMetadata(originToken.chainName);
|
|
197
215
|
// If there's no native token, we can't represent local gas
|
|
198
216
|
if (!originMetadata.nativeToken)
|
|
@@ -205,6 +223,7 @@ export class WarpCore {
|
|
|
205
223
|
senderPubKey,
|
|
206
224
|
interchainFee,
|
|
207
225
|
tokenFeeQuote,
|
|
226
|
+
destinationToken,
|
|
208
227
|
});
|
|
209
228
|
// Get the local gas token. This assumes the chain's native token will pay for local gas
|
|
210
229
|
// This will need to be smarter if more complex scenarios on Cosmos are supported
|
|
@@ -215,7 +234,19 @@ export class WarpCore {
|
|
|
215
234
|
* Gets a list of populated transactions required to transfer a token to a remote chain
|
|
216
235
|
* Typically just 1 transaction but sometimes more, like when an approval is required first
|
|
217
236
|
*/
|
|
218
|
-
async getTransferRemoteTxs({ originTokenAmount, destination, sender, recipient, interchainFee, tokenFeeQuote, }) {
|
|
237
|
+
async getTransferRemoteTxs({ originTokenAmount, destination, sender, recipient, interchainFee, tokenFeeQuote, destinationToken, }) {
|
|
238
|
+
// Check if this is a MultiCollateral transfer
|
|
239
|
+
if (destinationToken &&
|
|
240
|
+
this.isMultiCollateralTransfer(originTokenAmount.token, destinationToken)) {
|
|
241
|
+
return this.getMultiCollateralTransferTxs({
|
|
242
|
+
originTokenAmount,
|
|
243
|
+
destination,
|
|
244
|
+
sender,
|
|
245
|
+
recipient,
|
|
246
|
+
destinationToken,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// Standard warp route transfer
|
|
219
250
|
const transactions = [];
|
|
220
251
|
const { token, amount } = originTokenAmount;
|
|
221
252
|
const destinationName = this.multiProvider.getChainName(destination);
|
|
@@ -228,6 +259,7 @@ export class WarpCore {
|
|
|
228
259
|
destination,
|
|
229
260
|
sender,
|
|
230
261
|
recipient,
|
|
262
|
+
destinationToken,
|
|
231
263
|
});
|
|
232
264
|
interchainFee = transferFee.igpQuote;
|
|
233
265
|
tokenFeeQuote = transferFee.tokenFeeQuote;
|
|
@@ -320,11 +352,104 @@ export class WarpCore {
|
|
|
320
352
|
transactions.push(transferTx);
|
|
321
353
|
return transactions;
|
|
322
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if this is a MultiCollateral transfer.
|
|
357
|
+
* Returns true if both tokens are MultiCollateral tokens.
|
|
358
|
+
*/
|
|
359
|
+
isMultiCollateralTransfer(originToken, destinationToken) {
|
|
360
|
+
if (!destinationToken)
|
|
361
|
+
return false;
|
|
362
|
+
return (originToken.isMultiCollateralToken() &&
|
|
363
|
+
destinationToken.isMultiCollateralToken());
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Executes a MultiCollateral transfer between different collateral routers.
|
|
367
|
+
* Uses transferRemoteTo for both same-chain and cross-chain transfers.
|
|
368
|
+
* Same-chain: calls handle() directly on target router (atomic, no relay needed).
|
|
369
|
+
*/
|
|
370
|
+
async getMultiCollateralTransferTxs({ originTokenAmount, destination, sender, recipient, destinationToken, }) {
|
|
371
|
+
const transactions = [];
|
|
372
|
+
const { token: originToken, amount } = originTokenAmount;
|
|
373
|
+
const destinationName = this.multiProvider.getChainName(destination);
|
|
374
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
375
|
+
originToken,
|
|
376
|
+
destination,
|
|
377
|
+
destinationToken,
|
|
378
|
+
});
|
|
379
|
+
assert(originToken.collateralAddressOrDenom, 'Origin token missing collateralAddressOrDenom');
|
|
380
|
+
assert(resolvedDestinationToken.addressOrDenom, 'Destination token missing addressOrDenom');
|
|
381
|
+
const providerType = TOKEN_STANDARD_TO_PROVIDER_TYPE[originToken.standard];
|
|
382
|
+
const adapter = originToken.getHypAdapter(this.multiProvider, destinationName);
|
|
383
|
+
const transferQuote = await adapter.quoteTransferRemoteToGas({
|
|
384
|
+
destination: this.multiProvider.getDomainId(destination),
|
|
385
|
+
recipient,
|
|
386
|
+
amount,
|
|
387
|
+
targetRouter: resolvedDestinationToken.addressOrDenom,
|
|
388
|
+
});
|
|
389
|
+
assert(!transferQuote.igpQuote.addressOrDenom ||
|
|
390
|
+
isZeroishAddress(transferQuote.igpQuote.addressOrDenom), `MultiCollateral transferRemoteTo requires native IGP fee; got ${transferQuote.igpQuote.addressOrDenom}`);
|
|
391
|
+
const tokenFeeAmount = transferQuote.tokenFeeQuote?.amount ?? 0n;
|
|
392
|
+
const totalDebit = amount + tokenFeeAmount;
|
|
393
|
+
const [isApproveRequired, isRevokeApprovalRequired] = await Promise.all([
|
|
394
|
+
adapter.isApproveRequired(sender, originToken.addressOrDenom, totalDebit),
|
|
395
|
+
adapter.isRevokeApprovalRequired(sender, originToken.addressOrDenom),
|
|
396
|
+
]);
|
|
397
|
+
if (isApproveRequired && isRevokeApprovalRequired) {
|
|
398
|
+
const revokeTxReq = await adapter.populateApproveTx({
|
|
399
|
+
weiAmountOrId: 0,
|
|
400
|
+
recipient: originToken.addressOrDenom,
|
|
401
|
+
});
|
|
402
|
+
transactions.push({
|
|
403
|
+
category: WarpTxCategory.Revoke,
|
|
404
|
+
type: providerType,
|
|
405
|
+
transaction: revokeTxReq,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (isApproveRequired) {
|
|
409
|
+
const approveTxReq = await adapter.populateApproveTx({
|
|
410
|
+
weiAmountOrId: totalDebit,
|
|
411
|
+
recipient: originToken.addressOrDenom,
|
|
412
|
+
});
|
|
413
|
+
transactions.push({
|
|
414
|
+
category: WarpTxCategory.Approval,
|
|
415
|
+
type: providerType,
|
|
416
|
+
transaction: approveTxReq,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// transferRemoteTo works for both same-chain and cross-chain.
|
|
420
|
+
// Same-chain: calls handle() directly on target router (atomic, no relay needed).
|
|
421
|
+
const destinationDomainId = this.multiProvider.getDomainId(destination);
|
|
422
|
+
const txReq = await adapter.populateTransferRemoteToTx({
|
|
423
|
+
destination: destinationDomainId,
|
|
424
|
+
recipient,
|
|
425
|
+
amount,
|
|
426
|
+
targetRouter: resolvedDestinationToken.addressOrDenom,
|
|
427
|
+
interchainGas: transferQuote,
|
|
428
|
+
});
|
|
429
|
+
transactions.push({
|
|
430
|
+
category: WarpTxCategory.Transfer,
|
|
431
|
+
type: providerType,
|
|
432
|
+
transaction: txReq,
|
|
433
|
+
});
|
|
434
|
+
return transactions;
|
|
435
|
+
}
|
|
323
436
|
/**
|
|
324
437
|
* Fetch local and interchain fee estimates for a remote transfer
|
|
325
438
|
*/
|
|
326
|
-
async estimateTransferRemoteFees({ originTokenAmount, destination, recipient, sender, senderPubKey, }) {
|
|
439
|
+
async estimateTransferRemoteFees({ originTokenAmount, destination, recipient, sender, senderPubKey, destinationToken, }) {
|
|
327
440
|
this.logger.debug('Fetching remote transfer fee estimates');
|
|
441
|
+
const { token: originToken } = originTokenAmount;
|
|
442
|
+
// Handle MultiCollateral fee estimation
|
|
443
|
+
if (this.isMultiCollateralTransfer(originToken, destinationToken)) {
|
|
444
|
+
return this.estimateMultiCollateralFees({
|
|
445
|
+
originTokenAmount,
|
|
446
|
+
destination,
|
|
447
|
+
destinationToken,
|
|
448
|
+
recipient,
|
|
449
|
+
sender,
|
|
450
|
+
senderPubKey,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
328
453
|
// First get interchain gas quote (aka IGP quote)
|
|
329
454
|
// Start with this because it's used in the local fee estimation
|
|
330
455
|
const { igpQuote, tokenFeeQuote } = await this.getInterchainTransferFee({
|
|
@@ -348,11 +473,43 @@ export class WarpCore {
|
|
|
348
473
|
tokenFeeQuote,
|
|
349
474
|
};
|
|
350
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Estimate fees for a MultiCollateral transfer.
|
|
478
|
+
*/
|
|
479
|
+
async estimateMultiCollateralFees({ originTokenAmount, destination, destinationToken, recipient, sender, senderPubKey, }) {
|
|
480
|
+
const { token: originToken } = originTokenAmount;
|
|
481
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
482
|
+
originToken,
|
|
483
|
+
destination,
|
|
484
|
+
destinationToken,
|
|
485
|
+
});
|
|
486
|
+
const { igpQuote: interchainQuote, tokenFeeQuote } = await this.getInterchainTransferFee({
|
|
487
|
+
originTokenAmount,
|
|
488
|
+
destination,
|
|
489
|
+
sender,
|
|
490
|
+
recipient,
|
|
491
|
+
destinationToken: resolvedDestinationToken,
|
|
492
|
+
});
|
|
493
|
+
const localQuote = await this.getLocalTransferFeeAmount({
|
|
494
|
+
originToken,
|
|
495
|
+
destination,
|
|
496
|
+
sender,
|
|
497
|
+
senderPubKey,
|
|
498
|
+
interchainFee: interchainQuote,
|
|
499
|
+
tokenFeeQuote,
|
|
500
|
+
destinationToken,
|
|
501
|
+
});
|
|
502
|
+
return {
|
|
503
|
+
interchainQuote,
|
|
504
|
+
localQuote,
|
|
505
|
+
tokenFeeQuote,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
351
508
|
/**
|
|
352
509
|
* Computes the max transferrable amount of the from the given
|
|
353
510
|
* token balance, accounting for local and interchain gas fees
|
|
354
511
|
*/
|
|
355
|
-
async getMaxTransferAmount({ balance, destination, recipient, sender, senderPubKey, feeEstimate, }) {
|
|
512
|
+
async getMaxTransferAmount({ balance, destination, recipient, sender, senderPubKey, feeEstimate, destinationToken, }) {
|
|
356
513
|
const originToken = balance.token;
|
|
357
514
|
if (!feeEstimate) {
|
|
358
515
|
feeEstimate = await this.estimateTransferRemoteFees({
|
|
@@ -361,6 +518,7 @@ export class WarpCore {
|
|
|
361
518
|
recipient,
|
|
362
519
|
sender,
|
|
363
520
|
senderPubKey,
|
|
521
|
+
destinationToken,
|
|
364
522
|
});
|
|
365
523
|
}
|
|
366
524
|
const { localQuote, interchainQuote, tokenFeeQuote } = feeEstimate;
|
|
@@ -377,6 +535,7 @@ export class WarpCore {
|
|
|
377
535
|
destination,
|
|
378
536
|
recipient,
|
|
379
537
|
sender,
|
|
538
|
+
destinationToken,
|
|
380
539
|
});
|
|
381
540
|
// Because tokenFeeQuote is calculated based on the amount, we need to recalculate
|
|
382
541
|
// the tokenFeeQuote after subtracting the localQuote and IGP to get max transfer amount
|
|
@@ -403,26 +562,28 @@ export class WarpCore {
|
|
|
403
562
|
/**
|
|
404
563
|
* Checks if destination chain's collateral is sufficient to cover the transfer
|
|
405
564
|
*/
|
|
406
|
-
async isDestinationCollateralSufficient({ originTokenAmount, destination, }) {
|
|
565
|
+
async isDestinationCollateralSufficient({ originTokenAmount, destination, destinationToken, }) {
|
|
407
566
|
const { token: originToken, amount } = originTokenAmount;
|
|
408
|
-
const destinationName = this.multiProvider.getChainName(destination);
|
|
409
567
|
this.logger.debug(`Checking collateral for ${originToken.symbol} to ${destination}`);
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
568
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
569
|
+
originToken,
|
|
570
|
+
destination,
|
|
571
|
+
destinationToken,
|
|
572
|
+
});
|
|
573
|
+
if (!TOKEN_COLLATERALIZED_STANDARDS.includes(resolvedDestinationToken.standard)) {
|
|
574
|
+
this.logger.debug(`${resolvedDestinationToken.symbol} is not collateralized, skipping`);
|
|
414
575
|
return true;
|
|
415
576
|
}
|
|
416
|
-
const destinationBalance = await this.getTokenCollateral(
|
|
417
|
-
const destinationBalanceInOriginDecimals = convertDecimalsToIntegerString(
|
|
577
|
+
const destinationBalance = await this.getTokenCollateral(resolvedDestinationToken);
|
|
578
|
+
const destinationBalanceInOriginDecimals = convertDecimalsToIntegerString(resolvedDestinationToken.decimals, originToken.decimals, destinationBalance.toString());
|
|
418
579
|
// check for scaling factor
|
|
419
580
|
if (originToken.scale &&
|
|
420
|
-
|
|
421
|
-
originToken.scale !==
|
|
581
|
+
resolvedDestinationToken.scale &&
|
|
582
|
+
originToken.scale !== resolvedDestinationToken.scale) {
|
|
422
583
|
const precisionFactor = 100_000;
|
|
423
584
|
const scaledAmount = convertToScaledAmount({
|
|
424
585
|
fromScale: originToken.scale,
|
|
425
|
-
toScale:
|
|
586
|
+
toScale: resolvedDestinationToken.scale,
|
|
426
587
|
amount,
|
|
427
588
|
precisionFactor,
|
|
428
589
|
});
|
|
@@ -446,26 +607,42 @@ export class WarpCore {
|
|
|
446
607
|
/**
|
|
447
608
|
* Ensure the remote token transfer would be valid for the given chains, amount, sender, and recipient
|
|
448
609
|
*/
|
|
449
|
-
async validateTransfer({ originTokenAmount, destination, recipient, sender, senderPubKey, }) {
|
|
610
|
+
async validateTransfer({ originTokenAmount, destination, recipient, sender, senderPubKey, destinationToken, }) {
|
|
450
611
|
const chainError = this.validateChains(originTokenAmount.token.chainName, destination);
|
|
451
612
|
if (chainError)
|
|
452
613
|
return chainError;
|
|
453
614
|
const recipientError = this.validateRecipient(recipient, destination);
|
|
454
615
|
if (recipientError)
|
|
455
616
|
return recipientError;
|
|
456
|
-
const
|
|
617
|
+
const resolvedDestinationToken = (() => {
|
|
618
|
+
try {
|
|
619
|
+
return this.resolveDestinationToken({
|
|
620
|
+
originToken: originTokenAmount.token,
|
|
621
|
+
destination,
|
|
622
|
+
destinationToken,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
const message = error instanceof Error ? error.message : 'Invalid destination token';
|
|
627
|
+
return { error: message };
|
|
628
|
+
}
|
|
629
|
+
})();
|
|
630
|
+
if ('error' in resolvedDestinationToken) {
|
|
631
|
+
return { destinationToken: resolvedDestinationToken.error };
|
|
632
|
+
}
|
|
633
|
+
const amountError = await this.validateAmount(originTokenAmount, destination, recipient, resolvedDestinationToken);
|
|
457
634
|
if (amountError)
|
|
458
635
|
return amountError;
|
|
459
|
-
const destinationRateLimitError = await this.validateDestinationRateLimit(originTokenAmount, destination);
|
|
636
|
+
const destinationRateLimitError = await this.validateDestinationRateLimit(originTokenAmount, destination, resolvedDestinationToken);
|
|
460
637
|
if (destinationRateLimitError)
|
|
461
638
|
return destinationRateLimitError;
|
|
462
|
-
const destinationCollateralError = await this.validateDestinationCollateral(originTokenAmount, destination);
|
|
639
|
+
const destinationCollateralError = await this.validateDestinationCollateral(originTokenAmount, destination, resolvedDestinationToken);
|
|
463
640
|
if (destinationCollateralError)
|
|
464
641
|
return destinationCollateralError;
|
|
465
642
|
const originCollateralError = await this.validateOriginCollateral(originTokenAmount);
|
|
466
643
|
if (originCollateralError)
|
|
467
644
|
return originCollateralError;
|
|
468
|
-
const balancesError = await this.validateTokenBalances(originTokenAmount, destination, sender, recipient, senderPubKey);
|
|
645
|
+
const balancesError = await this.validateTokenBalances(originTokenAmount, destination, sender, recipient, senderPubKey, resolvedDestinationToken);
|
|
469
646
|
if (balancesError)
|
|
470
647
|
return balancesError;
|
|
471
648
|
return null;
|
|
@@ -516,21 +693,23 @@ export class WarpCore {
|
|
|
516
693
|
/**
|
|
517
694
|
* Ensure token amount is valid
|
|
518
695
|
*/
|
|
519
|
-
async validateAmount(originTokenAmount, destination, recipient) {
|
|
696
|
+
async validateAmount(originTokenAmount, destination, recipient, destinationToken) {
|
|
520
697
|
if (!originTokenAmount.amount || originTokenAmount.amount < 0n) {
|
|
521
698
|
const isNft = originTokenAmount.token.isNft();
|
|
522
699
|
return { amount: isNft ? 'Invalid Token Id' : 'Invalid amount' };
|
|
523
700
|
}
|
|
524
701
|
// Check the transfer amount is sufficient on the destination side
|
|
525
702
|
const originToken = originTokenAmount.token;
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
703
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
704
|
+
originToken,
|
|
705
|
+
destination,
|
|
706
|
+
destinationToken,
|
|
707
|
+
});
|
|
708
|
+
const destinationAdapter = resolvedDestinationToken.getAdapter(this.multiProvider);
|
|
530
709
|
// Get the min required destination amount
|
|
531
710
|
const minDestinationTransferAmount = await destinationAdapter.getMinimumTransferAmount(recipient);
|
|
532
711
|
// Convert the minDestinationTransferAmount to an origin amount
|
|
533
|
-
const minOriginTransferAmount =
|
|
712
|
+
const minOriginTransferAmount = originToken.amount(convertDecimalsToIntegerString(resolvedDestinationToken.decimals, originToken.decimals, minDestinationTransferAmount.toString()));
|
|
534
713
|
if (minOriginTransferAmount.amount > originTokenAmount.amount) {
|
|
535
714
|
return {
|
|
536
715
|
amount: `Minimum transfer amount is ${minOriginTransferAmount.getDecimalFormattedAmount()} ${originToken.symbol}`,
|
|
@@ -541,7 +720,7 @@ export class WarpCore {
|
|
|
541
720
|
/**
|
|
542
721
|
* Ensure the sender has sufficient balances for transfer and interchain gas
|
|
543
722
|
*/
|
|
544
|
-
async validateTokenBalances(originTokenAmount, destination, sender, recipient, senderPubKey) {
|
|
723
|
+
async validateTokenBalances(originTokenAmount, destination, sender, recipient, senderPubKey, destinationToken) {
|
|
545
724
|
const { token: originToken, amount } = originTokenAmount;
|
|
546
725
|
const { amount: senderBalance } = await originToken.getBalance(this.multiProvider, sender);
|
|
547
726
|
const senderBalanceAmount = originTokenAmount.token.amount(senderBalance);
|
|
@@ -555,6 +734,7 @@ export class WarpCore {
|
|
|
555
734
|
destination,
|
|
556
735
|
sender,
|
|
557
736
|
recipient,
|
|
737
|
+
destinationToken,
|
|
558
738
|
});
|
|
559
739
|
// Get balance of the IGP fee token, which may be different from the transfer token
|
|
560
740
|
const interchainQuoteTokenBalance = originToken.isFungibleWith(interchainQuote.token)
|
|
@@ -581,6 +761,7 @@ export class WarpCore {
|
|
|
581
761
|
senderPubKey,
|
|
582
762
|
interchainFee: interchainQuote,
|
|
583
763
|
tokenFeeQuote,
|
|
764
|
+
destinationToken,
|
|
584
765
|
});
|
|
585
766
|
const feeEstimate = { interchainQuote, localQuote };
|
|
586
767
|
// Check 5: Ensure balances can cover the COMBINED amount and fees
|
|
@@ -591,6 +772,7 @@ export class WarpCore {
|
|
|
591
772
|
sender,
|
|
592
773
|
senderPubKey,
|
|
593
774
|
feeEstimate,
|
|
775
|
+
destinationToken,
|
|
594
776
|
});
|
|
595
777
|
if (amount > maxTransfer.amount) {
|
|
596
778
|
return { amount: 'Insufficient balance for gas and transfer' };
|
|
@@ -600,10 +782,11 @@ export class WarpCore {
|
|
|
600
782
|
/**
|
|
601
783
|
* Ensure the sender has sufficient balances for transfer and interchain gas
|
|
602
784
|
*/
|
|
603
|
-
async validateDestinationCollateral(originTokenAmount, destination) {
|
|
785
|
+
async validateDestinationCollateral(originTokenAmount, destination, destinationToken) {
|
|
604
786
|
const valid = await this.isDestinationCollateralSufficient({
|
|
605
787
|
originTokenAmount,
|
|
606
788
|
destination,
|
|
789
|
+
destinationToken,
|
|
607
790
|
});
|
|
608
791
|
if (!valid) {
|
|
609
792
|
return { amount: 'Insufficient collateral on destination' };
|
|
@@ -613,24 +796,28 @@ export class WarpCore {
|
|
|
613
796
|
/**
|
|
614
797
|
* Ensure the sender has sufficient balances for minting
|
|
615
798
|
*/
|
|
616
|
-
async validateDestinationRateLimit(originTokenAmount, destination) {
|
|
799
|
+
async validateDestinationRateLimit(originTokenAmount, destination, destinationToken) {
|
|
617
800
|
const { token: originToken, amount } = originTokenAmount;
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
801
|
+
const resolvedDestinationToken = this.resolveDestinationToken({
|
|
802
|
+
originToken,
|
|
803
|
+
destination,
|
|
804
|
+
destinationToken,
|
|
805
|
+
});
|
|
806
|
+
if (!MINT_LIMITED_STANDARDS.includes(resolvedDestinationToken.standard)) {
|
|
807
|
+
this.logger.debug(`${resolvedDestinationToken.symbol} does not have rate limit constraint, skipping`);
|
|
623
808
|
return null;
|
|
624
809
|
}
|
|
625
810
|
let destinationMintLimit = 0n;
|
|
626
|
-
if (
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
811
|
+
if (resolvedDestinationToken.standard === TokenStandard.EvmHypVSXERC20 ||
|
|
812
|
+
resolvedDestinationToken.standard ===
|
|
813
|
+
TokenStandard.EvmHypVSXERC20Lockbox ||
|
|
814
|
+
resolvedDestinationToken.standard === TokenStandard.EvmHypXERC20 ||
|
|
815
|
+
resolvedDestinationToken.standard === TokenStandard.EvmHypXERC20Lockbox) {
|
|
816
|
+
const adapter = resolvedDestinationToken.getAdapter(this.multiProvider);
|
|
631
817
|
destinationMintLimit = await adapter.getMintLimit();
|
|
632
|
-
if (
|
|
633
|
-
|
|
818
|
+
if (resolvedDestinationToken.standard === TokenStandard.EvmHypVSXERC20 ||
|
|
819
|
+
resolvedDestinationToken.standard ===
|
|
820
|
+
TokenStandard.EvmHypVSXERC20Lockbox) {
|
|
634
821
|
const bufferCap = await adapter.getMintMaxLimit();
|
|
635
822
|
const max = bufferCap / 2n;
|
|
636
823
|
if (destinationMintLimit > max) {
|
|
@@ -639,11 +826,11 @@ export class WarpCore {
|
|
|
639
826
|
}
|
|
640
827
|
}
|
|
641
828
|
}
|
|
642
|
-
else if (
|
|
643
|
-
const adapter =
|
|
829
|
+
else if (resolvedDestinationToken.standard === TokenStandard.EvmHypCollateralFiat) {
|
|
830
|
+
const adapter = resolvedDestinationToken.getAdapter(this.multiProvider);
|
|
644
831
|
destinationMintLimit = await adapter.getMintLimit();
|
|
645
832
|
}
|
|
646
|
-
const destinationMintLimitInOriginDecimals = convertDecimalsToIntegerString(
|
|
833
|
+
const destinationMintLimitInOriginDecimals = convertDecimalsToIntegerString(resolvedDestinationToken.decimals, originToken.decimals, destinationMintLimit.toString());
|
|
647
834
|
const isSufficient = BigInt(destinationMintLimitInOriginDecimals) >= amount;
|
|
648
835
|
this.logger.debug(`${originTokenAmount.token.symbol} to ${destination} has ${isSufficient ? 'sufficient' : 'INSUFFICIENT'} rate limits`);
|
|
649
836
|
if (!isSufficient)
|
|
@@ -664,6 +851,24 @@ export class WarpCore {
|
|
|
664
851
|
}
|
|
665
852
|
return null;
|
|
666
853
|
}
|
|
854
|
+
resolveDestinationToken({ originToken, destination, destinationToken, }) {
|
|
855
|
+
const destinationName = this.multiProvider.getChainName(destination);
|
|
856
|
+
const destinationCandidates = originToken
|
|
857
|
+
.getConnections()
|
|
858
|
+
.filter((connection) => connection.token.chainName === destinationName)
|
|
859
|
+
.map((connection) => connection.token);
|
|
860
|
+
assert(destinationCandidates.length > 0, `No connection found for ${destinationName}`);
|
|
861
|
+
if (destinationToken) {
|
|
862
|
+
assert(destinationToken.chainName === destinationName, `Destination token chain mismatch for ${destinationName}`);
|
|
863
|
+
const matchedToken = destinationCandidates.find((candidate) => candidate.equals(destinationToken) ||
|
|
864
|
+
candidate.addressOrDenom.toLowerCase() ===
|
|
865
|
+
destinationToken.addressOrDenom.toLowerCase());
|
|
866
|
+
assert(matchedToken, `Destination token ${destinationToken.addressOrDenom} is not connected from ${originToken.chainName} to ${destinationName}`);
|
|
867
|
+
return matchedToken;
|
|
868
|
+
}
|
|
869
|
+
assert(destinationCandidates.length === 1, `Ambiguous route to ${destinationName}; specify destination token`);
|
|
870
|
+
return destinationCandidates[0];
|
|
871
|
+
}
|
|
667
872
|
/**
|
|
668
873
|
* Search through token list to find token with matching chain and address
|
|
669
874
|
*/
|