-
Notifications
You must be signed in to change notification settings - Fork 271
Expand file tree
/
Copy pathsendEvm.ts
More file actions
142 lines (137 loc) · 5.77 KB
/
sendEvm.ts
File metadata and controls
142 lines (137 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import bs58 from 'bs58'
import { BigNumber, ContractTransaction } from 'ethers'
import { parseUnits } from 'ethers/lib/utils'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { makeBytes32 } from '@layerzerolabs/devtools'
import { createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat'
import { createLogger, promptToContinue } from '@layerzerolabs/io-devtools'
import { ChainType, endpointIdToChainType, endpointIdToNetwork } from '@layerzerolabs/lz-definitions'
import layerzeroConfig from '../../layerzero.config'
import { SendResult } from '../common/types'
import { DebugLogger, KnownErrors, MSG_TYPE, isEmptyOptionsEvm } from '../common/utils'
import { getLayerZeroScanLink } from '../solana'
const logger = createLogger()
export interface EvmArgs {
srcEid: number
dstEid: number
amount: string
to: string
minAmount?: string
extraOptions?: string
composeMsg?: string
oftAddress?: string
}
export async function sendEvm(
{ srcEid, dstEid, amount, to, minAmount, extraOptions, composeMsg, oftAddress }: EvmArgs,
hre: HardhatRuntimeEnvironment
): Promise<SendResult> {
if (endpointIdToChainType(srcEid) !== ChainType.EVM) {
throw new Error(`non-EVM srcEid (${srcEid}) not supported here`)
}
const getHreByEid = createGetHreByEid(hre)
let srcEidHre: HardhatRuntimeEnvironment
try {
srcEidHre = await getHreByEid(srcEid)
} catch (error) {
DebugLogger.printErrorAndFixSuggestion(
KnownErrors.ERROR_GETTING_HRE,
`For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}`
)
throw error
}
const signer = await srcEidHre.ethers.getNamedSigner('deployer')
// 1️⃣ resolve the OFT wrapper address
let wrapperAddress: string
if (oftAddress) {
wrapperAddress = oftAddress
} else {
const { contracts } = typeof layerzeroConfig === 'function' ? await layerzeroConfig() : layerzeroConfig
const wrapper = contracts.find((c) => c.contract.eid === srcEid)
if (!wrapper) throw new Error(`No config for EID ${srcEid}`)
if (wrapper.contract.contractName) {
wrapperAddress = (await srcEidHre.deployments.get(wrapper.contract.contractName)).address
} else if (wrapper.contract.address) {
wrapperAddress = wrapper.contract.address
} else {
throw new Error(`No contract address found for EID ${srcEid}`)
}
}
// 2️⃣ load OFT ABI
const oftArtifact = await srcEidHre.artifacts.readArtifact('OFT')
const oft = await srcEidHre.ethers.getContractAt(oftArtifact.abi, wrapperAddress, signer)
// 3️⃣ fetch the underlying ERC-20
const underlying = await oft.token()
// 4️⃣ fetch decimals from the underlying token
const erc20 = await srcEidHre.ethers.getContractAt('ERC20', underlying, signer)
const decimals: number = await erc20.decimals()
// 5️⃣ normalize the user-supplied amount
const amountUnits: BigNumber = parseUnits(amount, decimals)
// Decide how to encode `to` based on target chain:
const dstChain = endpointIdToChainType(dstEid)
let toBytes: string
if (dstChain === ChainType.SOLANA) {
// Base58→32-byte buffer
toBytes = makeBytes32(bs58.decode(to))
} else {
// hex string → Uint8Array → zero-pad to 32 bytes
toBytes = makeBytes32(to)
}
// 6️⃣ build sendParam and dispatch
const sendParam = {
dstEid,
to: toBytes,
amountLD: amountUnits.toString(),
minAmountLD: minAmount ? parseUnits(minAmount, decimals).toString() : amountUnits.toString(),
extraOptions: extraOptions ? extraOptions.toString() : '0x',
composeMsg: composeMsg ? composeMsg.toString() : '0x',
oftCmd: '0x',
}
// Check whether there are extra options or enforced options. If not, warn the user.
// Read on Message Options: https://docs.layerzero.network/v2/concepts/message-options
if (!extraOptions) {
try {
const enforcedOptions = composeMsg
? await oft.enforcedOptions(dstEid, MSG_TYPE.SEND_AND_CALL)
: await oft.enforcedOptions(dstEid, MSG_TYPE.SEND)
if (isEmptyOptionsEvm(enforcedOptions)) {
const proceed = await promptToContinue(
'No extra options were included and OFT has no set enforced options. Your quote / send will most likely fail. Continue?'
)
if (!proceed) {
throw new Error('Aborted due to missing options')
}
}
} catch (error) {
logger.debug(`Failed to check enforced options: ${error}`)
}
}
// 6️⃣ Quote (MessagingFee = { nativeFee, lzTokenFee })
logger.info('Quoting the native gas cost for the send transaction...')
let msgFee: { nativeFee: BigNumber; lzTokenFee: BigNumber }
try {
msgFee = await oft.quoteSend(sendParam, false)
} catch (error) {
DebugLogger.printErrorAndFixSuggestion(
KnownErrors.ERROR_QUOTING_NATIVE_GAS_COST,
`For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}`
)
throw error
}
logger.info('Sending the transaction...')
let tx: ContractTransaction
try {
tx = await oft.send(sendParam, msgFee, signer.address, {
value: msgFee.nativeFee,
})
} catch (error) {
DebugLogger.printErrorAndFixSuggestion(
KnownErrors.ERROR_SENDING_TRANSACTION,
`For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}`
)
throw error
}
const receipt = await tx.wait()
const txHash = receipt.transactionHash
const scanLink = getLayerZeroScanLink(txHash, srcEid >= 40_000 && srcEid < 50_000)
return { txHash, scanLink }
}