From 26007a292261a9478540e22bf86370748ecbba83 Mon Sep 17 00:00:00 2001 From: Josh Crites Date: Mon, 2 Mar 2026 13:34:28 -0500 Subject: [PATCH] Add simulate-before-send pattern to all TypeScript skill examples Without .simulate() before .send(), failing Aztec transactions hang for up to 600s with opaque errors. .simulate() surfaces revert reasons instantly. This adds the pattern to all ~60 .send() calls across 15 skill files plus documents it in CLAUDE.md. Transformation rules applied: - Standard sends: add .simulate({ from }) before .send() - Deploy chains: break .deploy().send() into variable, simulate, send - Account deploys: break getDeployMethod().send() chains similarly - Error tests: replace .send() with .simulate() entirely - Helper functions: add .simulate() inside before .send() - Parallel sends: simulate all upfront, then send in parallel - AuthWit: simulate without authWitnesses (only from) Refs: AztecProtocol/aztec-starter#245 --- CLAUDE.md | 36 +++++++++++++++++++ skills/aztec-accounts/SKILL.md | 4 ++- skills/aztec-accounts/account-recovery.md | 5 ++- skills/aztec-accounts/schnorr-accounts.md | 11 ++++-- skills/aztec-deploy/SKILL.md | 4 ++- skills/aztec-deploy/deploy-script.md | 3 ++ skills/aztec-deploy/fee-payment.md | 10 ++++-- skills/aztec-e2e-testing/SKILL.md | 9 +++-- skills/aztec-e2e-testing/jest-setup.md | 12 +++++-- skills/aztec-e2e-testing/sponsored-testing.md | 22 +++++++++--- skills/aztec-e2e-testing/test-patterns.md | 24 +++++++------ skills/aztec-typescript/SKILL.md | 6 ++-- skills/aztec-typescript/authwit-frontend.md | 9 +++-- skills/aztec-typescript/contract-client.md | 7 +++- .../aztec-typescript/transaction-patterns.md | 35 +++++++++++++----- skills/aztec-typescript/wallet-setup.md | 5 ++- 16 files changed, 160 insertions(+), 42 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 71e2e89..0d6b04e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -181,6 +181,42 @@ Or pass version to sync: aztec_sync_repos({ version: "v4.0.0-devnet.2-patch.1", force: true }) ``` +## ⚠️ Critical: Simulate Before Send + +**Always call `.simulate()` before `.send()` for every state-changing transaction.** + +Without simulation, failing transactions hang for up to 600 seconds with opaque errors. `.simulate()` surfaces revert reasons instantly. + +```typescript +// Standard method call +await contract.methods.myMethod(args).simulate({ from: account.address }); +const tx = await contract.methods.myMethod(args).send({ + from: account.address, + fee: { paymentMethod }, + wait: { timeout: 600 } +}); + +// Contract deployment (break the chain) +const deployRequest = MyContract.deploy(wallet, admin); +await deployRequest.simulate({ from: admin }); +const contract = await deployRequest.send({ + from: admin, fee: { paymentMethod }, wait: { timeout, returnReceipt: true } +}); + +// Account deployment (break the chain) +const deployMethod = await account.getDeployMethod(); +await deployMethod.simulate({ from: AztecAddress.ZERO }); +await deployMethod.send({ + from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout } +}); +``` + +**Key rules:** +- `.simulate()` only needs `from` — no `fee`, `wait`, or `authWitnesses` +- For deploy chains, break into a variable first, then simulate, then send +- For error tests, use `.simulate()` instead of `.send()` to catch reverts +- View/read-only calls already use `.simulate()` — skip those + ## Useful Resources - Aztec Documentation: https://docs.aztec.network diff --git a/skills/aztec-accounts/SKILL.md b/skills/aztec-accounts/SKILL.md index 00e5f34..3186b9f 100644 --- a/skills/aztec-accounts/SKILL.md +++ b/skills/aztec-accounts/SKILL.md @@ -32,7 +32,9 @@ const account = await wallet.createSchnorrAccount(secretKey, salt, signingKey); console.log(`Account address: ${account.address}`); // Deploy account (required before use) -await (await account.getDeployMethod()).send({ +const deployMethod = await account.getDeployMethod(); +await deployMethod.simulate({ from: AztecAddress.ZERO }); +await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: 120000 } diff --git a/skills/aztec-accounts/account-recovery.md b/skills/aztec-accounts/account-recovery.md index d5f9f74..6683387 100644 --- a/skills/aztec-accounts/account-recovery.md +++ b/skills/aztec-accounts/account-recovery.md @@ -154,6 +154,7 @@ async function useRecoveredAccount() { // Use account for transactions const contract = MyContract.at(contractAddress, wallet); + await contract.methods.myMethod(args).simulate({ from: account.address }); await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod }, @@ -191,7 +192,9 @@ const isDeployed = await isAccountDeployed(wallet, account); if (!isDeployed) { console.log('Account not yet deployed, deploying...'); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 120000 } diff --git a/skills/aztec-accounts/schnorr-accounts.md b/skills/aztec-accounts/schnorr-accounts.md index 2413a45..268d65b 100644 --- a/skills/aztec-accounts/schnorr-accounts.md +++ b/skills/aztec-accounts/schnorr-accounts.md @@ -42,6 +42,7 @@ const paymentMethod = new SponsoredFeePaymentMethod(sponsoredFPC.address); const deployMethod = await account.getDeployMethod(); // Deploy +await deployMethod.simulate({ from: AztecAddress.ZERO }); const tx = await deployMethod.send({ from: AztecAddress.ZERO, // No sender for account deployment fee: { paymentMethod }, @@ -101,6 +102,7 @@ export async function createAndDeployAccount( // Deploy account const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); const tx = await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, @@ -137,7 +139,9 @@ async function createTestAccounts(wallet: EmbeddedWallet, count: number) { const account = await wallet.createSchnorrAccount(secretKey, salt, signingKey); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 120000 } @@ -181,7 +185,9 @@ export async function generateSchnorrAccounts( // Step 2: Deploy all accounts (sends transactions) for (const account of accounts) { - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 120000 } @@ -207,6 +213,7 @@ After deployment, register accounts for transaction sending: await wallet.registerSender(account.address); // Now the wallet can send transactions from this account +await contract.methods.myMethod(args).simulate({ from: account.address }); await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod }, diff --git a/skills/aztec-deploy/SKILL.md b/skills/aztec-deploy/SKILL.md index 25b5ff7..f3299fc 100644 --- a/skills/aztec-deploy/SKILL.md +++ b/skills/aztec-deploy/SKILL.md @@ -43,7 +43,9 @@ async function main() { const account = await deploySchnorrAccount(wallet); // 4. Deploy contract - const { contract } = await MyContract.deploy(wallet, account.address).send({ + const deployRequest = MyContract.deploy(wallet, account.address); + await deployRequest.simulate({ from: account.address }); + const { contract } = await deployRequest.send({ from: account.address, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout, returnReceipt: true } diff --git a/skills/aztec-deploy/deploy-script.md b/skills/aztec-deploy/deploy-script.md index 6e077a5..5c67e34 100644 --- a/skills/aztec-deploy/deploy-script.md +++ b/skills/aztec-deploy/deploy-script.md @@ -62,6 +62,7 @@ async function main() { // Deploy account const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); const accountTx = await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: sponsoredPaymentMethod }, @@ -82,6 +83,7 @@ async function main() { const contractDeployMethod = MyContract.deploy(wallet, ...constructorArgs); logger.info('Waiting for deployment transaction to be mined...'); + await contractDeployMethod.simulate({ from: account.address }); const { contract } = await contractDeployMethod.send({ from: account.address, fee: { paymentMethod: sponsoredPaymentMethod }, @@ -191,6 +193,7 @@ export async function deploySchnorrAccount(wallet?: EmbeddedWallet): Promise { const signingKey = GrumpkinScalar.random(); const salt = Fr.random(); account = await wallet.createSchnorrAccount(secretKey, salt, signingKey); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, }); // Deploy contract - contract = await MyContract.deploy(wallet, account.address).send({ + const deployRequest = MyContract.deploy(wallet, account.address); + await deployRequest.simulate({ from: account.address }); + contract = await deployRequest.send({ from: account.address, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -63,6 +67,7 @@ describe("MyContract", () => { }, 600000); it("should perform an action", async () => { + await contract.methods.myMethod(args).simulate({ from: account.address }); const tx = await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod }, diff --git a/skills/aztec-e2e-testing/jest-setup.md b/skills/aztec-e2e-testing/jest-setup.md index 08200ab..2a508a6 100644 --- a/skills/aztec-e2e-testing/jest-setup.md +++ b/skills/aztec-e2e-testing/jest-setup.md @@ -131,7 +131,9 @@ describe("MyContract", () => { const salt = Fr.random(); account = await wallet.createSchnorrAccount(secretKey, salt, signingKey); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -140,7 +142,9 @@ describe("MyContract", () => { await wallet.registerSender(account.address); // 4. Deploy contract - contract = await MyContract.deploy(wallet, account.address).send({ + const deployRequest = MyContract.deploy(wallet, account.address); + await deployRequest.simulate({ from: account.address }); + contract = await deployRequest.send({ from: account.address, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -198,7 +202,9 @@ export async function createTestContext(accountCount: number = 1): Promise { ```typescript // Deploy account with sponsored fees (no sender required) -await (await account.getDeployMethod()).send({ +const deployMethod = await account.getDeployMethod(); +await deployMethod.simulate({ from: AztecAddress.ZERO }); +await deployMethod.send({ from: AztecAddress.ZERO, // ZERO address for account deployment fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -42,7 +44,9 @@ await (await account.getDeployMethod()).send({ ```typescript // Deploy contract with sponsored fees -const contract = await MyContract.deploy(wallet, admin).send({ +const deployRequest = MyContract.deploy(wallet, admin); +await deployRequest.simulate({ from: admin }); +const contract = await deployRequest.send({ from: admin, fee: { paymentMethod: sponsoredPaymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -53,6 +57,7 @@ const contract = await MyContract.deploy(wallet, admin).send({ ```typescript // Execute transaction with sponsored fees +await contract.methods.myMethod(args).simulate({ from: account.address }); await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod: sponsoredPaymentMethod }, @@ -98,7 +103,9 @@ describe("MyContract with Sponsored Fees", () => { GrumpkinScalar.random() ); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -107,7 +114,9 @@ describe("MyContract with Sponsored Fees", () => { await wallet.registerSender(account.address); // 4. Deploy contract - contract = await MyContract.deploy(wallet, account.address).send({ + const deployRequest = MyContract.deploy(wallet, account.address); + await deployRequest.simulate({ from: account.address }); + contract = await deployRequest.send({ from: account.address, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, @@ -116,6 +125,7 @@ describe("MyContract with Sponsored Fees", () => { }, 600000); it("should perform action with sponsored fees", async () => { + await contract.methods.myAction(args).simulate({ from: account.address }); const tx = await contract.methods.myAction(args).send({ from: account.address, fee: { paymentMethod }, @@ -187,7 +197,9 @@ async function createSponsoredAccount( GrumpkinScalar.random() ); - await (await account.getDeployMethod()).send({ + const deployMethod = await account.getDeployMethod(); + await deployMethod.simulate({ from: AztecAddress.ZERO }); + await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: getTimeouts().deployTimeout }, diff --git a/skills/aztec-e2e-testing/test-patterns.md b/skills/aztec-e2e-testing/test-patterns.md index 44872f7..a2f1b0a 100644 --- a/skills/aztec-e2e-testing/test-patterns.md +++ b/skills/aztec-e2e-testing/test-patterns.md @@ -16,6 +16,7 @@ it("should deploy contract successfully", async () => { ```typescript it("should execute public function", async () => { + await contract.methods.create_item(itemId).simulate({ from: account.address }); const tx = await contract.methods.create_item(itemId).send({ from: account.address, fee: { paymentMethod }, @@ -30,6 +31,7 @@ it("should execute public function", async () => { ```typescript it("should execute private function", async () => { + await contract.methods.transfer(recipient, amount).simulate({ from: sender.address }); const tx = await contract.methods.transfer(recipient, amount).send({ from: sender.address, fee: { paymentMethod }, @@ -45,11 +47,7 @@ it("should execute private function", async () => { ```typescript it("should reject invalid input", async () => { await expect( - contract.methods.create_item(invalidId).send({ - from: account.address, - fee: { paymentMethod }, - wait: { timeout: getTimeouts().txTimeout }, - }) + contract.methods.create_item(invalidId).simulate({ from: account.address }) ).rejects.toThrow(); }, 60000); ``` @@ -60,11 +58,7 @@ it("should reject invalid input", async () => { it("should reject unauthorized caller", async () => { // Non-admin tries to perform admin action await expect( - contract.methods.admin_function().send({ - from: nonAdminAccount.address, - fee: { paymentMethod }, - wait: { timeout: getTimeouts().txTimeout }, - }) + contract.methods.admin_function().simulate({ from: nonAdminAccount.address }) ).rejects.toThrow(); }, 60000); ``` @@ -84,6 +78,7 @@ describe("Multi-user tests", () => { it("should allow transfer between users", async () => { // User1 transfers to User2 + await contract.methods.transfer(user2.address, amount).simulate({ from: user1.address }); await contract.methods.transfer(user2.address, amount).send({ from: user1.address, fee: { paymentMethod }, @@ -111,6 +106,7 @@ it("should complete full workflow", async () => { const gameId = new Fr(100); // Step 1: Create game + await contract.methods.create_game(gameId).simulate({ from: player1.address }); await contract.methods.create_game(gameId).send({ from: player1.address, fee: { paymentMethod }, @@ -118,6 +114,7 @@ it("should complete full workflow", async () => { }); // Step 2: Join game + await contract.methods.join_game(gameId).simulate({ from: player2.address }); await contract.methods.join_game(gameId).send({ from: player2.address, fee: { paymentMethod }, @@ -126,12 +123,14 @@ it("should complete full workflow", async () => { // Step 3: Play rounds for (let round = 1; round <= 3; round++) { + await contract.methods.play_round(gameId, round, 2, 2, 2, 2, 1).simulate({ from: player1.address }); await contract.methods.play_round(gameId, round, 2, 2, 2, 2, 1).send({ from: player1.address, fee: { paymentMethod }, wait: { timeout: getTimeouts().txTimeout }, }); + await contract.methods.play_round(gameId, round, 1, 1, 2, 2, 3).simulate({ from: player2.address }); await contract.methods.play_round(gameId, round, 1, 1, 2, 2, 3).send({ from: player2.address, fee: { paymentMethod }, @@ -140,12 +139,14 @@ it("should complete full workflow", async () => { } // Step 4: Finish + await contract.methods.finish_game(gameId).simulate({ from: player1.address }); await contract.methods.finish_game(gameId).send({ from: player1.address, fee: { paymentMethod }, wait: { timeout: getTimeouts().txTimeout }, }); + await contract.methods.finish_game(gameId).simulate({ from: player2.address }); await contract.methods.finish_game(gameId).send({ from: player2.address, fee: { paymentMethod }, @@ -168,6 +169,7 @@ async function executeTx( paymentMethod: SponsoredFeePaymentMethod, timeout: number ) { + await contract.methods[method](...args).simulate({ from }); const tx = await contract.methods[method](...args).send({ from, fee: { paymentMethod }, @@ -231,6 +233,7 @@ describe("Tests requiring cleanup", () => { }); it("test 1", async () => { + await contract.methods.create_item(testId).simulate({ from: account.address }); await contract.methods.create_item(testId).send({ from: account.address, fee: { paymentMethod }, @@ -239,6 +242,7 @@ describe("Tests requiring cleanup", () => { }); it("test 2", async () => { + await contract.methods.create_item(testId).simulate({ from: account.address }); await contract.methods.create_item(testId).send({ from: account.address, fee: { paymentMethod }, diff --git a/skills/aztec-typescript/SKILL.md b/skills/aztec-typescript/SKILL.md index 728d8cb..97d826a 100644 --- a/skills/aztec-typescript/SKILL.md +++ b/skills/aztec-typescript/SKILL.md @@ -25,6 +25,7 @@ import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee"; const contract = MyContract.at(contractAddress, wallet); // Call a method +await contract.methods.myMethod(arg1, arg2).simulate({ from: account.address }); const tx = await contract.methods.myMethod(arg1, arg2).send({ from: account.address, fee: { paymentMethod }, @@ -72,5 +73,6 @@ import { type Logger, createLogger } from "@aztec/foundation/log"; 1. Get contract instance (`at()` or `deploy()`) 2. Call method via `contract.methods.xxx()` -3. Send with fee payment and wait `.send({ from, fee, wait: { timeout } })` -4. Transaction resolves when confirmed +3. Simulate first to catch errors `.simulate({ from })` +4. Send with fee payment and wait `.send({ from, fee, wait: { timeout } })` +5. Transaction resolves when confirmed diff --git a/skills/aztec-typescript/authwit-frontend.md b/skills/aztec-typescript/authwit-frontend.md index 8556034..97690af 100644 --- a/skills/aztec-typescript/authwit-frontend.md +++ b/skills/aztec-typescript/authwit-frontend.md @@ -32,7 +32,8 @@ const witness = await wallet.createAuthWit(fromAddress, { action, }); -// 4. Include witness in send() options +// 4. Simulate first (without authWitnesses), then send with witness +await vaultContract.methods.deposit(amount).simulate({ from: wallet.address }); await vaultContract.methods .deposit(amount) .send({ from: wallet.address, authWitnesses: [witness], wait: { timeout: 600 } }); @@ -50,6 +51,7 @@ await vaultContract.methods ```typescript // Private AuthWit - included with transaction const witness = await wallet.createAuthWit(fromAddress, { caller, action }); +await someAction.simulate({ from: wallet.address }); await someAction.send({ authWitnesses: [witness], wait: { timeout: 600 } }); ``` @@ -62,9 +64,11 @@ await someAction.send({ authWitnesses: [witness], wait: { timeout: 600 } }); ```typescript // Public AuthWit - registered on-chain first const innerHash = await action.computeInnerAuthWitHash(); +await wallet.setPublicAuthWit(innerHash, true).simulate({ from: wallet.address }); await wallet.setPublicAuthWit(innerHash, true).send({ wait: { timeout: 600 } }); // Later, the authorized action can execute +await someAction.simulate({ from: wallet.address }); await someAction.send({ wait: { timeout: 600 } }); ``` @@ -84,7 +88,8 @@ const witness2 = await wallet2.createAuthWit(from2Address, { action: action2, }); -// Include all witnesses in send() options +// Simulate first (without authWitnesses), then send with witnesses +await contract.methods.multiSourceOperation().simulate({ from }); await contract.methods .multiSourceOperation() .send({ from, authWitnesses: [witness1, witness2], wait: { timeout: 600 } }); diff --git a/skills/aztec-typescript/contract-client.md b/skills/aztec-typescript/contract-client.md index 8ce0ab1..22396e1 100644 --- a/skills/aztec-typescript/contract-client.md +++ b/skills/aztec-typescript/contract-client.md @@ -55,6 +55,7 @@ export class MyContractClient { ): Promise { this.logger.info(`Creating item ${itemId}...`); + await this.contract.methods.create_item(itemId).simulate({ from: sender }); const tx = await this.contract.methods.create_item(itemId).send({ from: sender, fee: { paymentMethod: this.paymentMethod }, @@ -78,6 +79,7 @@ export class MyContractClient { ): Promise { this.logger.info(`Transferring ${amount} to ${to}...`); + await this.contract.methods.transfer(to, amount).simulate({ from: sender }); const tx = await this.contract.methods.transfer(to, amount).send({ from: sender, fee: { paymentMethod: this.paymentMethod }, @@ -146,7 +148,9 @@ export class MyContractClient { paymentMethod: SponsoredFeePaymentMethod, timeout: number = 120000 ): Promise { - const contract = await MyContract.deploy(wallet, admin).send({ + const deployRequest = MyContract.deploy(wallet, admin); + await deployRequest.simulate({ from: admin }); + const contract = await deployRequest.send({ from: admin, fee: { paymentMethod }, wait: { timeout, returnReceipt: true } @@ -207,6 +211,7 @@ async function safeCall( // Usage in client async createItem(itemId: bigint, sender: AztecAddress): Promise { return safeCall('create_item', async () => { + await this.contract.methods.create_item(itemId).simulate({ from: sender }); const tx = await this.contract.methods.create_item(itemId).send({ from: sender, fee: { paymentMethod: this.paymentMethod }, diff --git a/skills/aztec-typescript/transaction-patterns.md b/skills/aztec-typescript/transaction-patterns.md index 2bc2714..f1141fd 100644 --- a/skills/aztec-typescript/transaction-patterns.md +++ b/skills/aztec-typescript/transaction-patterns.md @@ -7,7 +7,10 @@ Common patterns for Aztec transaction handling in TypeScript. ```typescript import { TxStatus } from "@aztec/stdlib/tx"; -// 1. Call method and wait for confirmation (v4 inline wait) +// 1. Simulate first to catch errors instantly +await contract.methods.myMethod(args).simulate({ from: senderAddress }); + +// 2. Call method and wait for confirmation (v4 inline wait) const tx = await contract.methods.myMethod(args).send({ from: senderAddress, fee: { paymentMethod }, @@ -37,7 +40,8 @@ interface SendOptions { }; } -// Usage: wait options are now inline with send +// Usage: simulate first, then send with inline wait options +await contract.methods.transfer(to, amount).simulate({ from: account.address }); const receipt = await contract.methods.transfer(to, amount).send({ fee: { paymentMethod }, wait: { timeout: 600 } // Wait options are now inline @@ -50,6 +54,7 @@ Public functions execute on-chain with visible state changes: ```typescript // Create a new item (public state change) +await contract.methods.create_item(itemId).simulate({ from: account.address }); const tx = await contract.methods.create_item(itemId).send({ from: account.address, fee: { paymentMethod }, @@ -57,7 +62,8 @@ const tx = await contract.methods.create_item(itemId).send({ }); // Update public storage -const tx = await contract.methods.set_value(newValue).send({ +await contract.methods.set_value(newValue).simulate({ from: admin.address }); +const tx2 = await contract.methods.set_value(newValue).send({ from: admin.address, fee: { paymentMethod }, wait: { timeout: 60000 } @@ -70,6 +76,7 @@ Private functions execute client-side, creating encrypted notes: ```typescript // Private transfer (creates notes) +await contract.methods.transfer(recipient, amount).simulate({ from: sender.address }); const tx = await contract.methods.transfer(recipient, amount).send({ from: sender.address, fee: { paymentMethod }, @@ -77,7 +84,8 @@ const tx = await contract.methods.transfer(recipient, amount).send({ }); // Store private data -const tx = await contract.methods.store_secret(secretData).send({ +await contract.methods.store_secret(secretData).simulate({ from: account.address }); +const tx2 = await contract.methods.store_secret(secretData).send({ from: account.address, fee: { paymentMethod }, wait: { timeout: 60000 } @@ -129,6 +137,7 @@ async function sendWithRetry( // Usage const tx = await sendWithRetry(async () => { + await contract.methods.myMethod(args).simulate({ from: account.address }); return await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod }, @@ -146,7 +155,10 @@ async function waitForAll( return Promise.all(transactions); } -// Usage +// Usage: simulate all upfront, then send in parallel +await contract.methods.action1(args1).simulate({ from }); +await contract.methods.action2(args2).simulate({ from }); + const txPromises = [ contract.methods.action1(args1).send({ from, fee, wait: { timeout } }), contract.methods.action2(args2).send({ from, fee, wait: { timeout } }), @@ -175,9 +187,9 @@ async function executeSequential( // Usage const receipts = await executeSequential([ - () => contract.methods.step1().send({ from, fee, wait: { timeout } }), - () => contract.methods.step2().send({ from, fee, wait: { timeout } }), - () => contract.methods.step3().send({ from, fee, wait: { timeout } }), + async () => { await contract.methods.step1().simulate({ from }); return contract.methods.step1().send({ from, fee, wait: { timeout } }); }, + async () => { await contract.methods.step2().simulate({ from }); return contract.methods.step2().send({ from, fee, wait: { timeout } }); }, + async () => { await contract.methods.step3().simulate({ from }); return contract.methods.step3().send({ from, fee, wait: { timeout } }); }, ]); ``` @@ -187,6 +199,7 @@ const receipts = await executeSequential([ ```typescript try { + await contract.methods.transfer(to, amount).simulate({ from: account.address }); await contract.methods.transfer(to, amount).send({ from: account.address, fee: { paymentMethod }, @@ -210,6 +223,7 @@ try { ```typescript import { TxStatus } from "@aztec/stdlib/tx"; +await contract.methods.myMethod(args).simulate({ from: account.address }); const tx = await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod }, @@ -253,7 +267,9 @@ async function deployContract( paymentMethod: SponsoredFeePaymentMethod, timeout: number ): Promise { - const contract = await MyContract.deploy(wallet, admin).send({ + const deployRequest = MyContract.deploy(wallet, admin); + await deployRequest.simulate({ from: admin }); + const contract = await deployRequest.send({ from: admin, fee: { paymentMethod }, wait: { timeout, returnReceipt: true } @@ -277,6 +293,7 @@ async function sendTx( paymentMethod: SponsoredFeePaymentMethod, timeout: number ) { + await contract.methods[methodName](...args).simulate({ from }); const tx = await contract.methods[methodName](...args).send({ from, fee: { paymentMethod }, diff --git a/skills/aztec-typescript/wallet-setup.md b/skills/aztec-typescript/wallet-setup.md index f7f6f2e..97e38d2 100644 --- a/skills/aztec-typescript/wallet-setup.md +++ b/skills/aztec-typescript/wallet-setup.md @@ -164,13 +164,16 @@ async function main() { const account = await deploySchnorrAccount(wallet); // Deploy or connect to contract - const contract = await MyContract.deploy(wallet, account.address).send({ + const deployRequest = MyContract.deploy(wallet, account.address); + await deployRequest.simulate({ from: account.address }); + const contract = await deployRequest.send({ from: account.address, fee: { paymentMethod }, wait: { timeout: timeouts.deployTimeout, returnReceipt: true } }); // Interact + await contract.methods.myMethod(args).simulate({ from: account.address }); await contract.methods.myMethod(args).send({ from: account.address, fee: { paymentMethod },