|
|
|
@ -1,36 +1,30 @@ |
|
|
|
|
import { input } from '@inquirer/prompts'; |
|
|
|
|
import { PopulatedTransaction, ethers } from 'ethers'; |
|
|
|
|
import { select } from '@inquirer/prompts'; |
|
|
|
|
import { ethers } from 'ethers'; |
|
|
|
|
|
|
|
|
|
import { |
|
|
|
|
ERC20__factory, |
|
|
|
|
HypERC20Collateral__factory, |
|
|
|
|
HypERC20__factory, |
|
|
|
|
} from '@hyperlane-xyz/core'; |
|
|
|
|
import { |
|
|
|
|
ChainName, |
|
|
|
|
EvmHypCollateralAdapter, |
|
|
|
|
EvmHypNativeAdapter, |
|
|
|
|
EvmHypSyntheticAdapter, |
|
|
|
|
HyperlaneContractsMap, |
|
|
|
|
HyperlaneCore, |
|
|
|
|
IHypTokenAdapter, |
|
|
|
|
MultiProtocolProvider, |
|
|
|
|
MultiProvider, |
|
|
|
|
TokenType, |
|
|
|
|
ProviderType, |
|
|
|
|
TokenAmount, |
|
|
|
|
WarpCore, |
|
|
|
|
WarpCoreConfig, |
|
|
|
|
} from '@hyperlane-xyz/sdk'; |
|
|
|
|
import { Address, timeout } from '@hyperlane-xyz/utils'; |
|
|
|
|
|
|
|
|
|
import { log, logBlue, logGreen } from '../../logger.js'; |
|
|
|
|
import { logBlue, logGreen, logRed } from '../../logger.js'; |
|
|
|
|
import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; |
|
|
|
|
import { getContext, getMergedContractAddresses } from '../context.js'; |
|
|
|
|
import { runPreflightChecks } from '../deploy/utils.js'; |
|
|
|
|
import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; |
|
|
|
|
import { runSingleChainSelectionStep } from '../utils/chains.js'; |
|
|
|
|
|
|
|
|
|
export async function sendTestTransfer({ |
|
|
|
|
key, |
|
|
|
|
chainConfigPath, |
|
|
|
|
coreArtifactsPath, |
|
|
|
|
warpConfigPath, |
|
|
|
|
origin, |
|
|
|
|
destination, |
|
|
|
|
routerAddress, |
|
|
|
@ -42,6 +36,7 @@ export async function sendTestTransfer({ |
|
|
|
|
key: string; |
|
|
|
|
chainConfigPath: string; |
|
|
|
|
coreArtifactsPath?: string; |
|
|
|
|
warpConfigPath: string; |
|
|
|
|
origin?: ChainName; |
|
|
|
|
destination?: ChainName; |
|
|
|
|
routerAddress?: Address; |
|
|
|
@ -50,11 +45,12 @@ export async function sendTestTransfer({ |
|
|
|
|
timeoutSec: number; |
|
|
|
|
skipWaitForDelivery: boolean; |
|
|
|
|
}) { |
|
|
|
|
const { signer, multiProvider, customChains, coreArtifacts } = |
|
|
|
|
const { signer, multiProvider, customChains, coreArtifacts, warpCoreConfig } = |
|
|
|
|
await getContext({ |
|
|
|
|
chainConfigPath, |
|
|
|
|
coreConfig: { coreArtifactsPath }, |
|
|
|
|
keyConfig: { key }, |
|
|
|
|
warpConfig: { warpConfigPath }, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (!origin) { |
|
|
|
@ -71,51 +67,6 @@ export async function sendTestTransfer({ |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!routerAddress) { |
|
|
|
|
routerAddress = await input({ |
|
|
|
|
message: 'Please specify the router address', |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: move to SDK token router app
|
|
|
|
|
// deduce TokenType
|
|
|
|
|
// 1. decimals() call implies synthetic
|
|
|
|
|
// 2. wrappedToken() call implies collateral
|
|
|
|
|
// 3. if neither, it's native
|
|
|
|
|
let tokenAddress: Address | undefined; |
|
|
|
|
let tokenType: TokenType; |
|
|
|
|
const provider = multiProvider.getProvider(origin); |
|
|
|
|
try { |
|
|
|
|
const synthRouter = HypERC20__factory.connect(routerAddress, provider); |
|
|
|
|
await synthRouter.decimals(); |
|
|
|
|
tokenType = TokenType.synthetic; |
|
|
|
|
tokenAddress = routerAddress; |
|
|
|
|
} catch (error) { |
|
|
|
|
try { |
|
|
|
|
const collateralRouter = HypERC20Collateral__factory.connect( |
|
|
|
|
routerAddress, |
|
|
|
|
provider, |
|
|
|
|
); |
|
|
|
|
tokenAddress = await collateralRouter.wrappedToken(); |
|
|
|
|
tokenType = TokenType.collateral; |
|
|
|
|
} catch (error) { |
|
|
|
|
tokenType = TokenType.native; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (tokenAddress) { |
|
|
|
|
// checks token balances for collateral and synthetic
|
|
|
|
|
await assertTokenBalance( |
|
|
|
|
multiProvider, |
|
|
|
|
signer, |
|
|
|
|
origin, |
|
|
|
|
tokenAddress, |
|
|
|
|
wei.toString(), |
|
|
|
|
); |
|
|
|
|
} else { |
|
|
|
|
await assertNativeBalances(multiProvider, signer, [origin], wei.toString()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
await runPreflightChecks({ |
|
|
|
|
origin, |
|
|
|
|
remotes: [destination], |
|
|
|
@ -129,8 +80,8 @@ export async function sendTestTransfer({ |
|
|
|
|
executeDelivery({ |
|
|
|
|
origin, |
|
|
|
|
destination, |
|
|
|
|
warpCoreConfig, |
|
|
|
|
routerAddress, |
|
|
|
|
tokenType, |
|
|
|
|
wei, |
|
|
|
|
recipient, |
|
|
|
|
signer, |
|
|
|
@ -146,8 +97,8 @@ export async function sendTestTransfer({ |
|
|
|
|
async function executeDelivery({ |
|
|
|
|
origin, |
|
|
|
|
destination, |
|
|
|
|
warpCoreConfig, |
|
|
|
|
routerAddress, |
|
|
|
|
tokenType, |
|
|
|
|
wei, |
|
|
|
|
recipient, |
|
|
|
|
multiProvider, |
|
|
|
@ -157,8 +108,8 @@ async function executeDelivery({ |
|
|
|
|
}: { |
|
|
|
|
origin: ChainName; |
|
|
|
|
destination: ChainName; |
|
|
|
|
routerAddress: Address; |
|
|
|
|
tokenType: TokenType; |
|
|
|
|
warpCoreConfig: WarpCoreConfig; |
|
|
|
|
routerAddress?: Address; |
|
|
|
|
wei: string; |
|
|
|
|
recipient?: string; |
|
|
|
|
multiProvider: MultiProvider; |
|
|
|
@ -179,74 +130,75 @@ async function executeDelivery({ |
|
|
|
|
const provider = multiProvider.getProvider(origin); |
|
|
|
|
const connectedSigner = signer.connect(provider); |
|
|
|
|
|
|
|
|
|
// TODO replace all code below with WarpCore
|
|
|
|
|
// https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3259
|
|
|
|
|
const warpCore = WarpCore.FromConfig( |
|
|
|
|
MultiProtocolProvider.fromMultiProvider(multiProvider), |
|
|
|
|
warpCoreConfig, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (tokenType === TokenType.collateral) { |
|
|
|
|
const wrappedToken = await getWrappedToken(routerAddress, provider); |
|
|
|
|
const token = ERC20__factory.connect(wrappedToken, connectedSigner); |
|
|
|
|
const approval = await token.allowance(signerAddress, routerAddress); |
|
|
|
|
if (approval.lt(wei)) { |
|
|
|
|
const approveTx = await token.approve(routerAddress, wei); |
|
|
|
|
await approveTx.wait(); |
|
|
|
|
if (!routerAddress) { |
|
|
|
|
const tokensForRoute = warpCore.getTokensForRoute(origin, destination); |
|
|
|
|
if (tokensForRoute.length === 0) { |
|
|
|
|
logRed(`No Warp Routes found from ${origin} to ${destination}`); |
|
|
|
|
throw new Error('Error finding warp route'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
routerAddress = (await select({ |
|
|
|
|
message: `Select router address`, |
|
|
|
|
choices: [ |
|
|
|
|
...tokensForRoute.map((t) => ({ |
|
|
|
|
value: t.addressOrDenom, |
|
|
|
|
description: `${t.name} ($${t.symbol})`, |
|
|
|
|
})), |
|
|
|
|
], |
|
|
|
|
pageSize: 10, |
|
|
|
|
})) as string; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let adapter: IHypTokenAdapter<PopulatedTransaction>; |
|
|
|
|
const multiProtocolProvider = |
|
|
|
|
MultiProtocolProvider.fromMultiProvider(multiProvider); |
|
|
|
|
if (tokenType === TokenType.native) { |
|
|
|
|
adapter = new EvmHypNativeAdapter(origin, multiProtocolProvider, { |
|
|
|
|
token: routerAddress, |
|
|
|
|
}); |
|
|
|
|
} else if (tokenType === TokenType.collateral) { |
|
|
|
|
adapter = new EvmHypCollateralAdapter(origin, multiProtocolProvider, { |
|
|
|
|
token: routerAddress, |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
adapter = new EvmHypSyntheticAdapter(origin, multiProtocolProvider, { |
|
|
|
|
token: routerAddress, |
|
|
|
|
}); |
|
|
|
|
const token = warpCore.findToken(origin, routerAddress); |
|
|
|
|
if (!token) { |
|
|
|
|
logRed( |
|
|
|
|
`No Warp Routes found from ${origin} to ${destination} with router address ${routerAddress}`, |
|
|
|
|
); |
|
|
|
|
throw new Error('Error finding warp route'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const senderAddress = await signer.getAddress(); |
|
|
|
|
const errors = await warpCore.validateTransfer({ |
|
|
|
|
originTokenAmount: token.amount(wei), |
|
|
|
|
destination, |
|
|
|
|
recipient: recipient ?? senderAddress, |
|
|
|
|
sender: senderAddress, |
|
|
|
|
}); |
|
|
|
|
if (errors) { |
|
|
|
|
logRed('Unable to validate transfer', errors); |
|
|
|
|
throw new Error('Error validating transfer'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const transferTxs = await warpCore.getTransferRemoteTxs({ |
|
|
|
|
originTokenAmount: new TokenAmount(wei, token), |
|
|
|
|
destination, |
|
|
|
|
sender: senderAddress, |
|
|
|
|
recipient: recipient ?? senderAddress, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const txReceipts = []; |
|
|
|
|
for (const tx of transferTxs) { |
|
|
|
|
if (tx.type === ProviderType.EthersV5) { |
|
|
|
|
const txResponse = await connectedSigner.sendTransaction(tx.transaction); |
|
|
|
|
const txReceipt = await multiProvider.handleTx(origin, txResponse); |
|
|
|
|
txReceipts.push(txReceipt); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const destinationDomain = multiProvider.getDomainId(destination); |
|
|
|
|
log('Fetching interchain gas quote'); |
|
|
|
|
const interchainGas = await adapter.quoteTransferRemoteGas(destinationDomain); |
|
|
|
|
log('Interchain gas quote:', interchainGas); |
|
|
|
|
const transferTx = (await adapter.populateTransferRemoteTx({ |
|
|
|
|
weiAmountOrId: wei, |
|
|
|
|
destination: destinationDomain, |
|
|
|
|
recipient, |
|
|
|
|
interchainGas, |
|
|
|
|
})) as ethers.PopulatedTransaction; |
|
|
|
|
|
|
|
|
|
const txResponse = await connectedSigner.sendTransaction(transferTx); |
|
|
|
|
const txReceipt = await multiProvider.handleTx(origin, txResponse); |
|
|
|
|
|
|
|
|
|
const message = core.getDispatchedMessages(txReceipt)[0]; |
|
|
|
|
const transferTxReceipt = txReceipts[txReceipts.length - 1]; |
|
|
|
|
|
|
|
|
|
const message = core.getDispatchedMessages(transferTxReceipt)[0]; |
|
|
|
|
logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`); |
|
|
|
|
logBlue(`Message ID: ${message.id}`); |
|
|
|
|
|
|
|
|
|
if (skipWaitForDelivery) return; |
|
|
|
|
|
|
|
|
|
// Max wait 10 minutes
|
|
|
|
|
await core.waitForMessageProcessed(txReceipt, 10000, 60); |
|
|
|
|
await core.waitForMessageProcessed(transferTxReceipt, 10000, 60); |
|
|
|
|
logGreen(`Transfer sent to destination chain!`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async function getWrappedToken( |
|
|
|
|
address: Address, |
|
|
|
|
provider: ethers.providers.Provider, |
|
|
|
|
): Promise<Address> { |
|
|
|
|
try { |
|
|
|
|
const contract = HypERC20Collateral__factory.connect(address, provider); |
|
|
|
|
const wrappedToken = await contract.wrappedToken(); |
|
|
|
|
if (ethers.utils.isAddress(wrappedToken)) return wrappedToken; |
|
|
|
|
else throw new Error('Invalid wrapped token address'); |
|
|
|
|
} catch (error) { |
|
|
|
|
log('Error getting wrapped token', error); |
|
|
|
|
throw new Error( |
|
|
|
|
`Could not get wrapped token from router address ${address}`, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|