11// docs:start:setup
2- import { privateKeyToAccount } from "viem/accounts" ;
3- import { decodeEventLog , getContract , pad } from "viem" ;
4- import { foundry } from "viem/chains" ;
2+ import { decodeEventLog , pad } from "@aztec/viem" ;
3+ import { foundry } from "@aztec/viem/chains" ;
54import { AztecAddress , EthAddress } from "@aztec/aztec.js/addresses" ;
65import { Fr } from "@aztec/aztec.js/fields" ;
76import { createAztecNodeClient } from "@aztec/aztec.js/node" ;
@@ -13,40 +12,41 @@ import { TestWallet } from "@aztec/test-wallet/server";
1312import { getInitialTestAccountsData } from "@aztec/accounts/testing" ;
1413import { createExtendedL1Client } from "@aztec/ethereum/client" ;
1514import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract" ;
15+ import { CheckpointNumber } from "@aztec/foundation/branded-types" ;
16+ import { RollupContract } from "@aztec/ethereum/contracts" ;
1617import SimpleNFT from "../../../target/solidity/nft_bridge/SimpleNFT.sol/SimpleNFT.json" with { type : "json" } ;
1718import NFTPortal from "../../../target/solidity/nft_bridge/NFTPortal.sol/NFTPortal.json" with { type : "json" } ;
1819import { NFTPunkContract } from "./artifacts/NFTPunk.js" ;
1920import { NFTBridgeContract } from "./artifacts/NFTBridge.js" ;
2021
21- // Setup L1 client using anvil's 1st account which should have a ton of ETH already
22- const l1Account = privateKeyToAccount (
23- "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
24- ) ;
25- const l1Client = createExtendedL1Client (
26- [ "http://localhost:8545" ] ,
27- l1Account ,
28- foundry
29- ) ;
22+ // Setup L1 client using anvil's default mnemonic (same as e2e tests)
23+ const MNEMONIC = "test test test test test test test test test test test junk" ;
24+ const l1Client = createExtendedL1Client ( [ "http://localhost:8545" ] , MNEMONIC ) ;
25+ const ownerEthAddress = l1Client . account . address ;
3026
3127// Setup L2 using Aztec's local network and one of its initial accounts
32- console . log ( "🔮 Setting up L2...\n" ) ;
28+ console . log ( "Setting up L2...\n" ) ;
3329const node = createAztecNodeClient ( "http://localhost:8080" ) ;
3430const aztecWallet = await TestWallet . create ( node ) ;
3531const [ accData ] = await getInitialTestAccountsData ( ) ;
3632const account = await aztecWallet . createSchnorrAccount (
3733 accData . secret ,
3834 accData . salt
3935) ;
40- console . log ( `✅ Account: ${ account . address . toString ( ) } \n` ) ;
36+ console . log ( `Account: ${ account . address . toString ( ) } \n` ) ;
4137
4238// Get node info
4339const nodeInfo = await node . getNodeInfo ( ) ;
4440const registryAddress = nodeInfo . l1ContractAddresses . registryAddress . toString ( ) ;
4541const inboxAddress = nodeInfo . l1ContractAddresses . inboxAddress . toString ( ) ;
42+ const rollupAddress = nodeInfo . l1ContractAddresses . rollupAddress . toString ( ) ;
43+
44+ // Create rollup contract instance for querying epoch information
45+ const rollup = new RollupContract ( l1Client , rollupAddress ) ;
4646// docs:end:setup
4747
4848// docs:start:deploy_l1_contracts
49- console . log ( "📦 Deploying L1 contracts...\n" ) ;
49+ console . log ( "Deploying L1 contracts...\n" ) ;
5050
5151const { address : nftAddress } = await deployL1Contract (
5252 l1Client ,
@@ -60,12 +60,12 @@ const { address: portalAddress } = await deployL1Contract(
6060 NFTPortal . bytecode . object as `0x${string } `
6161) ;
6262
63- console . log ( `✅ SimpleNFT: ${ nftAddress } ` ) ;
64- console . log ( `✅ NFTPortal: ${ portalAddress } \n` ) ;
63+ console . log ( `SimpleNFT: ${ nftAddress } ` ) ;
64+ console . log ( `NFTPortal: ${ portalAddress } \n` ) ;
6565// docs:end:deploy_l1_contracts
6666
6767// docs:start:deploy_l2_contracts
68- console . log ( "📦 Deploying L2 contracts...\n" ) ;
68+ console . log ( "Deploying L2 contracts...\n" ) ;
6969
7070const l2Nft = await NFTPunkContract . deploy ( aztecWallet , account . address )
7171 . send ( { from : account . address } )
@@ -75,36 +75,28 @@ const l2Bridge = await NFTBridgeContract.deploy(aztecWallet, l2Nft.address)
7575 . send ( { from : account . address } )
7676 . deployed ( ) ;
7777
78- console . log ( `✅ L2 NFT: ${ l2Nft . address . toString ( ) } ` ) ;
79- console . log ( `✅ L2 Bridge: ${ l2Bridge . address . toString ( ) } \n` ) ;
78+ console . log ( `L2 NFT: ${ l2Nft . address . toString ( ) } ` ) ;
79+ console . log ( `L2 Bridge: ${ l2Bridge . address . toString ( ) } \n` ) ;
8080// docs:end:deploy_l2_contracts
8181
8282// docs:start:initialize_portal
83- console . log ( "🔧 Initializing portal..." ) ;
83+ console . log ( "Initializing portal..." ) ;
8484
85- const nftContract = getContract ( {
86- address : nftAddress . toString ( ) as `0x${string } `,
87- abi : SimpleNFT . abi ,
88- client : l1Client ,
89- } ) ;
90- const portalContract = getContract ( {
85+ // Initialize the portal contract
86+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
87+ const initHash = await l1Client . writeContract ( {
9188 address : portalAddress . toString ( ) as `0x${string } `,
9289 abi : NFTPortal . abi ,
93- client : l1Client ,
90+ functionName : "initialize" ,
91+ args : [ registryAddress , nftAddress . toString ( ) , l2Bridge . address . toString ( ) ] ,
9492} ) ;
93+ await l1Client . waitForTransactionReceipt ( { hash : initHash } ) ;
9594
96- const hash = await portalContract . write . initialize ( [
97- registryAddress as `0x${string } `,
98- nftAddress . toString ( ) as `0x${string } `,
99- l2Bridge . address . toString ( ) as `0x${string } `,
100- ] ) ;
101- await l1Client . waitForTransactionReceipt ( { hash } ) ;
102-
103- console . log ( "✅ Portal initialized\n" ) ;
95+ console . log ( "Portal initialized\n" ) ;
10496// docs:end:initialize_portal
10597
10698// docs:start:initialize_l2_bridge
107- console . log ( "🔧 Setting up L2 bridge..." ) ;
99+ console . log ( "Setting up L2 bridge..." ) ;
108100
109101await l2Bridge . methods
110102 . set_portal ( EthAddress . fromString ( portalAddress . toString ( ) ) )
@@ -116,37 +108,51 @@ await l2Nft.methods
116108 . send ( { from : account . address } )
117109 . wait ( ) ;
118110
119- console . log ( "✅ Bridge configured\n" ) ;
111+ console . log ( "Bridge configured\n" ) ;
120112// docs:end:initialize_l2_bridge
121113
122114// docs:start:mint_nft_l1
123- console . log ( "🎨 Minting NFT on L1..." ) ;
115+ console . log ( "Minting NFT on L1..." ) ;
124116
125- const mintHash = await nftContract . write . mint ( [ l1Account . address ] ) ;
117+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
118+ const mintHash = await l1Client . writeContract ( {
119+ address : nftAddress . toString ( ) as `0x${string } `,
120+ abi : SimpleNFT . abi ,
121+ functionName : "mint" ,
122+ args : [ ownerEthAddress ] ,
123+ } ) ;
126124await l1Client . waitForTransactionReceipt ( { hash : mintHash } ) ;
127125
128126// no need to parse logs, this will be tokenId 0 since it's a fresh contract
129127const tokenId = 0n ;
130128
131- console . log ( `✅ Minted tokenId: ${ tokenId } \n` ) ;
129+ console . log ( `Minted tokenId: ${ tokenId } \n` ) ;
132130// docs:end:mint_nft_l1
133131
134132// docs:start:deposit_to_aztec
135- console . log ( "🌉 Depositing NFT to Aztec..." ) ;
133+ console . log ( "Depositing NFT to Aztec..." ) ;
136134
137135const secret = Fr . random ( ) ;
138136const secretHash = await computeSecretHash ( secret ) ;
139137
140- const approveHash = await nftContract . write . approve ( [
141- portalAddress . toString ( ) as `0x${string } `,
142- tokenId ,
143- ] ) ;
138+ // Approve portal to transfer the NFT
139+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
140+ const approveHash = await l1Client . writeContract ( {
141+ address : nftAddress . toString ( ) as `0x${string } `,
142+ abi : SimpleNFT . abi ,
143+ functionName : "approve" ,
144+ args : [ portalAddress . toString ( ) , tokenId ] ,
145+ } ) ;
144146await l1Client . waitForTransactionReceipt ( { hash : approveHash } ) ;
145147
146- const depositHash = await portalContract . write . depositToAztec ( [
147- tokenId ,
148- pad ( secretHash . toString ( ) as `0x${string } `, { dir : "left" , size : 32 } ) ,
149- ] ) ;
148+ // Deposit to Aztec
149+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
150+ const depositHash = await l1Client . writeContract ( {
151+ address : portalAddress . toString ( ) as `0x${string } `,
152+ abi : NFTPortal . abi ,
153+ functionName : "depositToAztec" ,
154+ args : [ tokenId , pad ( secretHash . toString ( ) as `0x${string } `, { dir : "left" , size : 32 } ) ] ,
155+ } ) ;
150156const depositReceipt = await l1Client . waitForTransactionReceipt ( {
151157 hash : depositHash ,
152158} ) ;
@@ -167,22 +173,23 @@ const INBOX_ABI = [
167173] as const ;
168174
169175// Find and decode the MessageSent event from the Inbox contract
170- const messageSentLog = depositReceipt . logs
176+ const messageSentLogs = depositReceipt . logs
171177 . filter ( ( log ) => log . address . toLowerCase ( ) === inboxAddress . toLowerCase ( ) )
172- . map ( ( log ) => {
178+ . map ( ( log : any ) => {
173179 try {
174- return decodeEventLog ( {
180+ const decoded = decodeEventLog ( {
175181 abi : INBOX_ABI ,
176182 data : log . data ,
177183 topics : log . topics ,
178184 } ) ;
185+ return { log, decoded } ;
179186 } catch {
180187 return null ;
181188 }
182189 } )
183- . find ( ( decoded ) => decoded ? .eventName === "MessageSent" ) ;
190+ . filter ( ( item ) : item is { log : any ; decoded : any } => item !== null && item . decoded . eventName === "MessageSent" ) ;
184191
185- const messageLeafIndex = new Fr ( messageSentLog ! . args . index ) ;
192+ const messageLeafIndex = new Fr ( messageSentLogs [ 0 ] . decoded . args . index ) ;
186193// docs:end:get_message_leaf_index
187194
188195// docs:start:mine_blocks
@@ -201,44 +208,44 @@ async function mine2Blocks(aztecWallet: TestWallet, accountAddress: AztecAddress
201208await mine2Blocks ( aztecWallet , account . address ) ;
202209
203210// Check notes before claiming (should be 0)
204- console . log ( "📝 Checking notes before claim..." ) ;
211+ console . log ( "Checking notes before claim..." ) ;
205212const notesBefore = await l2Nft . methods
206213 . notes_of ( account . address )
207214 . simulate ( { from : account . address } ) ;
208215console . log ( ` Notes count: ${ notesBefore } ` ) ;
209216
210- console . log ( "🎯 Claiming NFT on L2..." ) ;
217+ console . log ( "Claiming NFT on L2..." ) ;
211218await l2Bridge . methods
212219 . claim ( account . address , new Fr ( Number ( tokenId ) ) , secret , messageLeafIndex )
213220 . send ( { from : account . address } )
214221 . wait ( ) ;
215- console . log ( "✅ NFT claimed on L2\n" ) ;
222+ console . log ( "NFT claimed on L2\n" ) ;
216223
217224// Check notes after claiming (should be 1)
218- console . log ( "📝 Checking notes after claim..." ) ;
225+ console . log ( "Checking notes after claim..." ) ;
219226const notesAfterClaim = await l2Nft . methods
220227 . notes_of ( account . address )
221228 . simulate ( { from : account . address } ) ;
222229console . log ( ` Notes count: ${ notesAfterClaim } \n` ) ;
223230// docs:end:claim_on_l2
224231
225232// docs:start:exit_from_l2
226- // L2 → L1 flow
227- console . log ( "🚪 Exiting NFT from L2..." ) ;
233+ // L2 -> L1 flow
234+ console . log ( "Exiting NFT from L2..." ) ;
228235// Mine blocks, not necessary on devnet, but must wait for 2 blocks
229236await mine2Blocks ( aztecWallet , account . address ) ;
230237
231- const recipientEthAddress = EthAddress . fromString ( l1Account . address ) ;
238+ const recipientEthAddress = EthAddress . fromString ( ownerEthAddress ) ;
232239
233240const exitReceipt = await l2Bridge . methods
234241 . exit ( new Fr ( Number ( tokenId ) ) , recipientEthAddress )
235242 . send ( { from : account . address } )
236243 . wait ( ) ;
237244
238- console . log ( `✅ Exit message sent (block: ${ exitReceipt . blockNumber } )\n` ) ;
245+ console . log ( `Exit message sent (block: ${ exitReceipt . blockNumber } )\n` ) ;
239246
240247// Check notes after burning (should be 0 again)
241- console . log ( "📝 Checking notes after burn..." ) ;
248+ console . log ( "Checking notes after burn..." ) ;
242249const notesAfterBurn = await l2Nft . methods
243250 . notes_of ( account . address )
244251 . simulate ( { from : account . address } ) ;
@@ -258,9 +265,14 @@ const content = sha256ToField([
258265] ) ;
259266
260267// Get rollup version from the portal contract (it stores it during initialize)
261- const version = await portalContract . read . rollupVersion ( ) ;
268+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
269+ const version = ( await l1Client . readContract ( {
270+ address : portalAddress . toString ( ) as `0x${string } `,
271+ abi : NFTPortal . abi ,
272+ functionName : "rollupVersion" ,
273+ } ) ) as bigint ;
262274
263- // Compute the L2→ L1 message hash
275+ // Compute the L2-> L1 message hash
264276const msgLeaf = computeL2ToL1MessageHash ( {
265277 l2Sender : l2Bridge . address ,
266278 l1Recipient : EthAddress . fromString ( portalAddress . toString ( ) ) ,
@@ -271,7 +283,7 @@ const msgLeaf = computeL2ToL1MessageHash({
271283
272284// Wait for the block to be proven before withdrawing
273285// Waiting for the block to be proven is not necessary on the local network, but it is necessary on devnet
274- console . log ( "⏳ Waiting for block to be proven..." ) ;
286+ console . log ( "Waiting for block to be proven..." ) ;
275287console . log ( ` Exit block number: ${ exitReceipt . blockNumber } ` ) ;
276288
277289let provenBlockNumber = await node . getProvenBlockNumber ( ) ;
@@ -285,12 +297,19 @@ while (provenBlockNumber < exitReceipt.blockNumber!) {
285297 provenBlockNumber = await node . getProvenBlockNumber ( ) ;
286298}
287299
288- console . log ( "✅ Block proven!\n" ) ;
300+ console . log ( "Block proven!\n" ) ;
289301
290- // Compute the membership witness using the message hash
302+ // Get the epoch for the exit block's checkpoint
303+ // In Aztec, checkpoint number equals block number (1:1 mapping)
304+ const epoch = await rollup . getEpochNumberForCheckpoint (
305+ CheckpointNumber . fromBlockNumber ( exitReceipt . blockNumber ! )
306+ ) ;
307+ console . log ( ` Epoch for block ${ exitReceipt . blockNumber } : ${ epoch } ` ) ;
308+
309+ // Compute the membership witness using the message hash and epoch
291310const witness = await computeL2ToL1MembershipWitness (
292311 node ,
293- exitReceipt . blockNumber ! ,
312+ epoch ,
294313 msgLeaf
295314) ;
296315const siblingPathHex = witness ! . siblingPath
@@ -299,13 +318,14 @@ const siblingPathHex = witness!.siblingPath
299318// docs:end:get_withdrawal_witness
300319
301320// docs:start:withdraw_on_l1
302- console . log ( "💰 Withdrawing NFT on L1..." ) ;
303- const withdrawHash = await portalContract . write . withdraw ( [
304- tokenId ,
305- BigInt ( exitReceipt . blockNumber ! ) ,
306- BigInt ( witness ! . leafIndex ) ,
307- siblingPathHex ,
308- ] ) ;
321+ console . log ( "Withdrawing NFT on L1..." ) ;
322+ // @ts -expect-error - viem type inference doesn't work with JSON-imported ABIs
323+ const withdrawHash = await l1Client . writeContract ( {
324+ address : portalAddress . toString ( ) as `0x${string } `,
325+ abi : NFTPortal . abi ,
326+ functionName : "withdraw" ,
327+ args : [ tokenId , BigInt ( epoch ) , BigInt ( witness ! . leafIndex ) , siblingPathHex ] ,
328+ } ) ;
309329await l1Client . waitForTransactionReceipt ( { hash : withdrawHash } ) ;
310- console . log ( "✅ NFT withdrawn to L1\n" ) ;
330+ console . log ( "NFT withdrawn to L1\n" ) ;
311331// docs:end:withdraw_on_l1
0 commit comments