Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
members = [
"examples/contracts/counter_contract",
"examples/contracts/bob_token_contract",
"examples/tutorials/token_bridge_contract/contracts/aztec/nft",
"examples/tutorials/token_bridge_contract/contracts/aztec/nft_bridge"
"examples/contracts/nft",
"examples/contracts/nft_bridge"
]
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#i

Define your custom note with the `#[note]` macro:

#include_code nft_note_struct /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/nft.nr rust
#include_code nft_note_struct /docs/examples/contracts/nft/src/nft.nr rust

The `#[note]` macro generates the following for your struct:

Expand Down Expand Up @@ -98,13 +98,13 @@ struct Storage<Context> {

### Inserting notes

#include_code mint /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code mint /docs/examples/contracts/nft/src/main.nr rust

### Reading and removing notes

Use `pop_notes` to read and nullify notes atomically. This is the recommended pattern for most use cases:

#include_code burn /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code burn /docs/examples/contracts/nft/src/main.nr rust

:::warning
There's also a `get_notes` function that reads without nullifying, but use it with caution - the returned notes may have already been spent in another transaction.
Expand Down
52 changes: 26 additions & 26 deletions docs/docs-developers/docs/tutorials/js_tutorials/token_bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ touch src/nft.nr

In this file, you're going to create a **private note** that represents NFT ownership. This is a struct with macros that indicate it is a note that can be compared and packed:

#include_code nft_note_struct /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/nft.nr rust
#include_code nft_note_struct /docs/examples/contracts/nft/src/nft.nr rust

You now have a note that represents the owner of a particular NFT. Next, move on to the contract itself.

Expand All @@ -134,36 +134,36 @@ Write the storage struct and a simple [initializer](../../foundational-topics/co
<!-- wrapped in a code block to add a "}" at the end -->

```rust
#include_code contract_setup /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr raw
#include_code contract_setup /docs/examples/contracts/nft/src/main.nr raw
}
```

### Utility Functions

Add an internal function to handle the `DelayedPublicMutable` value change. Mark the function as public and `#[only_self]` so only the contract can call it:

#include_code mark_nft_exists /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code mark_nft_exists /docs/examples/contracts/nft/src/main.nr rust

This function is marked with `#[only_self]`, meaning only the contract itself can call it. It uses `schedule_value_change` to update the `nfts` storage, preventing the same NFT from being minted twice or burned when it doesn't exist. You'll call this public function from a private function later using `enqueue_self`.

Another useful function checks how many notes a caller has. You can use this later to verify the claim and exit from L2:

#include_code notes_of /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code notes_of /docs/examples/contracts/nft/src/main.nr rust

### Add Minting and Burning

Before anything else, you need to set the minter. This will be the bridge contract, so only the bridge contract can mint NFTs. This value doesn't need to change after initialization. Here's how to initialize the `PublicImmutable`:

#include_code set_minter /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code set_minter /docs/examples/contracts/nft/src/main.nr rust

Now for the magic - minting NFTs **privately**. The bridge will call this to mint to a user, deliver the note using [constrained message delivery](../../aztec-nr/framework-description/how_to_emit_event.md) (best practice when "sending someone a
note") and then [enqueue a public call](../../aztec-nr/framework-description/how_to_call_contracts.md) to the `_mark_nft_exists` function:

#include_code mint /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code mint /docs/examples/contracts/nft/src/main.nr rust

The bridge will also need to burn NFTs when users withdraw back to L1:

#include_code burn /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft/src/main.nr rust
#include_code burn /docs/examples/contracts/nft/src/main.nr rust

### Compiling!

Expand Down Expand Up @@ -241,7 +241,7 @@ Clean up `main.nr` which is just a placeholder, and let's write the storage stru
<!-- wrapped in a code block to add a "}" at the end -->

```rust
#include_code bridge_setup /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft_bridge/src/main.nr raw
#include_code bridge_setup /docs/examples/contracts/nft_bridge/src/main.nr raw
}
```

Expand All @@ -255,7 +255,7 @@ You need to define how to encode messages. Here's a simple approach: when an NFT

Build the `claim` function, which consumes the message and mints the NFT on the L2 side:

#include_code claim /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft_bridge/src/main.nr rust
#include_code claim /docs/examples/contracts/nft_bridge/src/main.nr rust

:::tip Secret

Expand All @@ -265,7 +265,7 @@ The secret prevents front-running. Certainly you don't want anyone to claim your

Similarly, exiting to L1 means burning the NFT on the L2 side and pushing a message through the protocol. To ensure only the L1 recipient can claim it, hash the `token_id` together with the `recipient`:

#include_code exit /docs/examples/tutorials/token_bridge_contract/contracts/aztec/nft_bridge/src/main.nr rust
#include_code exit /docs/examples/contracts/nft_bridge/src/main.nr rust

Cross-chain messaging on Aztec is powerful because it doesn't conform to any specific format—you can structure messages however you want.

Expand Down Expand Up @@ -319,7 +319,7 @@ touch contracts/SimpleNFT.sol

Create a minimal NFT contract sufficient for demonstrating bridging:

#include_code simple_nft /docs/examples/tutorials/token_bridge_contract/contracts/SimpleNFT.sol solidity
#include_code simple_nft /docs/examples/solidity/nft_bridge/SimpleNFT.sol solidity

### Create the NFT Portal

Expand All @@ -332,15 +332,15 @@ touch contracts/NFTPortal.sol
Initialize it with Aztec's registry, which holds the canonical contracts for Aztec-related contracts, including the Inbox and Outbox. These are the message-passing contracts—Aztec sequencers read any messages on these contracts.

```solidity
#include_code portal_setup /docs/examples/tutorials/token_bridge_contract/contracts/NFTPortal.sol raw
#include_code portal_setup /docs/examples/solidity/nft_bridge/NFTPortal.sol raw
}
```

The core logic is similar to the L2 logic. `depositToAztec` calls the `Inbox` canonical contract to send a message to Aztec, and `withdraw` calls the `Outbox` contract.

Add these two functions with explanatory comments:

#include_code portal_deposit_and_withdraw /docs/examples/tutorials/token_bridge_contract/contracts/NFTPortal.sol solidity
#include_code portal_deposit_and_withdraw /docs/examples/solidity/nft_bridge/NFTPortal.sol solidity

The portal handles two flows:

Expand Down Expand Up @@ -383,19 +383,19 @@ This section assumes you're working locally using the local network. For the tes

First, initialize the clients: `aztec.js` for Aztec and `viem` for Ethereum:

#include_code setup /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code setup /docs/examples/ts/token_bridge/index.ts typescript

You now have wallets for both chains, correctly connected to their respective chains. Next, deploy the L1 contracts:

#include_code deploy_l1_contracts /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code deploy_l1_contracts /docs/examples/ts/token_bridge/index.ts typescript

Now deploy the L2 contracts. Thanks to the TypeScript bindings generated with `aztec codegen`, deployment is straightforward:

#include_code deploy_l2_contracts /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code deploy_l2_contracts /docs/examples/ts/token_bridge/index.ts typescript

Now that you have the L2 bridge's contract address, initialize the L1 bridge:

#include_code initialize_portal /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code initialize_portal /docs/examples/ts/token_bridge/index.ts typescript

The L2 contracts were already initialized when you deployed them, but you still need to:

Expand All @@ -404,49 +404,49 @@ The L2 contracts were already initialized when you deployed them, but you still

Complete these initialization steps:

#include_code initialize_l2_bridge /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code initialize_l2_bridge /docs/examples/ts/token_bridge/index.ts typescript

This completes the setup. It's a lot of configuration, but you're dealing with four contracts across two chains.

### L1 → L2 Flow

Now for the main flow. Mint a CryptoPunk on L1, deposit it to Aztec, and claim it on Aztec. Put everything in the same script. To mint, call the L1 contract with `mint`, which will mint `tokenId = 0`:

#include_code mint_nft_l1 /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code mint_nft_l1 /docs/examples/ts/token_bridge/index.ts typescript

To bridge, first approve the portal address to transfer the NFT, then transfer it by calling `depositToAztec`:

#include_code deposit_to_aztec /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code deposit_to_aztec /docs/examples/ts/token_bridge/index.ts typescript

The `Inbox` contract will emit an important log: `MessageSent(inProgress, index, leaf, updatedRollingHash);`. This log provides the **leaf index** of the message in the [L1-L2 Message Tree](../../aztec-nr/framework-description/ethereum-aztec-messaging/index.md)—the location of the message in the tree that will appear on L2. You need this index, plus the secret, to correctly claim and decrypt the message.

Use viem to extract this information:

#include_code get_message_leaf_index /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code get_message_leaf_index /docs/examples/ts/token_bridge/index.ts typescript

This extracts the logs from the deposit and retrieves the leaf index. You can now claim it on L2. However, for security reasons, at least 2 blocks must pass before a message can be claimed on L2. If you called `claim` on the L2 contract immediately, it would return "no message available".

Add a utility function to mine two blocks (it deploys a contract with a random salt):

#include_code mine_blocks /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code mine_blocks /docs/examples/ts/token_bridge/index.ts typescript

Now claim the message on L2:

#include_code claim_on_l2 /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code claim_on_l2 /docs/examples/ts/token_bridge/index.ts typescript

### L2 → L1 Flow

Great! You can expand the L2 contract to add features like NFT transfers. For now, exit the NFT on L2 and redeem it on L1. Mine two blocks because of `DelayedMutable`:

#include_code exit_from_l2 /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code exit_from_l2 /docs/examples/ts/token_bridge/index.ts typescript

Just like in the L1 → L2 flow, you need to know what to claim on L1. Where in the message tree is the message you want to claim? Use the utility `computeL2ToL1MembershipWitness`, which provides the leaf and the sibling path of the message:

#include_code get_withdrawal_witness /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code get_withdrawal_witness /docs/examples/ts/token_bridge/index.ts typescript

With this information, call the L1 contract and use the index and the sibling path to claim the L1 NFT:

#include_code withdraw_on_l1 /docs/examples/tutorials/token_bridge_contract/scripts/index.ts typescript
#include_code withdraw_on_l1 /docs/examples/ts/token_bridge/index.ts typescript

You can now try the whole flow with:

Expand Down
37 changes: 37 additions & 0 deletions docs/examples/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,39 @@ function compile {
$REPO_ROOT/noir-projects/noir-contracts/bootstrap.sh compile "$@"
}

function compile-solidity {
echo_header "Compiling Solidity examples"
local SOLIDITY_DIR="$REPO_ROOT/docs/examples/solidity"
local OUTPUT_DIR="$REPO_ROOT/docs/target/solidity"

# Find all .sol files recursively
local sol_files
sol_files=$(find "$SOLIDITY_DIR" -name "*.sol" 2>/dev/null)
if [ -z "$sol_files" ]; then
echo_stderr "No Solidity files found in $SOLIDITY_DIR"
return 0
fi

mkdir -p "$OUTPUT_DIR"

# Compile using the local foundry.toml with proper remappings
(
cd "$SOLIDITY_DIR"
for subdir in */; do
if [ -d "$subdir" ] && ls "$subdir"/*.sol >/dev/null 2>&1; then
local subdir_name=$(basename "$subdir")
echo_stderr "Compiling $subdir_name..."
forge build \
--contracts "$subdir" \
--out "$OUTPUT_DIR/$subdir_name" \
--no-cache
fi
done
)

echo_stderr "Solidity artifacts written to $OUTPUT_DIR"
}

function validate-ts {
echo_header "Validating TypeScript examples"
(cd ts && ./bootstrap.sh "$@")
Expand All @@ -25,8 +58,12 @@ function validate-ts {
case "$cmd" in
"")
compile
compile-solidity
validate-ts
;;
compile-solidity)
compile-solidity
;;
*)
default_cmd_handler "$@"
;;
Expand Down
6 changes: 6 additions & 0 deletions docs/examples/contracts/nft/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "nft"
type = "contract"

[dependencies]
aztec = { path = "../../../../noir-projects/aztec-nr/aztec" }
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ name = "nft_bridge"
type = "contract"

[dependencies]
aztec = { path = "../../../../../../../noir-projects/aztec-nr/aztec" }
aztec = { path = "../../../../noir-projects/aztec-nr/aztec" }
NFTPunk = { path = "../nft" }
15 changes: 15 additions & 0 deletions docs/examples/solidity/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[profile.default]
src = '.'
out = 'out'
libs = ['../../../l1-contracts/lib', '../../../l1-contracts/src']
solc_version = '0.8.27'
evm_version = 'prague'
optimizer = true

# Match l1-contracts remappings exactly
remappings = [
"@oz/=../../../l1-contracts/lib/openzeppelin-contracts/contracts/",
"@aztec/=../../../l1-contracts/src/",
"@openzeppelin/contracts/=../../../l1-contracts/lib/openzeppelin-contracts/contracts/",
"@aztec-blob-lib/=../../../l1-contracts/src/mock/libraries/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isnt being used in the docs examples contracts, i think we can remove it

]
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
pragma solidity >=0.8.27;

// docs:start:portal_setup
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IRegistry} from "@aztec/l1-contracts/src/governance/interfaces/IRegistry.sol";
import {IInbox} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol";
import {IRollup} from "@aztec/l1-contracts/src/core/interfaces/IRollup.sol";
import {DataStructures} from "@aztec/l1-contracts/src/core/libraries/DataStructures.sol";
import {Hash} from "@aztec/l1-contracts/src/core/libraries/crypto/Hash.sol";
import {IERC721} from "@oz/token/ERC721/IERC721.sol";
import {IRegistry} from "@aztec/governance/interfaces/IRegistry.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";
import {IRollup} from "@aztec/core/interfaces/IRollup.sol";
import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
import {Hash} from "@aztec/core/libraries/crypto/Hash.sol";

contract NFTPortal {
IRegistry public registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// docs:start:simple_nft
pragma solidity >=0.8.27;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721} from "@oz/token/ERC721/ERC721.sol";

contract SimpleNFT is ERC721 {
uint256 private _currentTokenId;
Expand Down
39 changes: 35 additions & 4 deletions docs/examples/ts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,14 @@ validate_project() {
if [ "$has_deps" = true ]; then
# Install linked @aztec dependencies
if [ ${#aztec_deps[@]} -gt 0 ]; then
yarn add "${aztec_deps[@]}" >/dev/null 2>&1
echo_stderr "Adding aztec deps: ${aztec_deps[*]}"
yarn add "${aztec_deps[@]}"
fi

# Install external npm dependencies
if [ ${#npm_deps[@]} -gt 0 ]; then
yarn add "${npm_deps[@]}" >/dev/null 2>&1
echo_stderr "Adding npm deps: ${npm_deps[*]}"
yarn add "${npm_deps[@]}"
fi
else
# Fallback to default dependencies if none specified
Expand All @@ -185,10 +187,39 @@ validate_project() {
@aztec/aztec.js@link:$REPO_ROOT/yarn-project/aztec.js \
@aztec/accounts@link:$REPO_ROOT/yarn-project/accounts \
@aztec/test-wallet@link:$REPO_ROOT/yarn-project/test-wallet \
@aztec/kv-store@link:$REPO_ROOT/yarn-project/kv-store \
>/dev/null 2>&1
@aztec/kv-store@link:$REPO_ROOT/yarn-project/kv-store
fi

# Verify linked packages exist and have built artifacts
echo_stderr "Verifying linked packages..."
for dep in "${aztec_deps[@]}"; do
# Extract package name from @aztec/foo@link:... format
local pkg_full=$(echo "$dep" | cut -d'@' -f2) # aztec/foo
local pkg_name=${pkg_full#aztec/} # foo
local link_target="$REPO_ROOT/yarn-project/$pkg_name"

if [ ! -d "$link_target" ]; then
echo_stderr "ERROR: Link target does not exist: $link_target"
return 1
fi

if [ ! -d "$link_target/dest" ]; then
echo_stderr "ERROR: Package not built (no dest/): $link_target"
ls -la "$link_target" || true
return 1
fi

# Check for .d.ts files (type declarations)
local dts_count=$(find "$link_target/dest" -name "*.d.ts" 2>/dev/null | wc -l)
if [ "$dts_count" -eq 0 ]; then
echo_stderr "ERROR: No .d.ts files found in $link_target/dest"
ls -la "$link_target/dest" | head -20 || true
return 1
fi

echo_stderr " ✓ @aztec/$pkg_name: $dts_count .d.ts files"
done

yarn add -D typescript >/dev/null 2>&1

# Create tsconfig.json from template
Expand Down
Loading
Loading