|
| 1 | +# Aztec Wallet Web App Tutorial |
| 2 | + |
| 3 | +This tutorial demonstrates how to build a browser-based wallet application for Aztec using React and Vite. It showcases creating wallets, deploying contracts, and interacting with the Aztec network directly from a web browser. |
| 4 | + |
| 5 | +## What You'll Build |
| 6 | + |
| 7 | +A web application that demonstrates three core Aztec operations: |
| 8 | + |
| 9 | +1. **Create Wallet** - Initialize a TestWallet with a Schnorr account |
| 10 | +2. **Deploy Contract** - Deploy the PrivateVoting contract to Aztec |
| 11 | +3. **Cast Vote** - Interact with the deployed contract |
| 12 | + |
| 13 | +## Prerequisites |
| 14 | + |
| 15 | +- Node.js v18 or higher |
| 16 | +- Yarn package manager |
| 17 | +- A running Aztec sandbox instance |
| 18 | + |
| 19 | +## Quick Start |
| 20 | + |
| 21 | +### 1. Install Dependencies |
| 22 | + |
| 23 | +```bash |
| 24 | +yarn install |
| 25 | +``` |
| 26 | + |
| 27 | +### 2. Start Aztec Sandbox |
| 28 | + |
| 29 | +```bash |
| 30 | +aztec start --sandbox |
| 31 | +``` |
| 32 | + |
| 33 | +The sandbox must be running on `http://localhost:8080` before starting the app. |
| 34 | + |
| 35 | +### 3. Start Development Server |
| 36 | + |
| 37 | +```bash |
| 38 | +yarn dev |
| 39 | +``` |
| 40 | + |
| 41 | +Open your browser to `http://localhost:5173` and click through the steps. |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Creating From Scratch |
| 46 | + |
| 47 | +If you want to build this project from scratch, follow these steps: |
| 48 | + |
| 49 | +### 1. Create Vite Project |
| 50 | + |
| 51 | +```bash |
| 52 | +yarn create vite testwallet-webapp-tutorial-new --template react-ts |
| 53 | +cd testwallet-webapp-tutorial-new |
| 54 | +yarn install |
| 55 | +``` |
| 56 | + |
| 57 | +### 2. Install Aztec Dependencies |
| 58 | + |
| 59 | +```bash |
| 60 | +yarn add @aztec/ [email protected] \ |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | +``` |
| 65 | + |
| 66 | +### 3. Install Build Tooling Dependencies |
| 67 | + |
| 68 | +```bash |
| 69 | +yarn add -D vite-plugin-node-polyfills @types/node |
| 70 | +``` |
| 71 | + |
| 72 | +### 4. Configure Vite |
| 73 | + |
| 74 | +Replace the default `vite.config.ts` with: |
| 75 | + |
| 76 | +```typescript |
| 77 | +import { defineConfig, searchForWorkspaceRoot } from 'vite' |
| 78 | +import react from '@vitejs/plugin-react' |
| 79 | +import { nodePolyfills, type PolyfillOptions } from "vite-plugin-node-polyfills"; |
| 80 | + |
| 81 | +const nodeModulesPath = `${searchForWorkspaceRoot(process.cwd())}/node_modules`; |
| 82 | + |
| 83 | +// Workaround for vite-plugin-node-polyfills module resolution bug |
| 84 | +// See: https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/81 |
| 85 | +const nodePolyfillsFix = (options?: PolyfillOptions | undefined) => { |
| 86 | + return { |
| 87 | + ...nodePolyfills(options), |
| 88 | + resolveId(source: string) { |
| 89 | + const m = |
| 90 | + /^vite-plugin-node-polyfills\/shims\/(buffer|global|process)$/.exec( |
| 91 | + source, |
| 92 | + ); |
| 93 | + if (m) { |
| 94 | + return `${nodeModulesPath}/vite-plugin-node-polyfills/shims/${m[1]}/dist/index.cjs`; |
| 95 | + } |
| 96 | + }, |
| 97 | + }; |
| 98 | +}; |
| 99 | + |
| 100 | +export default defineConfig({ |
| 101 | + server: { |
| 102 | + // Headers needed for bb WASM to work in multithreaded mode |
| 103 | + headers: { |
| 104 | + "Cross-Origin-Opener-Policy": "same-origin", |
| 105 | + "Cross-Origin-Embedder-Policy": "require-corp", |
| 106 | + }, |
| 107 | + }, |
| 108 | + plugins: [ |
| 109 | + react(), |
| 110 | + nodePolyfillsFix({ |
| 111 | + include: ["buffer", "path", "process", "net", "tty"], |
| 112 | + }), |
| 113 | + ], |
| 114 | + optimizeDeps: { |
| 115 | + include: ['pino', 'pino/browser'], |
| 116 | + exclude: ['@aztec/noir-noirc_abi', '@aztec/noir-acvm_js', '@aztec/bb.js', '@aztec/noir-noir_js'] |
| 117 | + }, |
| 118 | +}) |
| 119 | +``` |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## Understanding the Configuration |
| 124 | + |
| 125 | +### Why These Vite Settings Are Required |
| 126 | + |
| 127 | +Aztec's browser support requires several special configurations: |
| 128 | + |
| 129 | +#### 1. WASM Multithreading Headers |
| 130 | + |
| 131 | +```typescript |
| 132 | +headers: { |
| 133 | + "Cross-Origin-Opener-Policy": "same-origin", |
| 134 | + "Cross-Origin-Embedder-Policy": "require-corp", |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +Aztec uses WebAssembly with SharedArrayBuffer for multithreaded cryptographic operations. Modern browsers require these CORS headers to enable SharedArrayBuffer for security reasons. |
| 139 | + |
| 140 | +#### 2. Node.js Polyfills |
| 141 | + |
| 142 | +```typescript |
| 143 | +nodePolyfillsFix({ |
| 144 | + include: ["buffer", "path", "process", "net", "tty"], |
| 145 | +}) |
| 146 | +``` |
| 147 | + |
| 148 | +Aztec SDK packages (`@aztec/aztec.js`, `@aztec/accounts`, etc.) were originally designed for Node.js and depend on Node.js APIs that don't exist in browsers: |
| 149 | + |
| 150 | +- **`buffer`** - Binary data handling used by cryptographic operations and WASM modules |
| 151 | +- **`process`** - Environment variables and process information |
| 152 | +- **`path`** - File path utilities used by module resolution |
| 153 | +- **`net` & `tty`** - Required by the Pino logger used throughout Aztec SDK |
| 154 | + |
| 155 | +The `vite-plugin-node-polyfills` provides browser-compatible implementations of these APIs. |
| 156 | + |
| 157 | +**Note**: The `nodePolyfillsFix` wrapper is a workaround for a module resolution bug in vite-plugin-node-polyfills v0.19.0+ where the plugin's polyfill imports fail to resolve correctly during Vite's optimization phase, causing runtime errors even though builds succeed. The fix manually resolves polyfill paths to their absolute file system locations. |
| 158 | + |
| 159 | +#### 3. Dependency Optimization |
| 160 | + |
| 161 | +```typescript |
| 162 | +optimizeDeps: { |
| 163 | + include: ['pino', 'pino/browser'], |
| 164 | + exclude: ['@aztec/noir-noirc_abi', '@aztec/noir-acvm_js', '@aztec/bb.js', '@aztec/noir-noir_js'] |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +- **Include**: Pre-bundle the Pino logger for better performance |
| 169 | +- **Exclude**: Prevent Vite from optimizing Aztec's WASM modules, which would break their loading mechanism |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Key Code Patterns |
| 174 | + |
| 175 | +### Connecting to Aztec |
| 176 | + |
| 177 | +```typescript |
| 178 | +import { createAztecNodeClient } from '@aztec/aztec.js/node' |
| 179 | +import { getPXEConfig } from '@aztec/pxe/client/lazy'; |
| 180 | +import { TestWallet } from '@aztec/test-wallet/client/lazy'; |
| 181 | + |
| 182 | +const nodeURL = 'http://localhost:8080'; |
| 183 | +const aztecNode = await createAztecNodeClient(nodeURL); |
| 184 | +const config = getPXEConfig(); |
| 185 | +config.dataDirectory = 'pxe'; |
| 186 | +const wallet = await TestWallet.create(aztecNode, config); |
| 187 | +``` |
| 188 | + |
| 189 | +### Creating a Schnorr Account |
| 190 | + |
| 191 | +```typescript |
| 192 | +import { getInitialTestAccountsData } from '@aztec/accounts/testing'; |
| 193 | + |
| 194 | +const [accountData] = await getInitialTestAccountsData(); |
| 195 | +const accountManager = await wallet.createSchnorrAccount( |
| 196 | + accountData.secret, |
| 197 | + accountData.salt, |
| 198 | + accountData.signingKey |
| 199 | +); |
| 200 | +const accountAddress = accountManager.address; |
| 201 | +``` |
| 202 | + |
| 203 | +### Deploying a Contract |
| 204 | + |
| 205 | +```typescript |
| 206 | +import { PrivateVotingContract } from '@aztec/noir-contracts.js/PrivateVoting'; |
| 207 | + |
| 208 | +const deployedContract = await PrivateVotingContract.deploy(wallet, address) |
| 209 | + .send({ from: address }) |
| 210 | + .deployed(); |
| 211 | +``` |
| 212 | + |
| 213 | +### Calling Contract Methods |
| 214 | + |
| 215 | +```typescript |
| 216 | +import { AztecAddress } from '@aztec/aztec.js/addresses' |
| 217 | + |
| 218 | +const contract = await PrivateVotingContract.at(contractAddress, wallet); |
| 219 | +await contract.methods.cast_vote(AztecAddress.random()) |
| 220 | + .send({ from: address }) |
| 221 | + .wait(); |
| 222 | +``` |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +## Troubleshooting |
| 227 | + |
| 228 | +### "Buffer is not defined" or "process is not defined" |
| 229 | + |
| 230 | +**Problem**: Node.js polyfills aren't loading correctly. |
| 231 | + |
| 232 | +**Solution**: Ensure you're using the `nodePolyfillsFix` wrapper in your Vite config (not the plain `nodePolyfills` plugin). The wrapper fixes a module resolution bug in the polyfills plugin. |
| 233 | + |
| 234 | +### WASM Loading Errors |
| 235 | + |
| 236 | +**Problem**: Errors about SharedArrayBuffer or WASM initialization failures. |
| 237 | + |
| 238 | +**Solution**: Verify the CORS headers in your Vite config: |
| 239 | +```typescript |
| 240 | +headers: { |
| 241 | + "Cross-Origin-Opener-Policy": "same-origin", |
| 242 | + "Cross-Origin-Embedder-Policy": "require-corp", |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +Check browser console - these headers must be present in the response. |
| 247 | + |
| 248 | +### Module Not Found Errors |
| 249 | + |
| 250 | +**Problem**: Cannot resolve Aztec packages or their dependencies. |
| 251 | + |
| 252 | +**Solution**: |
| 253 | +- Ensure all Aztec packages are on the **same version** (e.g., `3.0.0-devnet.5`) |
| 254 | +- Verify WASM modules are excluded in `optimizeDeps.exclude` |
| 255 | +- Clear Vite cache: `rm -rf node_modules/.vite` |
| 256 | + |
| 257 | +### "Cannot connect to PXE" or Network Errors |
| 258 | + |
| 259 | +**Problem**: Application can't reach the Aztec sandbox. |
| 260 | + |
| 261 | +**Solution**: |
| 262 | +- Ensure Aztec sandbox is running: `aztec start --sandbox` |
| 263 | +- Verify it's accessible at `http://localhost:8080` |
| 264 | +- Check CORS if sandbox is on a different port |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +## Project Structure |
| 269 | + |
| 270 | +``` |
| 271 | +test-wallet-webapp/ |
| 272 | +├── src/ |
| 273 | +│ ├── App.tsx # Main application component |
| 274 | +│ ├── main.tsx # Application entry point |
| 275 | +│ ├── consoleInterceptor.ts # Console output capture utility |
| 276 | +│ └── ... |
| 277 | +├── vite.config.ts # Vite configuration with Aztec compatibility |
| 278 | +├── package.json # Dependencies |
| 279 | +└── README.md # This file |
| 280 | +``` |
| 281 | + |
| 282 | +--- |
| 283 | + |
| 284 | +## Learn More |
| 285 | + |
| 286 | +- [Aztec Documentation](https://docs.aztec.network/) |
| 287 | +- [Aztec.js API Reference](https://docs.aztec.network/apis/aztecjs) |
| 288 | +- [Vite Documentation](https://vitejs.dev/) |
| 289 | +- [vite-plugin-node-polyfills Issue #81](https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/81) |
0 commit comments