diff --git a/bootstrap.sh b/bootstrap.sh index 66d251a0c358..cdd88480cebb 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -637,7 +637,7 @@ case "$cmd" in "ci-docs") export CI=1 export USE_TEST_CACHE=1 - ./bootstrap.sh + BOOTSTRAP_TO=yarn-project ./bootstrap.sh docs/bootstrap.sh ci ;; "ci-barretenberg-debug") diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 3a72d0b1d19d..58bfc28ef935 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -110,6 +110,9 @@ Default content - `static/img/` - Static images and assets - `static/aztec-nr-api/` - Auto-generated Aztec.nr API documentation (HTML) - `static/typescript-api/` - Auto-generated TypeScript API documentation (markdown) +- `examples/` - Code examples (Noir circuits, Noir contracts, Solidity, TypeScript) +- `examples/ts/` - TypeScript aztec.js examples with `docker-compose.yml` for CI execution +- `examples/ts/aztecjs_runner/` - Runner script that executes examples against a live network - `scripts/` - Build and utility scripts - `scripts/typescript_api_generation/` - TypeScript API doc generation scripts and config @@ -142,6 +145,23 @@ Uses Docusaurus multi-instance versioning with separate version tracks: - Preprocessing macros (`#include_code`, `#release_version`, conditionals, etc.) only work in source folders, not in versioned copies - Create new versions with: `yarn docusaurus docs:version: ` +### Code Examples Pipeline + +The `examples/` directory contains runnable code examples that are included in documentation via `#include_code` markers. The examples pipeline has two stages: + +**Validation (type-checking)**: `examples/bootstrap.sh` compiles Noir circuits, Noir contracts, Solidity, and type-checks TypeScript examples. This runs on every PR. + +**Execution (runtime testing)**: TypeScript examples in `examples/ts/` are executed against a live Aztec network via Docker Compose. The `examples/ts/docker-compose.yml` spins up Anvil (L1 fork), an Aztec local network, and a runner service that executes the examples. + +- **CI**: `docs/bootstrap.sh ci` calls `examples/bootstrap.sh execute`, which uses `run_compose_test` from `ci3/` +- **Local**: Start a sandbox manually, then run `examples/ts/aztecjs_runner/run.sh` +- **`AZTEC_NODE_URL`**: All example `index.ts` files and `run.sh` use this env var (defaults to `http://localhost:8080`). In Docker Compose, it points to `http://local-network:8080`. + +When adding new TypeScript examples: +1. Create a directory under `examples/ts/` with `index.ts`, `config.yaml`, and empty `yarn.lock` +2. Use `process.env.AZTEC_NODE_URL ?? "http://localhost:8080"` for the node URL +3. Add the example to the list in `examples/ts/aztecjs_runner/run.sh` if it should be executed at runtime + ## Documentation Review Standards ## Primary Review Objectives @@ -329,5 +349,5 @@ Approved external documentation sources: - Suggest improvements even if they go beyond pure editing - When making changes to documentation processes or tooling, remember to check and update READMEs, project documentation (like this file), and code comments -Last updated: 2026-02-04 -Version: 1.4 +Last updated: 2026-02-23 +Version: 1.5 diff --git a/docs/developer_versioned_docs/version-v4.0.0-devnet.2-patch.1/docs/aztec-js/how_to_test.md b/docs/developer_versioned_docs/version-v4.0.0-devnet.2-patch.1/docs/aztec-js/how_to_test.md index 02675e521817..d3f7603e2817 100644 --- a/docs/developer_versioned_docs/version-v4.0.0-devnet.2-patch.1/docs/aztec-js/how_to_test.md +++ b/docs/developer_versioned_docs/version-v4.0.0-devnet.2-patch.1/docs/aztec-js/how_to_test.md @@ -17,21 +17,22 @@ This guide covers how to test Aztec smart contracts by connecting to a local net Connect to your local Aztec network and create an embedded wallet: -```typescript title="setup" showLineNumbers -const logger = createLogger("e2e:token"); +```typescript title="connect_to_network" showLineNumbers +import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; +import { EmbeddedWallet } from "@aztec/wallets/embedded"; +import { getInitialTestAccountsData } from "@aztec/accounts/testing"; -// We create PXE client connected to the local network URL -const node = createAztecNodeClient(AZTEC_NODE_URL); -// Wait for local network to be ready -await waitForNode(node, logger); -const wallet = await TestWallet.create(node); +const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; +const node = createAztecNodeClient(nodeUrl); -const nodeInfo = await node.getNodeInfo(); +// Wait for the network to be ready +await waitForNode(node); -logger.info(format("Aztec Local Network Info ", nodeInfo)); +// Create an EmbeddedWallet connected to the node +const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); ``` -> Source code: yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts#L37-L50 +> Source code: docs/examples/ts/aztecjs_connection/index.ts#L1-L14 The `EmbeddedWallet` manages accounts, tracks deployed contracts, and handles transaction proving. It connects to the Aztec node which provides access to both the Private eXecution Environment (PXE) and the network. diff --git a/docs/docs-developers/docs/aztec-js/how_to_read_data.md b/docs/docs-developers/docs/aztec-js/how_to_read_data.md index 05be1061fb37..171505eb9c4d 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_read_data.md +++ b/docs/docs-developers/docs/aztec-js/how_to_read_data.md @@ -22,7 +22,7 @@ The `from` option specifies which address context to use for the simulation. Thi ### Basic simulation -#include_code simulate_function yarn-project/end-to-end/src/composed/docs_examples.test.ts typescript +#include_code simulate_function docs/examples/ts/aztecjs_connection/index.ts typescript ### Handling return values diff --git a/docs/docs-developers/docs/aztec-js/how_to_test.md b/docs/docs-developers/docs/aztec-js/how_to_test.md index 598b77ab881d..66dfda01d19f 100644 --- a/docs/docs-developers/docs/aztec-js/how_to_test.md +++ b/docs/docs-developers/docs/aztec-js/how_to_test.md @@ -17,7 +17,7 @@ This guide covers how to test Aztec smart contracts by connecting to a local net Connect to your local Aztec network and create an embedded wallet: -#include_code setup yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts typescript +#include_code connect_to_network /docs/examples/ts/aztecjs_connection/index.ts typescript The `EmbeddedWallet` manages accounts, tracks deployed contracts, and handles transaction proving. It connects to the Aztec node which provides access to both the Private eXecution Environment (PXE) and the network. @@ -53,7 +53,7 @@ const contract = await TokenContract.deploy( Use `.simulate()` to read contract state without creating a transaction: -#include_code simulate_function yarn-project/end-to-end/src/composed/docs_examples.test.ts typescript +#include_code simulate_function docs/examples/ts/aztecjs_connection/index.ts typescript Simulations are free (no gas cost) and return the function's result directly. Use them for: diff --git a/docs/docs-developers/docs/foundational-topics/call_types.md b/docs/docs-developers/docs/foundational-topics/call_types.md index 74a4c44dba3e..a03da34a00d6 100644 --- a/docs/docs-developers/docs/foundational-topics/call_types.md +++ b/docs/docs-developers/docs/foundational-topics/call_types.md @@ -3,7 +3,7 @@ title: Call Types sidebar_position: 6 tags: [calls, contracts, execution] description: Understand the different types of contract calls in Aztec, including private and public execution modes, and how they compare to Ethereum's call types. -references: ["noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr", "noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr", "noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr", "noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr", "noir-projects/noir-contracts/contracts/protocol/router_contract/src/main.nr", "noir-projects/noir-contracts/contracts/protocol/router_contract/src/utils.nr", "yarn-project/end-to-end/src/composed/docs_examples.test.ts", "yarn-project/end-to-end/src/e2e_card_game.test.ts", "yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts"] +references: ["noir-projects/noir-contracts/contracts/app/auth_contract/src/main.nr", "noir-projects/noir-contracts/contracts/app/crowdfunding_contract/src/main.nr", "noir-projects/noir-contracts/contracts/app/lending_contract/src/main.nr", "noir-projects/noir-contracts/contracts/fees/fpc_contract/src/main.nr", "noir-projects/noir-contracts/contracts/protocol/router_contract/src/main.nr", "noir-projects/noir-contracts/contracts/protocol/router_contract/src/utils.nr", "docs/examples/ts/aztecjs_connection/index.ts", "yarn-project/end-to-end/src/e2e_card_game.test.ts", "yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts"] --- ## What is a Call @@ -197,7 +197,7 @@ There are two main ways to execute an Aztec contract function using the `aztec.j This is used to get a result out of an execution, either private or public. It creates no transaction and spends no gas. The mental model is fairly close to that of [`eth_call`](#eth_call), in that it can be used to call any type of function, simulate its execution and get a result out of it. `simulate` is also the only way to run [utility functions](#utility). -#include_code simulate_function yarn-project/end-to-end/src/composed/docs_examples.test.ts typescript +#include_code simulate_function docs/examples/ts/aztecjs_connection/index.ts typescript :::warning No correctness is guaranteed on the result of `simulate`! Correct execution is entirely optional and left up to the client that handles this request. diff --git a/docs/examples/bootstrap.sh b/docs/examples/bootstrap.sh index ab550302b893..6a6617444193 100755 --- a/docs/examples/bootstrap.sh +++ b/docs/examples/bootstrap.sh @@ -77,6 +77,12 @@ function validate-ts { (cd ts && ./bootstrap.sh "$@") } +function execute-examples { + echo_header "Executing TypeScript documentation examples" + local COMPOSE_DIR="$REPO_ROOT/docs/examples/ts" + run_compose_test "docs_examples" "docs-examples" "$COMPOSE_DIR" +} + ############################################################################## # CI failure handling - send Slack notifications instead of blocking the build ############################################################################## @@ -204,6 +210,7 @@ case "$cmd" in run_step "Compile (Noir contracts)" compile run_step "Compile (Solidity)" compile-solidity run_step "TypeScript validation" validate-ts + run_step "Execute examples" execute-examples if [[ ${#FAILED_STEPS[@]} -gt 0 ]]; then send_failure_slack_message @@ -220,6 +227,9 @@ case "$cmd" in compile-solidity) compile-solidity ;; + execute) + execute-examples + ;; *) default_cmd_handler "$@" ;; diff --git a/docs/examples/ts/README.md b/docs/examples/ts/README.md new file mode 100644 index 000000000000..a589c2baf7bf --- /dev/null +++ b/docs/examples/ts/README.md @@ -0,0 +1,129 @@ +# TypeScript Documentation Examples + +This directory contains TypeScript examples used in the Aztec documentation. Each example is a self-contained project that demonstrates specific Aztec.js functionality. + +## Directory Structure + +Each example directory contains: +- `index.ts` - The example code with `docs:start:` and `docs:end:` markers for inclusion in documentation +- `config.yaml` - Specifies dependencies and any custom contract artifacts needed +- `yarn.lock` - Empty file to prevent yarn from using parent monorepo's yarn.lock + +## Validation: Type Checking + +The `bootstrap.sh` script validates all examples by: + +1. Reading `config.yaml` to determine dependencies and custom contracts +2. Running codegen for any custom contracts specified (from `docs/target/`) +3. Installing linked `@aztec/*` dependencies from `yarn-project/` +4. Running `tsc --noEmit` to type-check the example + +Run validation for all examples: +```bash +./bootstrap.sh +``` + +Run validation for specific example(s): +```bash +./bootstrap.sh aztecjs_connection aztecjs_advanced +``` + +## Execution: Test Runner + +The `aztecjs_runner/run.sh` script executes examples against a live local Aztec network to verify they work correctly. + +### CI Execution (Docker Compose) + +In CI, examples run via `docker-compose.yml` which spins up an Anvil fork, a local Aztec network, and runs the examples automatically. This is triggered by `docs/examples/bootstrap.sh execute` (called from `docs/bootstrap.sh ci`). + +```bash +# Run via bootstrap (recommended for CI) +cd docs/examples && ./bootstrap.sh execute + +# Or invoke docker-compose directly +cd docs/examples/ts && run_compose_test docs_examples docs-examples . +``` + +### Local Execution + +For local development, start the sandbox manually and run examples directly. + +#### Prerequisites + +- Local Aztec network running (default: `localhost:8080`) +- Built yarn-project packages + +#### Usage + +Run all examples: +```bash +cd aztecjs_runner +./run.sh +``` + +Run specific example(s): +```bash +./run.sh connection # aztecjs_connection +./run.sh getting_started # aztecjs_getting_started +./run.sh advanced authwit # multiple examples +``` + +#### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `AZTEC_NODE_URL` | URL of the Aztec node to connect to | `http://localhost:8080` | + +The `AZTEC_NODE_URL` env var is used by both the runner script and the example `index.ts` files. In Docker Compose, it is set to `http://local-network:8080` to point at the compose network's Aztec node. + +### Currently Tested Examples + +| Example | Description | +|---------|-------------| +| `aztecjs_connection` | Basic network connection, account setup, token deployment | +| `aztecjs_getting_started` | Complete getting started tutorial flow | +| `aztecjs_advanced` | NO_WAIT transactions, BatchCall, sponsored FPC, events | +| `aztecjs_authwit` | Authentication witnesses for delegated actions | +| `aztecjs_testing` | Test patterns: minting, transfers, revert testing | + +### Examples Not Executed (Type-Checked Only) + +These examples require additional infrastructure or custom contracts with verification keys: + +| Example | Reason | +|---------|--------| +| `bob_token_contract` | Custom contract requires verification keys | +| `token_bridge` | Requires L1 contracts and bridge infrastructure | +| `recursive_verification` | Requires prover and verification key generation | + +## Adding New Examples + +1. Create a new directory with your example name +2. Add `index.ts` with your example code +3. Add `config.yaml` specifying dependencies: + +```yaml +# For examples using pre-built contracts from @aztec/noir-contracts.js +contracts: [] + +dependencies: + - "@aztec/aztec.js" + - "@aztec/accounts" + - "@aztec/test-wallet" + - "@aztec/noir-contracts.js" +``` + +4. Create empty `yarn.lock` file +5. Run `./bootstrap.sh your_example_name` to validate +6. If the example can run against a live network, add it to `aztecjs_runner/run.sh` + +## File Management + +The validation and runner scripts generate temporary files during execution. These are cleaned up automatically, but if you need to manually clean: + +```bash +# In each example directory +rm -rf node_modules .yarn artifacts package.json tsconfig.json .yarnrc.yml +rm -f .editorconfig .gitattributes .gitignore README.md codegenCache.json +> yarn.lock # Keep empty +``` diff --git a/docs/examples/ts/aztecjs_advanced/index.ts b/docs/examples/ts/aztecjs_advanced/index.ts index 85f3a2aef2ca..77418ea37395 100644 --- a/docs/examples/ts/aztecjs_advanced/index.ts +++ b/docs/examples/ts/aztecjs_advanced/index.ts @@ -16,9 +16,9 @@ import { getPublicEvents } from "@aztec/aztec.js/events"; import { BlockNumber } from "@aztec/aztec.js/fields"; // Setup: connect to network -const node = createAztecNodeClient("http://localhost:8080"); +const node = createAztecNodeClient(process.env.AZTEC_NODE_URL ?? "http://localhost:8080"); await waitForNode(node); -const wallet = await EmbeddedWallet.create(node); +const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( @@ -126,6 +126,10 @@ await token.methods .mint_to_public(aliceAddress, 10000n) .send({ from: aliceAddress }); +await token.methods + .mint_to_private(aliceAddress, 10000n) + .send({ from: aliceAddress }); + // docs:start:no_wait_deploy // Use NO_WAIT to get the transaction hash immediately and track deployment const txHash = await TokenContract.deploy( diff --git a/docs/examples/ts/aztecjs_authwit/index.ts b/docs/examples/ts/aztecjs_authwit/index.ts index da0f03a80be0..ed7df03e109a 100644 --- a/docs/examples/ts/aztecjs_authwit/index.ts +++ b/docs/examples/ts/aztecjs_authwit/index.ts @@ -6,9 +6,11 @@ import { Fr } from "@aztec/aztec.js/fields"; import { SetPublicAuthwitContractInteraction } from "@aztec/aztec.js/authorization"; // Setup: connect to network and deploy a token contract -const node = createAztecNodeClient("http://localhost:8080"); +const node = createAztecNodeClient( + process.env.AZTEC_NODE_URL ?? "http://localhost:8080", +); await waitForNode(node); -const wallet = await EmbeddedWallet.create(node); +const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( @@ -59,7 +61,13 @@ const privateWitness = await wallet.createAuthWit(aliceAddress, { }); // Bob executes the transfer, providing the authwit -await privateAction.send({ from: bobAddress, authWitnesses: [privateWitness] }); +// additionalScopes lets the PXE access Alice's private state +// during authwit verification +await privateAction.send({ + from: bobAddress, + authWitnesses: [privateWitness], + additionalScopes: [aliceAddress], +}); // docs:end:private_authwit // docs:start:public_authwit diff --git a/docs/examples/ts/aztecjs_connection/config.yaml b/docs/examples/ts/aztecjs_connection/config.yaml index b8a9d4e79e8b..2f8d4f9f2c46 100644 --- a/docs/examples/ts/aztecjs_connection/config.yaml +++ b/docs/examples/ts/aztecjs_connection/config.yaml @@ -13,3 +13,4 @@ dependencies: - "@aztec/wallets" - "@aztec/noir-contracts.js" - "@aztec/stdlib" + - "@aztec/ethereum" diff --git a/docs/examples/ts/aztecjs_connection/index.ts b/docs/examples/ts/aztecjs_connection/index.ts index 4267a6597b9b..827efeae1211 100644 --- a/docs/examples/ts/aztecjs_connection/index.ts +++ b/docs/examples/ts/aztecjs_connection/index.ts @@ -3,14 +3,14 @@ import { createAztecNodeClient, waitForNode } from "@aztec/aztec.js/node"; import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; -const nodeUrl = "http://localhost:8080"; +const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; const node = createAztecNodeClient(nodeUrl); // Wait for the network to be ready await waitForNode(node); // Create an EmbeddedWallet connected to the node -const wallet = await EmbeddedWallet.create(node); +const wallet = await EmbeddedWallet.create(node, { ephemeral: true }); // docs:end:connect_to_network // docs:start:verify_connection @@ -23,7 +23,13 @@ console.log("Chain ID:", nodeInfo.l1ChainId); const testAccounts = await getInitialTestAccountsData(); const [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { - return (await wallet.createSchnorrAccount(account.secret, account.salt, account.signingKey)).address; + return ( + await wallet.createSchnorrAccount( + account.secret, + account.salt, + account.signingKey, + ) + ).address; }), ); @@ -75,18 +81,33 @@ await deployMethod.send({ }); // docs:end:deploy_account_sponsored_fpc -// docs:start:deploy_account_fee_juice -// newAccount is the account created in the previous section -const deployMethodFeeJuice = await newAccount.getDeployMethod(); -await deployMethodFeeJuice.send({ - from: AztecAddress.ZERO, -}); -// docs:end:deploy_account_fee_juice +// docs:start:bridge_account_fee_juice +import { createExtendedL1Client } from "@aztec/ethereum/client"; +import { L1FeeJuicePortalManager } from "@aztec/aztec.js/ethereum"; +import { FeeJuicePaymentMethodWithClaim } from "@aztec/aztec.js/fee"; +import { createLogger } from "@aztec/aztec.js/log"; + +// Create a separate account to deploy with Fee Juice bridged from L1 +const feeJuiceSecret = Fr.random(); +const feeJuiceSalt = Fr.random(); +const feeJuiceAccount = await wallet.createSchnorrAccount( + feeJuiceSecret, + feeJuiceSalt, +); -// docs:start:verify_account_deployment -const metadata = await wallet.getContractMetadata(newAccount.address); -console.log("Account deployed:", metadata.isContractInitialized); -// docs:end:verify_account_deployment +// Bridge Fee Juice from L1 to the new account +const l1RpcUrl = process.env.ETHEREUM_HOST ?? "http://localhost:8545"; +const l1Mnemonic = + "test test test test test test test test test test test junk"; +const l1Client = createExtendedL1Client([l1RpcUrl], l1Mnemonic); +const logger = createLogger("docs:fee-juice-bridge"); +const portalManager = await L1FeeJuicePortalManager.new(node, l1Client, logger); +const claim = await portalManager.bridgeTokensPublic( + feeJuiceAccount.address, + 1000000000000000000000n, // 1000 Fee Juice + true, // mint on L1 (local network only) +); +// docs:end:bridge_account_fee_juice // docs:start:deploy_contract import { TokenContract } from "@aztec/noir-contracts.js/Token"; @@ -118,3 +139,22 @@ const balance = await token.methods console.log(`Alice's token balance: ${balance}`); // docs:end:simulate_function + +// docs:start:deploy_account_fee_juice +// Deploy the account using the bridged Fee Juice +const deployMethodFeeJuice = await feeJuiceAccount.getDeployMethod(); +await deployMethodFeeJuice.send({ + from: AztecAddress.ZERO, + fee: { + paymentMethod: new FeeJuicePaymentMethodWithClaim( + feeJuiceAccount.address, + claim, + ), + }, +}); +// docs:end:deploy_account_fee_juice + +// docs:start:verify_account_deployment +const metadata = await wallet.getContractMetadata(feeJuiceAccount.address); +console.log("Account deployed:", metadata.isContractInitialized); +// docs:end:verify_account_deployment diff --git a/docs/examples/ts/aztecjs_getting_started/index.ts b/docs/examples/ts/aztecjs_getting_started/index.ts index 9b3e7ec27ff5..6c526e4dd1da 100644 --- a/docs/examples/ts/aztecjs_getting_started/index.ts +++ b/docs/examples/ts/aztecjs_getting_started/index.ts @@ -2,8 +2,8 @@ import { EmbeddedWallet } from "@aztec/wallets/embedded"; import { getInitialTestAccountsData } from "@aztec/accounts/testing"; -const nodeUrl = "http://localhost:8080"; -const wallet = await EmbeddedWallet.create(nodeUrl); +const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080"; +const wallet = await EmbeddedWallet.create(nodeUrl, { ephemeral: true }); const [alice, bob] = await getInitialTestAccountsData(); await wallet.createSchnorrAccount(alice.secret, alice.salt); diff --git a/docs/examples/ts/aztecjs_runner/config.yaml b/docs/examples/ts/aztecjs_runner/config.yaml new file mode 100644 index 000000000000..f9743152b731 --- /dev/null +++ b/docs/examples/ts/aztecjs_runner/config.yaml @@ -0,0 +1,15 @@ +# Configuration for aztecjs_runner - runs all aztec.js example snippets +# This project executes the examples against a live local network + +# No custom contracts needed - using pre-built contracts from @aztec/noir-contracts.js +contracts: [] + +# Dependencies - union of all aztecjs example dependencies +dependencies: + - "@aztec/aztec.js" + - "@aztec/accounts" + - "@aztec/wallets" + - "@aztec/noir-contracts.js" + - "@aztec/ethereum" + - "@aztec/stdlib" + - "@aztec/foundation" diff --git a/docs/examples/ts/aztecjs_runner/index.ts b/docs/examples/ts/aztecjs_runner/index.ts new file mode 100644 index 000000000000..692ac60138ad --- /dev/null +++ b/docs/examples/ts/aztecjs_runner/index.ts @@ -0,0 +1,23 @@ +/** + * Placeholder for aztecjs_runner + * + * This project is a test runner for aztec.js documentation examples. + * Use ./run.sh to execute the examples against a live local network. + * + * The actual example files are in sibling directories: + * - aztecjs_connection/index.ts + * - aztecjs_advanced/index.ts + * - aztecjs_authwit/index.ts + * - aztecjs_testing/index.ts + */ + +// Import dependencies to verify they resolve correctly +import { createAztecNodeClient } from "@aztec/aztec.js/node"; +import { EmbeddedWallet } from "@aztec/wallets/embedded"; +import { TokenContract } from "@aztec/noir-contracts.js/Token"; +import { Fr } from "@aztec/aztec.js/fields"; + +// Export to satisfy noUnusedLocals +export { createAztecNodeClient, EmbeddedWallet, TokenContract, Fr }; + +console.log("aztecjs_runner: Use ./run.sh to execute examples against a live network"); diff --git a/docs/examples/ts/aztecjs_runner/run.sh b/docs/examples/ts/aztecjs_runner/run.sh new file mode 100755 index 000000000000..81a0540f00a4 --- /dev/null +++ b/docs/examples/ts/aztecjs_runner/run.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env bash +# Run aztec.js documentation examples against a live local network +# +# Prerequisites: +# - Local Aztec network running on localhost:8080 +# - yarn-project packages built +# +# Usage: +# ./run.sh # Run all examples +# ./run.sh connection # Run specific example +# ./run.sh getting_started advanced # Run multiple examples +# +# Available examples: connection, getting_started, advanced, authwit, testing + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +EXAMPLES_DIR="$(dirname "$SCRIPT_DIR")" +source "$EXAMPLES_DIR/lib.sh" +# Derive repo root from known path (docs/examples/ts/aztecjs_runner) to avoid +# git safe.directory failures when running inside Docker containers. +REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" +export AZTEC_NODE_URL="${AZTEC_NODE_URL:-http://localhost:8080}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Aztec.js Documentation Examples Test Runner ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# Check if network is running +echo -e "${YELLOW}Checking network connection at $AZTEC_NODE_URL...${NC}" +if ! curl -s "$AZTEC_NODE_URL" > /dev/null 2>&1; then + echo -e "${RED}ERROR: Cannot connect to Aztec network at $AZTEC_NODE_URL${NC}" + echo "Please start the network with: aztec start --local-network" + exit 1 +fi +echo -e "${GREEN}✓ Network is running${NC}" +echo "" + +# Setup function for a project +setup_project() { + local project_name=$1 + local project_dir="$EXAMPLES_DIR/$project_name" + + echo -e "${YELLOW}Setting up $project_name...${NC}" + + cd "$project_dir" + + # Clean up any previous setup + rm -rf node_modules .yarn package.json tsconfig.json artifacts 2>/dev/null || true + + # Run codegen for custom contracts if specified in config.yaml + local contract_count + contract_count="$(yq eval '.contracts | length' config.yaml 2>/dev/null || echo "0")" + + if [ "$contract_count" -gt 0 ]; then + local ARTIFACTS_DIR="$REPO_ROOT/docs/target" + local BUILDER_CLI="$REPO_ROOT/yarn-project/builder/dest/bin/cli.js" + + while IFS= read -r contract_name; do + local artifact="$ARTIFACTS_DIR/${contract_name}.json" + if [ -f "$artifact" ]; then + node --no-warnings "$BUILDER_CLI" codegen "$artifact" -o artifacts > /dev/null 2>&1 + fi + done < <(yq eval '.contracts[]' config.yaml) + fi + + # Initialize yarn + yarn init -y > /dev/null 2>&1 + yarn config set nodeLinker node-modules > /dev/null 2>&1 + + # Set package type to module for ESM + node -e "const pkg = require('./package.json'); pkg.type = 'module'; require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2));" + + # Read dependencies from config.yaml and install + parse_dependencies config.yaml "$REPO_ROOT" + if [ "$PARSED_DEPS_FOUND" = true ]; then + local all_link_deps=("${AZTEC_DEPS[@]}" "${EXPLICIT_LINK_DEPS[@]}") + [ ${#all_link_deps[@]} -gt 0 ] && yarn add "${all_link_deps[@]}" > /dev/null 2>&1 + [ ${#NPM_DEPS[@]} -gt 0 ] && yarn add "${NPM_DEPS[@]}" > /dev/null 2>&1 + fi + + yarn add -D typescript tsx > /dev/null 2>&1 + + # Copy tsconfig + cp "$EXAMPLES_DIR/tsconfig.template.json" tsconfig.json + + echo -e "${GREEN}✓ $project_name ready${NC}" +} + +# Run function for a project +run_project() { + local project_name=$1 + local project_dir="$EXAMPLES_DIR/$project_name" + + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE}▶ Running: $project_name${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + + cd "$project_dir" + + local start_time=$(date +%s) + local max_retries=5 + + for attempt in $(seq 1 $max_retries); do + if npx tsx index.ts; then + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + echo "" + echo -e "${GREEN}✓ PASS - $project_name (${duration}s)${NC}" + return 0 + fi + + if [ "$attempt" -lt "$max_retries" ]; then + echo -e "${YELLOW} Attempt $attempt/$max_retries failed, retrying in 10s...${NC}" + # Clean up PXE data between retries to avoid stale state + rm -rf "$project_dir/pxe_data_"* "$project_dir/wallet_data_"* 2>/dev/null || true + sleep 10 + fi + done + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + echo "" + echo -e "${RED}✗ FAIL - $project_name after $max_retries attempts (${duration}s)${NC}" + return 1 +} + +# Cleanup function +cleanup_project() { + local project_name=$1 + local project_dir="$EXAMPLES_DIR/$project_name" + + rm -rf "$project_dir/node_modules" \ + "$project_dir/.yarn" \ + "$project_dir/package.json" \ + "$project_dir/tsconfig.json" \ + "$project_dir/.yarnrc.yml" \ + "$project_dir/artifacts" 2>/dev/null || true + # Keep yarn.lock empty + > "$project_dir/yarn.lock" +} + +# Determine which examples to run +# Note: bob_token_contract and other custom contract examples require verification keys +# which aren't generated during docs compilation, so they're not included by default +if [ $# -eq 0 ]; then + EXAMPLES=("aztecjs_connection" "aztecjs_getting_started" "aztecjs_advanced" "aztecjs_authwit" "aztecjs_testing") +else + EXAMPLES=() + for arg in "$@"; do + case "$arg" in + connection) EXAMPLES+=("aztecjs_connection") ;; + getting_started) EXAMPLES+=("aztecjs_getting_started") ;; + advanced) EXAMPLES+=("aztecjs_advanced") ;; + authwit) EXAMPLES+=("aztecjs_authwit") ;; + testing) EXAMPLES+=("aztecjs_testing") ;; + *) + if [ -d "$EXAMPLES_DIR/aztecjs_$arg" ]; then + EXAMPLES+=("aztecjs_$arg") + elif [ -d "$EXAMPLES_DIR/$arg" ]; then + EXAMPLES+=("$arg") + else + echo -e "${RED}Unknown example: $arg${NC}" + exit 1 + fi + ;; + esac + done +fi + +echo "Running ${#EXAMPLES[@]} example(s): ${EXAMPLES[*]}" +echo "" + +# Track results +PASSED=0 +FAILED=0 +FAILED_EXAMPLES=() + +# Setup all projects first +echo -e "${YELLOW}Setting up projects...${NC}" +for example in "${EXAMPLES[@]}"; do + setup_project "$example" +done +echo "" + +# Run all projects +for example in "${EXAMPLES[@]}"; do + if run_project "$example"; then + PASSED=$((PASSED + 1)) + else + FAILED=$((FAILED + 1)) + FAILED_EXAMPLES+=("$example") + fi +done + +# Cleanup +echo "" +echo -e "${YELLOW}Cleaning up...${NC}" +for example in "${EXAMPLES[@]}"; do + cleanup_project "$example" +done + +# Summary +echo "" +echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" +echo -e "${BLUE} SUMMARY ${NC}" +echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}" +echo "" +echo -e " Passed: ${GREEN}$PASSED${NC}" +echo -e " Failed: ${RED}$FAILED${NC}" +echo "" + +if [ $FAILED -gt 0 ]; then + echo -e "${RED}Failed examples:${NC}" + for ex in "${FAILED_EXAMPLES[@]}"; do + echo -e " - $ex" + done + echo "" + exit 1 +else + echo -e "${GREEN}✅ All examples passed!${NC}" + echo "" + exit 0 +fi diff --git a/docs/examples/ts/aztecjs_runner/yarn.lock b/docs/examples/ts/aztecjs_runner/yarn.lock new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/examples/ts/aztecjs_testing/index.ts b/docs/examples/ts/aztecjs_testing/index.ts index 68af11270b77..930d756b853e 100644 --- a/docs/examples/ts/aztecjs_testing/index.ts +++ b/docs/examples/ts/aztecjs_testing/index.ts @@ -16,9 +16,9 @@ let token: TokenContract; // beforeAll equivalent - setup async function setup() { - const node = createAztecNodeClient("http://localhost:8080"); + const node = createAztecNodeClient(process.env.AZTEC_NODE_URL ?? "http://localhost:8080"); await waitForNode(node); - wallet = await EmbeddedWallet.create(node); + wallet = await EmbeddedWallet.create(node, { ephemeral: true }); const testAccounts = await getInitialTestAccountsData(); [aliceAddress, bobAddress] = await Promise.all( testAccounts.slice(0, 2).map(async (account) => { @@ -60,8 +60,8 @@ async function testTransferTokens() { .mint_to_public(aliceAddress, 1000n) .send({ from: aliceAddress }); - // Transfer to bob using the simple transfer method - await token.methods.transfer(bobAddress, 100n).send({ from: aliceAddress }); + // Transfer to bob using public transfer + await token.methods.transfer_in_public(aliceAddress, bobAddress, 100n, 0n).send({ from: aliceAddress }); const aliceBalance = await token.methods .balance_of_public(aliceAddress) @@ -83,7 +83,7 @@ async function testRevertOnOverTransfer() { try { await token.methods - .transfer(bobAddress, balance + 1n) + .transfer_in_public(aliceAddress, bobAddress, balance + 1n, 0n) .simulate({ from: aliceAddress }); throw new Error("Expected simulation to throw"); } catch (error) { diff --git a/docs/examples/ts/bootstrap.sh b/docs/examples/ts/bootstrap.sh index 0d682b8f2612..8623300c1169 100755 --- a/docs/examples/ts/bootstrap.sh +++ b/docs/examples/ts/bootstrap.sh @@ -2,6 +2,7 @@ set -euo pipefail source "$(git rev-parse --show-toplevel)/ci3/source_bootstrap" +source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" export REPO_ROOT=$(git rev-parse --show-toplevel) export ARTIFACTS_DIR="$REPO_ROOT/docs/target" @@ -56,6 +57,7 @@ validate_config() { return 0 } export -f validate_config +export -f parse_dependencies # Function to validate a single TS project # Must be exported for parallel execution @@ -137,45 +139,13 @@ validate_project() { # Read dependencies from config.yaml echo_stderr "Installing dependencies for '${project_name}'..." - # Separate @aztec packages (linked) from npm packages (external) - local aztec_deps=() - local explicit_link_deps=() - local npm_deps=() - local pkg - local has_deps=false - - while IFS= read -r pkg; do - has_deps=true - # Remove quotes and whitespace - pkg="${pkg//\"/}" - pkg="${pkg#"${pkg%%[![:space:]]*}"}" # ltrim - pkg="${pkg%"${pkg##*[![:space:]]}"}" # rtrim - - if [ -z "$pkg" ]; then - continue - fi + parse_dependencies config.yaml "$REPO_ROOT" - # Check if it's an external npm package (prefixed with npm:) - if [[ "$pkg" =~ ^npm: ]]; then - # External package: npm:viem -> viem - local npm_pkg="${pkg#npm:}" - npm_deps+=("$npm_pkg") - elif [[ "$pkg" =~ ^link: ]]; then - # Explicit link: link:@aztec/bb.js:barretenberg/ts -> @aztec/bb.js@link:$REPO_ROOT/barretenberg/ts - local link_spec="${pkg#link:}" - local link_pkg_name="${link_spec%%:*}" - local link_path="${link_spec#*:}" - explicit_link_deps+=("${link_pkg_name}@link:$REPO_ROOT/${link_path}") - elif [[ "$pkg" =~ ^@ ]]; then - # @aztec/* package - auto-link from yarn-project/ - local pkg_name="${pkg#@aztec/}" - aztec_deps+=("${pkg}@link:$REPO_ROOT/yarn-project/${pkg_name}") - else - echo_stderr "Warning: Unknown dependency format '$pkg' (use '@aztec/pkg', 'link:pkg:path', or 'npm:pkg')" - fi - done < <(yq eval '.dependencies[]' config.yaml) + local aztec_deps=("${AZTEC_DEPS[@]}") + local explicit_link_deps=("${EXPLICIT_LINK_DEPS[@]}") + local npm_deps=("${NPM_DEPS[@]}") - if [ "$has_deps" = true ]; then + if [ "$PARSED_DEPS_FOUND" = true ]; then # Install linked @aztec dependencies from yarn-project/ if [ ${#aztec_deps[@]} -gt 0 ]; then echo_stderr "Adding aztec deps: ${aztec_deps[*]}" diff --git a/docs/examples/ts/docker-compose.yml b/docs/examples/ts/docker-compose.yml new file mode 100644 index 000000000000..d881961f4f74 --- /dev/null +++ b/docs/examples/ts/docker-compose.yml @@ -0,0 +1,54 @@ +services: + fork: + image: aztecprotocol/build:3.0 + cpus: 1 + cpuset: ${CPU_LIST:-} + mem_limit: 2G + entrypoint: 'anvil --silent -p 8545 --host 0.0.0.0 --chain-id 31337' + + local-network: + image: aztecprotocol/build:3.0 + cpus: 4 + cpuset: ${CPU_LIST:-} + mem_limit: 8G + stop_grace_period: 60s + volumes: + - ../../../:/root/aztec-packages + - ${HOME}/.bb-crs:/root/.bb-crs + working_dir: /root/aztec-packages/yarn-project/aztec + command: 'node ./dest/bin start --local-network' + environment: + LOG_LEVEL: ${LOG_LEVEL:-verbose} + ETHEREUM_HOSTS: http://fork:8545 + L1_CHAIN_ID: 31337 + FORCE_COLOR: ${FORCE_COLOR:-1} + ARCHIVER_POLLING_INTERVAL_MS: 500 + P2P_BLOCK_CHECK_INTERVAL_MS: 500 + SEQ_POLLING_INTERVAL_MS: 500 + WS_BLOCK_CHECK_INTERVAL_MS: 500 + ARCHIVER_VIEM_POLLING_INTERVAL_MS: 500 + P2P_MIN_TX_POOL_AGE_MS: 0 + HARDWARE_CONCURRENCY: ${HARDWARE_CONCURRENCY:-} + + docs-examples: + image: aztecprotocol/build:3.0 + cpus: 4 + cpuset: ${CPU_LIST:-} + mem_limit: 8G + volumes: + - ../../../:/root/aztec-packages + - ${HOME}/.bb-crs:/root/.bb-crs + working_dir: /root/aztec-packages/docs/examples/ts/aztecjs_runner + environment: + LOG_LEVEL: ${LOG_LEVEL:-verbose} + FORCE_COLOR: ${FORCE_COLOR:-1} + AZTEC_NODE_URL: http://local-network:8080 + ETHEREUM_HOST: http://fork:8545 + entrypoint: > + bash -c ' + while ! nc -z local-network 8080; do sleep 1; done; + ./run.sh + ' + depends_on: + - local-network + - fork diff --git a/docs/examples/ts/lib.sh b/docs/examples/ts/lib.sh new file mode 100644 index 000000000000..03039d94befa --- /dev/null +++ b/docs/examples/ts/lib.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Shared utilities for docs/examples/ts scripts. +# Source this file — it has no shebang-executable purpose on its own. + +# parse_dependencies +# +# Reads a config.yaml via yq and classifies each dependency entry into one of +# three global arrays: +# AZTEC_DEPS — @aztec/* packages resolved to pkg@link:/yarn-project/ +# EXPLICIT_LINK_DEPS — link: packages resolved to pkg@link:/ +# NPM_DEPS — npm: packages (bare names, e.g. viem) +# +# Also sets PARSED_DEPS_FOUND to "true" if any dependency was found, "false" otherwise. +parse_dependencies() { + local config_file=$1 + local repo_root=$2 + + AZTEC_DEPS=() + EXPLICIT_LINK_DEPS=() + NPM_DEPS=() + PARSED_DEPS_FOUND=false + + local pkg + while IFS= read -r pkg; do + PARSED_DEPS_FOUND=true + # Remove quotes and whitespace + pkg="${pkg//\"/}" + pkg="${pkg#"${pkg%%[![:space:]]*}"}" # ltrim + pkg="${pkg%"${pkg##*[![:space:]]}"}" # rtrim + + if [ -z "$pkg" ]; then + continue + fi + + if [[ "$pkg" =~ ^npm: ]]; then + # External package: npm:viem -> viem + local npm_pkg="${pkg#npm:}" + NPM_DEPS+=("$npm_pkg") + elif [[ "$pkg" =~ ^link: ]]; then + # Explicit link: link:@aztec/bb.js:barretenberg/ts -> @aztec/bb.js@link:$repo_root/barretenberg/ts + local link_spec="${pkg#link:}" + local link_pkg_name="${link_spec%%:*}" + local link_path="${link_spec#*:}" + EXPLICIT_LINK_DEPS+=("${link_pkg_name}@link:${repo_root}/${link_path}") + elif [[ "$pkg" =~ ^@ ]]; then + # @aztec/* package - auto-link from yarn-project/ + local pkg_name="${pkg#@aztec/}" + AZTEC_DEPS+=("${pkg}@link:${repo_root}/yarn-project/${pkg_name}") + else + echo "Warning: Unknown dependency format '$pkg' (use '@aztec/pkg', 'link:pkg:path', or 'npm:pkg')" >&2 + fi + done < <(yq eval '.dependencies[]' "$config_file") +} diff --git a/yarn-project/end-to-end/src/composed/docs_examples.test.ts b/yarn-project/end-to-end/src/composed/docs_examples.test.ts deleted file mode 100644 index 3d0f4d548cd7..000000000000 --- a/yarn-project/end-to-end/src/composed/docs_examples.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { getInitialTestAccountsData } from '@aztec/accounts/testing'; -import { Contract } from '@aztec/aztec.js/contracts'; -import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields'; -import { createAztecNodeClient } from '@aztec/aztec.js/node'; -import { TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; - -import { TestWallet } from '../test-wallet/test_wallet.js'; - -// To run these tests against a local network: -// 1. Start a local Ethereum node (Anvil): -// anvil --host 127.0.0.1 --port 8545 -// -// 2. Start the Aztec local network: -// cd yarn-project/aztec -// NODE_NO_WARNINGS=1 ETHEREUM_HOSTS=http://127.0.0.1:8545 node ./dest/bin/index.js start --local-network -// -// 3. Run the tests: -// yarn test:e2e docs_examples.test.ts -describe('docs_examples', () => { - it('deploys and interacts with a token contract', async () => { - const AZTEC_NODE_URL = process.env.AZTEC_NODE_URL || 'http://localhost:8080'; - const node = createAztecNodeClient(AZTEC_NODE_URL); - - const wallet = await TestWallet.create(node); - const secretKey = Fr.random(); - const signingPrivateKey = GrumpkinScalar.random(); - - // Use a pre-funded wallet to pay for the fees for the deployments. - const [accountData] = await getInitialTestAccountsData(); - const prefundedAccount = await wallet.createSchnorrAccount(accountData.secret, accountData.salt); - const newAccountManager = await wallet.createSchnorrAccount(secretKey, Fr.random(), signingPrivateKey); - const newAccountDeployMethod = await newAccountManager.getDeployMethod(); - await newAccountDeployMethod.send({ - from: prefundedAccount.address, - }); - const newAccountAddress = newAccountManager.address; - const defaultAccountAddress = prefundedAccount.address; - - const deployedContract = await TokenContract.deploy( - wallet, // wallet instance - defaultAccountAddress, // account - 'TokenName', // constructor arg1 - 'TokenSymbol', // constructor arg2 - 18, - ).send({ from: defaultAccountAddress }); - - const contract = Contract.at(deployedContract.address, TokenContractArtifact, wallet); - - await contract.methods.mint_to_public(newAccountAddress, 1).send({ from: defaultAccountAddress }); - - // docs:start:simulate_function - const balance = await contract.methods.balance_of_public(newAccountAddress).simulate({ from: newAccountAddress }); - expect(balance).toEqual(1n); - // docs:end:simulate_function - }); -});