1+ import path from 'path'
2+
13import { BigNumber , ContractTransaction } from 'ethers'
24import { parseUnits } from 'ethers/lib/utils'
35import { HardhatRuntimeEnvironment } from 'hardhat/types'
46
5- import { createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat'
7+ import { OmniPointHardhat , createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat'
68import { createLogger } from '@layerzerolabs/io-devtools'
79import { ChainType , endpointIdToChainType , endpointIdToNetwork } from '@layerzerolabs/lz-definitions'
8- import { addressToBytes32 } from '@layerzerolabs/lz-v2-utilities'
9-
10- import layerzeroConfig from '../layerzero.config'
10+ import { Options , addressToBytes32 } from '@layerzerolabs/lz-v2-utilities'
1111
1212import { SendResult } from './types'
1313import { DebugLogger , KnownErrors , getLayerZeroScanLink } from './utils'
@@ -19,14 +19,29 @@ export interface EvmArgs {
1919 dstEid : number
2020 amount : string
2121 to : string
22+ oappConfig : string
2223 minAmount ?: string
23- extraOptions ?: string
24+ extraLzReceiveOptions ?: string [ ]
25+ extraLzComposeOptions ?: string [ ]
26+ extraNativeDropOptions ?: string [ ]
2427 composeMsg ?: string
2528 oftAddress ?: string
2629}
2730
2831export async function sendEvm (
29- { srcEid, dstEid, amount, to, minAmount, extraOptions, composeMsg, oftAddress } : EvmArgs ,
32+ {
33+ srcEid,
34+ dstEid,
35+ amount,
36+ to,
37+ oappConfig,
38+ minAmount,
39+ extraLzReceiveOptions,
40+ extraLzComposeOptions,
41+ extraNativeDropOptions,
42+ composeMsg,
43+ oftAddress,
44+ } : EvmArgs ,
3045 hre : HardhatRuntimeEnvironment
3146) : Promise < SendResult > {
3247 if ( endpointIdToChainType ( srcEid ) !== ChainType . EVM ) {
@@ -51,12 +66,13 @@ export async function sendEvm(
5166 if ( oftAddress ) {
5267 wrapperAddress = oftAddress
5368 } else {
54- const { contracts } = typeof layerzeroConfig === 'function' ? await layerzeroConfig ( ) : layerzeroConfig
55- const wrapper = contracts . find ( ( c ) => c . contract . eid === srcEid )
69+ const layerZeroConfig = ( await import ( path . resolve ( './' , oappConfig ) ) ) . default
70+ const { contracts } = typeof layerZeroConfig === 'function' ? await layerZeroConfig ( ) : layerZeroConfig
71+ const wrapper = contracts . find ( ( c : { contract : OmniPointHardhat } ) => c . contract . eid === srcEid )
5672 if ( ! wrapper ) throw new Error ( `No config for EID ${ srcEid } ` )
5773 wrapperAddress = wrapper . contract . contractName
5874 ? ( await srcEidHre . deployments . get ( wrapper . contract . contractName ) ) . address
59- : wrapper . contract . address !
75+ : wrapper . contract . address || ''
6076 }
6177
6278 // 2️⃣ load IOFT ABI, extend it with token()
@@ -78,18 +94,95 @@ export async function sendEvm(
7894 // hex string → Uint8Array → zero-pad to 32 bytes
7995 const toBytes = addressToBytes32 ( to )
8096
81- // 6️⃣ build sendParam and dispatch
97+ // 6️⃣ Build options dynamically using Options.newOptions()
98+ let options = Options . newOptions ( )
99+
100+ // Add lzReceive options
101+ if ( extraLzReceiveOptions && extraLzReceiveOptions . length > 0 ) {
102+ // Handle case where Hardhat's CSV parsing splits "gas,value" into separate elements
103+ if ( extraLzReceiveOptions . length % 2 !== 0 ) {
104+ throw new Error (
105+ `Invalid lzReceive options: received ${ extraLzReceiveOptions . length } values, but expected pairs of gas,value`
106+ )
107+ }
108+
109+ for ( let i = 0 ; i < extraLzReceiveOptions . length ; i += 2 ) {
110+ const gas = Number ( extraLzReceiveOptions [ i ] )
111+ const value = Number ( extraLzReceiveOptions [ i + 1 ] ) || 0
112+ options = options . addExecutorLzReceiveOption ( gas , value )
113+ logger . info ( `Added lzReceive option: ${ gas } gas, ${ value } value` )
114+ }
115+ }
116+
117+ // Add lzCompose options
118+ if ( extraLzComposeOptions && extraLzComposeOptions . length > 0 ) {
119+ // Handle case where Hardhat's CSV parsing splits "index,gas,value" into separate elements
120+ if ( extraLzComposeOptions . length % 3 !== 0 ) {
121+ throw new Error (
122+ `Invalid lzCompose options: received ${ extraLzComposeOptions . length } values, but expected triplets of index,gas,value`
123+ )
124+ }
125+
126+ for ( let i = 0 ; i < extraLzComposeOptions . length ; i += 3 ) {
127+ const index = Number ( extraLzComposeOptions [ i ] )
128+ const gas = Number ( extraLzComposeOptions [ i + 1 ] )
129+ const value = Number ( extraLzComposeOptions [ i + 2 ] ) || 0
130+ options = options . addExecutorComposeOption ( index , gas , value )
131+ logger . info ( `Added lzCompose option: index ${ index } , ${ gas } gas, ${ value } value` )
132+ }
133+ }
134+
135+ // Add native drop options
136+ if ( extraNativeDropOptions && extraNativeDropOptions . length > 0 ) {
137+ // Handle case where Hardhat's CSV parsing splits "amount,recipient" into separate elements
138+ if ( extraNativeDropOptions . length % 2 !== 0 ) {
139+ throw new Error (
140+ `Invalid native drop options: received ${ extraNativeDropOptions . length } values, but expected pairs of amount,recipient`
141+ )
142+ }
143+
144+ for ( let i = 0 ; i < extraNativeDropOptions . length ; i += 2 ) {
145+ const amountStr = extraNativeDropOptions [ i ]
146+ const recipient = extraNativeDropOptions [ i + 1 ]
147+
148+ if ( ! amountStr || ! recipient ) {
149+ throw new Error (
150+ `Invalid native drop option: Both amount and recipient must be provided. Got amount="${ amountStr } ", recipient="${ recipient } "`
151+ )
152+ }
153+
154+ try {
155+ options = options . addExecutorNativeDropOption ( amountStr . trim ( ) , recipient . trim ( ) )
156+ logger . info ( `Added native drop option: ${ amountStr . trim ( ) } wei to ${ recipient . trim ( ) } ` )
157+ } catch ( error ) {
158+ // Provide helpful context if the amount exceeds protocol limits
159+ const maxUint128 = BigInt ( '340282366920938463463374607431768211455' ) // 2^128 - 1
160+ const maxUint128Ether = Number ( maxUint128 ) / 1e18 // Convert to ETH for readability
161+
162+ throw new Error (
163+ `Failed to add native drop option with amount ${ amountStr . trim ( ) } wei. ` +
164+ `LayerZero protocol constrains native drop amounts to uint128 maximum ` +
165+ `(${ maxUint128 . toString ( ) } wei ≈ ${ maxUint128Ether . toFixed ( 2 ) } ETH). ` +
166+ `Original error: ${ error instanceof Error ? error . message : String ( error ) } `
167+ )
168+ }
169+ }
170+ }
171+
172+ const extraOptions = options . toHex ( )
173+
174+ // 7️⃣ build sendParam and dispatch
82175 const sendParam = {
83176 dstEid,
84177 to : toBytes ,
85178 amountLD : amountUnits . toString ( ) ,
86179 minAmountLD : minAmount ? parseUnits ( minAmount , decimals ) . toString ( ) : amountUnits . toString ( ) ,
87- extraOptions : extraOptions ? extraOptions . toString ( ) : '0x' ,
180+ extraOptions : extraOptions ,
88181 composeMsg : composeMsg ? composeMsg . toString ( ) : '0x' ,
89182 oftCmd : '0x' ,
90183 }
91184
92- // 6️⃣ Quote (MessagingFee = { nativeFee, lzTokenFee })
185+ // 8️⃣ Quote (MessagingFee = { nativeFee, lzTokenFee })
93186 logger . info ( 'Quoting the native gas cost for the send transaction...' )
94187 let msgFee : { nativeFee : BigNumber ; lzTokenFee : BigNumber }
95188 try {
0 commit comments