1
- import { JsonRpcProvider } from "@ethersproject/providers" ;
1
+ import { JsonRpcProvider , Log } from "@ethersproject/providers" ;
2
2
import { Wallet } from "ethers" ;
3
3
import {
4
4
ChildToParentRewardRouter__factory ,
@@ -12,72 +12,112 @@ import {
12
12
L2ToL1Message ,
13
13
L2ToL1MessageStatus ,
14
14
} from "../../lib/arbitrum-sdk/src" ;
15
+
16
+ import {
17
+ Chain ,
18
+ ChainContract ,
19
+ createPublicClient ,
20
+ createWalletClient ,
21
+ Hex ,
22
+ http ,
23
+ publicActions ,
24
+ } from 'viem'
25
+ import { privateKeyToAccount } from 'viem/accounts'
26
+ import {
27
+ getWithdrawals ,
28
+ GetWithdrawalStatusReturnType ,
29
+ publicActionsL1 ,
30
+ publicActionsL2 ,
31
+ walletActionsL1 ,
32
+ } from 'viem/op-stack'
33
+
15
34
const wait = async ( ms : number ) => new Promise ( ( res ) => setTimeout ( res , ms ) ) ;
16
35
17
- export default class ChildToParentMessageRedeemer {
18
- public startBlock : number ;
19
- public childToParentRewardRouter : ChildToParentRewardRouter ;
20
- public readonly retryDelay : number ;
36
+ export abstract class ChildToParentMessageRedeemer {
21
37
constructor (
22
- public readonly childChainProvider : JsonRpcProvider ,
23
- public readonly parentChainSigner : Wallet ,
38
+ public readonly childChainRpc : string ,
39
+ public readonly parentChainRpc : string ,
40
+ protected readonly parentChainPrivateKey : string ,
24
41
public readonly childToParentRewardRouterAddr : string ,
25
42
public readonly blockLag : number ,
26
- initialStartBlock : number ,
27
- retryDelay = 1000 * 60 * 10
28
- ) {
29
- this . startBlock = initialStartBlock ;
30
- this . childToParentRewardRouter = ChildToParentRewardRouter__factory . connect (
31
- childToParentRewardRouterAddr ,
32
- childChainProvider
33
- ) ;
34
- this . retryDelay = retryDelay ;
35
- }
43
+ public startBlock : number = 0 ,
44
+ public readonly retryDelay = 1000 * 60 * 10
45
+ ) { }
46
+
47
+ protected abstract _handleLogs ( logs : Log [ ] , oneOff : boolean ) : Promise < void > ;
36
48
37
49
public async redeemChildToParentMessages ( oneOff = false ) {
50
+ const childChainProvider = new JsonRpcProvider ( this . childChainRpc ) ;
51
+
38
52
const toBlock =
39
- ( await this . childChainProvider . getBlockNumber ( ) ) - this . blockLag ;
40
- const logs = await this . childChainProvider . getLogs ( {
53
+ ( await childChainProvider . getBlockNumber ( ) ) - this . blockLag ;
54
+ const logs = await childChainProvider . getLogs ( {
41
55
fromBlock : this . startBlock ,
42
56
toBlock : toBlock ,
43
- ...this . childToParentRewardRouter . filters . FundsRouted ( ) ,
57
+ address : this . childToParentRewardRouterAddr ,
58
+ topics : [ ChildToParentRewardRouter__factory . createInterface ( ) . getEventTopic ( 'FundsRouted' ) ] ,
44
59
} ) ;
45
60
if ( logs . length ) {
46
61
console . log (
47
62
`Found ${ logs . length } route events between blocks ${ this . startBlock } and ${ toBlock } `
48
63
) ;
49
64
}
65
+ await this . _handleLogs ( logs , oneOff ) ;
66
+ return toBlock
67
+ }
50
68
69
+ public async run ( oneOff = false ) {
70
+ while ( true ) {
71
+ let toBlock = 0
72
+ try {
73
+ toBlock = await this . redeemChildToParentMessages ( oneOff ) ;
74
+ } catch ( err ) {
75
+ console . log ( "err" , err ) ;
76
+ }
77
+ if ( oneOff ) {
78
+ break ;
79
+ } else {
80
+ this . startBlock = toBlock + 1 ;
81
+ await wait ( 1000 * 60 * 60 ) ;
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ export class ArbChildToParentMessageRedeemer extends ChildToParentMessageRedeemer {
88
+ protected async _handleLogs ( logs : Log [ ] , oneOff : boolean ) : Promise < void > {
89
+ const childChainProvider = new JsonRpcProvider ( this . childChainRpc ) ;
90
+ const parentChainSigner = new Wallet ( this . parentChainPrivateKey , new JsonRpcProvider ( this . parentChainRpc ) ) ;
51
91
for ( let log of logs ) {
52
92
const arbTransactionRec = new L2TransactionReceipt (
53
- await this . childChainProvider . getTransactionReceipt ( log . transactionHash )
93
+ await childChainProvider . getTransactionReceipt ( log . transactionHash )
54
94
) ;
55
95
let l2ToL1Events =
56
- ( await arbTransactionRec . getL2ToL1Events ( ) ) as EventArgs < L2ToL1TxEvent > [ ] ;
96
+ arbTransactionRec . getL2ToL1Events ( ) as EventArgs < L2ToL1TxEvent > [ ] ;
57
97
58
98
if ( l2ToL1Events . length != 1 ) {
59
99
throw new Error ( "Only 1 l2 to l1 message per tx supported" ) ;
60
100
}
61
101
62
102
for ( let l2ToL1Event of l2ToL1Events ) {
63
103
const l2ToL1Message = L2ToL1Message . fromEvent (
64
- this . parentChainSigner ,
104
+ parentChainSigner ,
65
105
l2ToL1Event
66
106
) ;
67
107
if ( ! oneOff ) {
68
108
console . log ( `Waiting for ${ l2ToL1Event . hash } to be ready:` ) ;
69
109
await l2ToL1Message . waitUntilReadyToExecute (
70
- this . childChainProvider ,
110
+ childChainProvider ,
71
111
this . retryDelay
72
112
) ;
73
113
}
74
114
75
- const status = await l2ToL1Message . status ( this . childChainProvider ) ;
115
+ const status = await l2ToL1Message . status ( childChainProvider ) ;
76
116
switch ( status ) {
77
117
case L2ToL1MessageStatus . CONFIRMED : {
78
118
console . log ( l2ToL1Event . hash , "confirmed; executing:" ) ;
79
119
const rec = await (
80
- await l2ToL1Message . execute ( this . childChainProvider )
120
+ await l2ToL1Message . execute ( childChainProvider )
81
121
) . wait ( 2 ) ;
82
122
console . log ( `${ l2ToL1Event . hash } executed:` , rec . transactionHash ) ;
83
123
break ;
@@ -96,21 +136,128 @@ export default class ChildToParentMessageRedeemer {
96
136
}
97
137
}
98
138
}
99
- this . startBlock = toBlock ;
100
139
}
140
+ }
101
141
102
- public async run ( oneOff = false ) {
103
- while ( true ) {
142
+
143
+ export type OpChildChainConfig = Chain & {
144
+ contracts : {
145
+ portal : { [ x : number ] : ChainContract }
146
+ disputeGameFactory : { [ x : number ] : ChainContract }
147
+ }
148
+ }
149
+
150
+ export class OpChildToParentMessageRedeemer extends ChildToParentMessageRedeemer {
151
+ public readonly childChainViemProvider
152
+ public readonly parentChainViemSigner
153
+
154
+ constructor (
155
+ childChainRpc : string ,
156
+ parentChainRpc : string ,
157
+ parentChainPrivateKey : string ,
158
+ childToParentRewardRouterAddr : string ,
159
+ blockLag : number ,
160
+ startBlock : number = 0 ,
161
+ public readonly childChainViem : OpChildChainConfig ,
162
+ public readonly parentChainViem : Chain ,
163
+ retryDelay = 1000 * 60 * 10 ,
164
+ ) {
165
+ super (
166
+ childChainRpc ,
167
+ parentChainRpc ,
168
+ parentChainPrivateKey ,
169
+ childToParentRewardRouterAddr ,
170
+ blockLag ,
171
+ startBlock ,
172
+ retryDelay
173
+ )
174
+
175
+ this . childChainViemProvider = createPublicClient ( {
176
+ chain : childChainViem ,
177
+ transport : http ( childChainRpc ) ,
178
+ } ) . extend ( publicActionsL2 ( ) )
179
+
180
+ this . parentChainViemSigner = createWalletClient ( {
181
+ chain : parentChainViem ,
182
+ account : privateKeyToAccount (
183
+ parentChainPrivateKey as `0x${string } `
184
+ ) ,
185
+ transport : http ( parentChainRpc ) ,
186
+ } )
187
+ . extend ( publicActions )
188
+ . extend ( walletActionsL1 ( ) )
189
+ . extend ( publicActionsL1 ( ) )
190
+ }
191
+
192
+ protected async _handleLogs ( logs : Log [ ] , oneOff : boolean ) : Promise < void > {
193
+ if ( ! oneOff ) throw new Error ( 'OpChildToParentMessageRedeemer only supports one-off mode' )
194
+ for ( const log of logs ) {
195
+ const receipt = await this . childChainViemProvider . getTransactionReceipt ( {
196
+ hash : log . transactionHash as Hex ,
197
+ } )
198
+
199
+ // 'waiting-to-prove'
200
+ // 'ready-to-prove'
201
+ // 'waiting-to-finalize'
202
+ // 'ready-to-finalize'
203
+ // 'finalized'
204
+ let status : GetWithdrawalStatusReturnType ;
104
205
try {
105
- await this . redeemChildToParentMessages ( oneOff ) ;
106
- } catch ( err ) {
107
- console . log ( "err" , err ) ;
206
+ status = await this . parentChainViemSigner . getWithdrawalStatus ( {
207
+ receipt,
208
+ targetChain : this . childChainViemProvider . chain ,
209
+ } )
210
+ } catch ( e : any ) {
211
+ // workaround
212
+ if ( e . metaMessages [ 0 ] === 'Error: Unproven()' ) {
213
+ status = 'ready-to-prove'
214
+ }
215
+ else {
216
+ throw e ;
217
+ }
108
218
}
109
- if ( oneOff ) {
110
- break ;
111
- } else {
112
- await wait ( 1000 * 60 * 60 ) ;
219
+
220
+ console . log ( `${ log . transactionHash } ${ status } ` )
221
+
222
+ if ( status === 'ready-to-prove' ) {
223
+ // 1. Get withdrawal information
224
+ const [ withdrawal ] = getWithdrawals ( receipt )
225
+ const output = await this . parentChainViemSigner . getL2Output ( {
226
+ l2BlockNumber : receipt . blockNumber ,
227
+ targetChain : this . childChainViem ,
228
+ } )
229
+ // 2. Build parameters to prove the withdrawal on the L2.
230
+ const args = await this . childChainViemProvider . buildProveWithdrawal ( {
231
+ output,
232
+ withdrawal,
233
+ } )
234
+ // 3. Prove the withdrawal on the L1.
235
+ const hash = await this . parentChainViemSigner . proveWithdrawal ( args )
236
+ // 4. Wait until the prove withdrawal is processed.
237
+ await this . parentChainViemSigner . waitForTransactionReceipt ( {
238
+ hash,
239
+ } )
240
+
241
+ console . log ( `${ log . transactionHash } proved:` , hash )
242
+ } else if ( status === 'ready-to-finalize' ) {
243
+ const [ withdrawal ] = getWithdrawals ( receipt )
244
+
245
+ // 1. Wait until the withdrawal is ready to finalize. (done)
246
+
247
+ // 2. Finalize the withdrawal.
248
+ const hash = await this . parentChainViemSigner . finalizeWithdrawal ( {
249
+ targetChain : this . childChainViemProvider . chain ,
250
+ withdrawal,
251
+ } )
252
+
253
+ // 3. Wait until the withdrawal is finalized.
254
+ await this . parentChainViemSigner . waitForTransactionReceipt ( {
255
+ hash,
256
+ } )
257
+
258
+ console . log ( `${ log . transactionHash } finalized:` , hash )
113
259
}
114
260
}
115
261
}
116
262
}
263
+
0 commit comments