1
+ import { Argv } from 'yargs'
2
+ import { utils } from 'ethers'
3
+ import { L1TransactionReceipt , L1ToL2MessageStatus , L1ToL2MessageWriter } from '@arbitrum/sdk'
4
+
1
5
import { loadEnv , CLIArgs , CLIEnvironment } from '../../env'
2
6
import { logger } from '../../logging'
3
- import { getProvider , sendTransaction , toGRT } from '../../network'
4
- import { utils } from 'ethers'
5
- import { parseEther } from '@ethersproject/units'
6
- import {
7
- L1TransactionReceipt ,
8
- L1ToL2MessageStatus ,
9
- L1ToL2MessageWriter ,
10
- L1ToL2MessageGasEstimator ,
11
- } from '@arbitrum/sdk'
12
- import { chainIdIsL2 } from '../../utils'
7
+ import { getProvider , sendTransaction , toGRT , ensureAllowance , toBN } from '../../network'
8
+ import { chainIdIsL2 , estimateRetryableTxGas } from '../../cross-chain'
13
9
14
10
const logAutoRedeemReason = ( autoRedeemRec ) => {
15
11
if ( autoRedeemRec == null ) {
@@ -32,7 +28,12 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
32
28
logAutoRedeemReason ( autoRedeemRec )
33
29
logger . info ( 'Attempting to redeem...' )
34
30
await l1ToL2Message . redeem ( )
35
- l2TxHash = ( await l1ToL2Message . getSuccessfulRedeem ( ) ) . transactionHash
31
+ const redeemAttempt = await l1ToL2Message . getSuccessfulRedeem ( )
32
+ if ( redeemAttempt . status == L1ToL2MessageStatus . REDEEMED ) {
33
+ l2TxHash = redeemAttempt . l2TxReceipt ? redeemAttempt . l2TxReceipt . transactionHash : 'null'
34
+ } else {
35
+ throw new Error ( `Unexpected L1ToL2MessageStatus after redeem attempt: ${ res . status } ` )
36
+ }
36
37
} else if ( res . status != L1ToL2MessageStatus . REDEEMED ) {
37
38
throw new Error ( `Unexpected L1ToL2MessageStatus ${ res . status } ` )
38
39
}
@@ -41,75 +42,78 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
41
42
42
43
export const sendToL2 = async ( cli : CLIEnvironment , cliArgs : CLIArgs ) : Promise < void > => {
43
44
logger . info ( `>>> Sending tokens to L2 <<<\n` )
45
+
46
+ // parse provider
47
+ const l1Provider = cli . wallet . provider
44
48
const l2Provider = getProvider ( cliArgs . l2ProviderUrl )
49
+ const l1ChainId = cli . chainId
45
50
const l2ChainId = ( await l2Provider . getNetwork ( ) ) . chainId
46
-
47
- if ( chainIdIsL2 ( cli . chainId ) || ! chainIdIsL2 ( l2ChainId ) ) {
51
+ if ( chainIdIsL2 ( l1ChainId ) || ! chainIdIsL2 ( l2ChainId ) ) {
48
52
throw new Error (
49
53
'Please use an L1 provider in --provider-url, and an L2 provider in --l2-provider-url' ,
50
54
)
51
55
}
52
- const gateway = cli . contracts [ 'L1GraphTokenGateway' ]
53
- const l1GRT = cli . contracts [ 'GraphToken' ]
54
- const l1GRTAddress = l1GRT . address
56
+
57
+ // parse params
58
+ const { L1GraphTokenGateway : l1Gateway , GraphToken : l1GRT } = cli . contracts
55
59
const amount = toGRT ( cliArgs . amount )
56
- const recipient = cliArgs . recipient ? cliArgs . recipient : cli . wallet . address
57
- const l2Dest = await gateway . l2Counterpart ( )
60
+ const recipient = cliArgs . recipient ?? cli . wallet . address
61
+ const l1GatewayAddress = l1Gateway . address
62
+ const l2GatewayAddress = await l1Gateway . l2Counterpart ( )
63
+ const calldata = cliArgs . calldata ?? '0x'
58
64
65
+ // transport tokens
59
66
logger . info ( `Will send ${ cliArgs . amount } GRT to ${ recipient } ` )
60
- logger . info ( `Using L1 gateway ${ gateway . address } and L2 gateway ${ l2Dest } ` )
67
+ logger . info ( `Using L1 gateway ${ l1GatewayAddress } and L2 gateway ${ l2GatewayAddress } ` )
68
+ await ensureAllowance ( cli . wallet , l1GatewayAddress , l1GRT , amount )
69
+
70
+ // estimate L2 ticket
61
71
// See https://github.com/OffchainLabs/arbitrum/blob/master/packages/arb-ts/src/lib/bridge.ts
62
- const depositCalldata = await gateway . getOutboundCalldata (
63
- l1GRTAddress ,
72
+ const depositCalldata = await l1Gateway . getOutboundCalldata (
73
+ l1GRT . address ,
64
74
cli . wallet . address ,
65
75
recipient ,
66
76
amount ,
67
- '0x' ,
77
+ calldata ,
68
78
)
69
-
70
- // Comment from Offchain Labs' implementation:
71
- // we add a 0.05 ether "deposit" buffer to pay for execution in the gas estimation
72
- logger . info ( 'Estimating retryable ticket gas:' )
73
- const baseFee = ( await cli . wallet . provider . getBlock ( 'latest' ) ) . baseFeePerGas
74
- const gasEstimator = new L1ToL2MessageGasEstimator ( l2Provider )
75
- const gasParams = await gasEstimator . estimateMessage (
76
- gateway . address ,
77
- l2Dest ,
79
+ const { maxGas, gasPriceBid, maxSubmissionCost } = await estimateRetryableTxGas (
80
+ l1Provider ,
81
+ l2Provider ,
82
+ l1GatewayAddress ,
83
+ l2GatewayAddress ,
78
84
depositCalldata ,
79
- parseEther ( '0' ) ,
80
- baseFee ,
81
- gateway . address ,
82
- gateway . address ,
85
+ {
86
+ maxGas : cliArgs . maxGas ,
87
+ gasPriceBid : cliArgs . gasPriceBid ,
88
+ maxSubmissionCost : cliArgs . maxSubmissionCost ,
89
+ } ,
83
90
)
84
- const maxGas = gasParams . maxGasBid
85
- const gasPriceBid = gasParams . maxGasPriceBid
86
- const maxSubmissionPrice = gasParams . maxSubmissionPriceBid
91
+ const ethValue = maxSubmissionCost . add ( gasPriceBid . mul ( maxGas ) )
87
92
logger . info (
88
- `Using max gas: ${ maxGas } , gas price bid: ${ gasPriceBid } , max submission price : ${ maxSubmissionPrice } ` ,
93
+ `Using maxGas: ${ maxGas } , gasPriceBid: ${ gasPriceBid } , maxSubmissionCost: ${ maxSubmissionCost } = tx value : ${ ethValue } ` ,
89
94
)
90
95
91
- const ethValue = maxSubmissionPrice . add ( gasPriceBid . mul ( maxGas ) )
92
- logger . info ( `tx value: ${ ethValue } ` )
93
- const data = utils . defaultAbiCoder . encode ( [ 'uint256' , 'bytes' ] , [ maxSubmissionPrice , '0x' ] )
94
-
95
- const params = [ l1GRTAddress , recipient , amount , maxGas , gasPriceBid , data ]
96
- logger . info ( 'Approving token transfer' )
97
- await sendTransaction ( cli . wallet , l1GRT , 'approve' , [ gateway . address , amount ] )
96
+ // build transaction
98
97
logger . info ( 'Sending outbound transfer transaction' )
99
- const receipt = await sendTransaction ( cli . wallet , gateway , 'outboundTransfer' , params , {
98
+ const txData = utils . defaultAbiCoder . encode ( [ 'uint256' , 'bytes' ] , [ maxSubmissionCost , calldata ] )
99
+ const txParams = [ l1GRT . address , recipient , amount , maxGas , gasPriceBid , txData ]
100
+ const txReceipt = await sendTransaction ( cli . wallet , l1Gateway , 'outboundTransfer' , txParams , {
100
101
value : ethValue ,
101
102
} )
102
- const l1Receipt = new L1TransactionReceipt ( receipt )
103
- const l1ToL2Message = await l1Receipt . getL1ToL2Message ( cli . wallet . connect ( l2Provider ) )
104
-
105
- logger . info ( 'Waiting for message to propagate to L2...' )
106
- try {
107
- await checkAndRedeemMessage ( l1ToL2Message )
108
- } catch ( e ) {
109
- logger . error ( 'Auto redeem failed' )
110
- logger . error ( e )
111
- logger . error ( 'You can re-attempt using redeem-send-to-l2 with the following txHash:' )
112
- logger . error ( receipt . transactionHash )
103
+ // get l2 ticket status
104
+ if ( txReceipt . status == 1 ) {
105
+ logger . info ( 'Waiting for message to propagate to L2...' )
106
+ const l1Receipt = new L1TransactionReceipt ( txReceipt )
107
+ const l1ToL2Messages = await l1Receipt . getL1ToL2Messages ( cli . wallet . connect ( l2Provider ) )
108
+ const l1ToL2Message = l1ToL2Messages [ 0 ]
109
+ try {
110
+ await checkAndRedeemMessage ( l1ToL2Message )
111
+ } catch ( e ) {
112
+ logger . error ( 'Auto redeem failed' )
113
+ logger . error ( e )
114
+ logger . error ( 'You can re-attempt using redeem-send-to-l2 with the following txHash:' )
115
+ logger . error ( txReceipt . transactionHash )
116
+ }
113
117
}
114
118
}
115
119
@@ -135,8 +139,38 @@ export const redeemSendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Pro
135
139
}
136
140
137
141
export const sendToL2Command = {
138
- command : 'send-to-l2 <amount> [recipient]' ,
142
+ command : 'send-to-l2 <amount> [recipient] [calldata] ' ,
139
143
describe : 'Perform an L1-to-L2 Graph Token transaction' ,
144
+ builder : ( yargs : Argv ) : Argv => {
145
+ return yargs
146
+ . option ( 'max-gas' , {
147
+ description : 'Max gas for the L2 redemption attempt' ,
148
+ requiresArg : true ,
149
+ type : 'string' ,
150
+ } )
151
+ . option ( 'gas-price-bid' , {
152
+ description : 'Gas price for the L2 redemption attempt' ,
153
+ requiresArg : true ,
154
+ type : 'string' ,
155
+ } )
156
+ . option ( 'max-submission-cost' , {
157
+ description : 'Max submission cost for the retryable ticket' ,
158
+ requiresArg : true ,
159
+ type : 'string' ,
160
+ } )
161
+ . positional ( 'amount' , { description : 'Amount to send (will be converted to wei)' } )
162
+ . positional ( 'recipient' , {
163
+ description : 'Receiving address in L2. Same to L1 address if empty' ,
164
+ } )
165
+ . positional ( 'calldata' , {
166
+ description : 'Calldata to pass to the recipient. Must be whitelisted in the bridge' ,
167
+ } )
168
+ . coerce ( {
169
+ maxGas : toBN ,
170
+ gasPriceBid : toBN ,
171
+ maxSubmissionCost : toBN ,
172
+ } )
173
+ } ,
140
174
handler : async ( argv : CLIArgs ) : Promise < void > => {
141
175
return sendToL2 ( await loadEnv ( argv ) , argv )
142
176
} ,
0 commit comments