Skip to content
Merged
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 boxes/boxes/react/src/hooks/useContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export function useContract() {
contractAddressSalt: salt,
});

const contract = await toast.promise(deploymentPromise, {
const { contract } = await toast.promise(deploymentPromise, {
pending: 'Deploying contract...',
success: {
render: ({ data }) => `Address: ${data.address}`,
render: ({ data }) => `Address: ${data.contract.address}`,
},
error: 'Error deploying contract',
});
Expand Down
4 changes: 2 additions & 2 deletions boxes/boxes/react/src/hooks/useNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export function useNumber({ contract }: { contract: Contract }) {

setWait(true);
const defaultAccountAddress = deployerEnv.getDefaultAccountAddress();
const viewTxReceipt = await contract!.methods
const { result } = await contract!.methods
.getNumber(defaultAccountAddress)
.simulate({ from: defaultAccountAddress });
toast(`Number is: ${viewTxReceipt.value}`);
toast(`Number is: ${result}`);
setWait(false);
};

Expand Down
2 changes: 1 addition & 1 deletion boxes/boxes/vanilla/app/embedded-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase {
wait: { timeout: 120 },
};

const receipt = await deployMethod.send(deployOpts);
const { receipt } = await deployMethod.send(deployOpts);

logger.info('Account deployed', receipt);

Expand Down
2 changes: 1 addition & 1 deletion boxes/boxes/vanilla/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ async function updateVoteTally(wallet: Wallet, from: AztecAddress) {

const batchResult = await new BatchCall(wallet, payloads).simulate({ from });

batchResult.forEach((value, i) => {
batchResult.forEach(({ result: value }, i) => {
results[i + 1] = value;
});

Expand Down
2 changes: 1 addition & 1 deletion boxes/boxes/vanilla/scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function deployContract(wallet: Wallet, deployer: AztecAddress) {

const sponsoredPFCContract = await getSponsoredPFCContract();

const contract = await PrivateVotingContract.deploy(wallet, deployer).send({
const { contract } = await PrivateVotingContract.deploy(wallet, deployer).send({
from: deployer,
contractAddressSalt: salt,
fee: {
Expand Down
4 changes: 2 additions & 2 deletions boxes/boxes/vite/src/hooks/useContract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export function useContract() {
contractAddressSalt: salt,
});

const contract = await toast.promise(deploymentPromise, {
const { contract } = await toast.promise(deploymentPromise, {
pending: 'Deploying contract...',
success: {
render: ({ data }) => `Address: ${data.address}`,
render: ({ data }) => `Address: ${data.contract.address}`,
},
error: 'Error deploying contract',
});
Expand Down
4 changes: 2 additions & 2 deletions boxes/boxes/vite/src/hooks/useNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export function useNumber({ contract }: { contract: Contract }) {

setWait(true);
const defaultAccountAddress = deployerEnv.getDefaultAccountAddress();
const viewTxReceipt = await contract!.methods
const { result } = await contract!.methods
.getNumber(defaultAccountAddress)
.simulate({ from: defaultAccountAddress });
toast(`Number is: ${viewTxReceipt.value}`);
toast(`Number is: ${result}`);
setWait(false);
};

Expand Down
218 changes: 218 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,224 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.js] `simulate()`, `send()`, and deploy return types changed to always return objects

All SDK interaction methods now return structured objects that include offchain output alongside the primary result. This affects `.simulate()`, `.send()`, deploy `.send()`, and `Wallet.sendTx()`.

**Impact**: Every call site that uses `.simulate()`, `.send()`, or deploy must destructure the result. This is a mechanical transformation. Custom wallet implementations must update `sendTx()` to return the new object shapes, using `extractOffchainOutput` to decode offchain messages from raw effects.

The offchain output includes two fields:

- `offchainEffects` — raw offchain effects emitted during execution, other than `offchainMessages`
- `offchainMessages` — decoded messages intended for specific recipients

We are making this change now so in the future we can add more fields to the responses of this APIs without breaking backwards compatibility,
so this won't ever happen again.

**`simulate()` — always returns `{ result, offchainEffects, offchainMessages }` object:**

```diff
- const value = await contract.methods.foo(args).simulate({ from: sender });
+ const { result: value } = await contract.methods.foo(args).simulate({ from: sender });
```

When using `includeMetadata` or `fee.estimateGas`, `stats` and `estimatedGas` are also available as optional fields on the same object:

```diff
- const { stats, estimatedGas } = await contract.methods.foo(args).simulate({
+ const sim = await contract.methods.foo(args).simulate({
from: sender,
includeMetadata: true,
});
+ const stats = sim.stats!;
+ const estimatedGas = sim.estimatedGas!;
```

`SimulationReturn` is no longer a generic conditional type — it's a single flat type with optional `stats` and `estimatedGas` fields.

**`send()` — returns `{ receipt, offchainEffects, offchainMessages }` object:**

```diff
- const receipt = await contract.methods.foo(args).send({ from: sender });
+ const { receipt } = await contract.methods.foo(args).send({ from: sender });
```

When using `NO_WAIT`, returns `{ txHash, offchainEffects, offchainMessages }` instead of a bare `TxHash`:

```diff
- const txHash = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT });
+ const { txHash } = await contract.methods.foo(args).send({ from: sender, wait: NO_WAIT });
```

Offchain messages emitted by the transaction are available on the result:

```typescript
const { receipt, offchainMessages } = await contract.methods.foo(args).send({ from: sender });
for (const msg of offchainMessages) {
console.log(`Message for ${msg.recipient} from contract ${msg.contractAddress}:`, msg.payload);
}
```

**Deploy — returns `{ contract, receipt, offchainEffects, offchainMessages }` object:**

```diff
- const myContract = await MyContract.deploy(wallet, ...args).send({ from: sender });
+ const { contract: myContract } = await MyContract.deploy(wallet, ...args).send({ from: sender });
```

The deploy receipt is also available via `receipt` if needed (e.g. for `receipt.txHash` or `receipt.transactionFee`).

**Custom wallet implementations — `sendTx()` must return objects:**

If you implement the `Wallet` interface (or extend `BaseWallet`), the `sendTx()` method must now return objects that include offchain output. Use `extractOffchainOutput` to split raw effects into decoded messages and remaining effects:

```diff
+ import { extractOffchainOutput } from '@aztec/aztec.js/contracts';

async sendTx(executionPayload, opts) {
const provenTx = await this.pxe.proveTx(...);
+ const offchainOutput = extractOffchainOutput(provenTx.getOffchainEffects());
const tx = await provenTx.toTx();
const txHash = tx.getTxHash();
await this.aztecNode.sendTx(tx);

if (opts.wait === NO_WAIT) {
- return txHash;
+ return { txHash, ...offchainOutput };
}
const receipt = await waitForTx(this.aztecNode, txHash, opts.wait);
- return receipt;
+ return { receipt, ...offchainOutput };
}
```

### `aztec new` crate directories are now named after the contract

`aztec new` and `aztec init` now name the generated crate directories after the contract instead of using generic `contract/` and `test/` names. For example, `aztec new counter` now creates:

```
counter/
├── Nargo.toml # [workspace] members = ["counter_contract", "counter_test"]
├── counter_contract/
│ ├── src/main.nr
│ └── Nargo.toml # type = "contract"
└── counter_test/
├── src/lib.nr
└── Nargo.toml # type = "lib"
```

This enables adding multiple contracts to a single workspace. Running `aztec new <name>` inside an existing workspace (a directory with a `Nargo.toml` containing `[workspace]`) now adds a new `<name>_contract` and `<name>_test` crate pair to the workspace instead of creating a new directory.

**What changed:**
- Crate directories are now `<name>_contract/` and `<name>_test/` instead of `contract/` and `test/`.
- Contract code is now at `<name>_contract/src/main.nr` instead of `contract/src/main.nr`.
- Contract dependencies go in `<name>_contract/Nargo.toml` instead of `contract/Nargo.toml`.
- Tests import the contract by its new crate name (e.g., `use counter_contract::Main;` instead of `use counter::Main;`).

### [CLI] `--name` flag removed from `aztec new` and `aztec init`

The `--name` flag has been removed from both `aztec new` and `aztec init`. For `aztec new`, the positional argument now serves as both the contract name and the directory name. For `aztec init`, the directory name is always used as the contract name.

**Migration:**

```diff
- aztec new my_project --name counter
+ aztec new counter
```

```diff
- aztec init --name counter
+ aztec init
```

**Impact**: If you were using `--name` to set a contract name different from the directory name, rename your directory or use `aztec new` with the desired contract name directly.

### [Aztec.js] Removed `SingleKeyAccountContract`

The `SchnorrSingleKeyAccount` contract and its TypeScript wrapper `SingleKeyAccountContract` have been removed. This contract was insecure: it used `ivpk_m` (incoming viewing public key) as its Schnorr signing key, meaning anyone who received a user's viewing key could sign transactions on their behalf.

**Migration:**

```diff
- import { SingleKeyAccountContract } from '@aztec/accounts/single_key';
- const contract = new SingleKeyAccountContract(signingKey);
+ import { SchnorrAccountContract } from '@aztec/accounts/schnorr';
+ const contract = new SchnorrAccountContract(signingKey);
```

**Impact**: If you were using `@aztec/accounts/single_key`, switch to `@aztec/accounts/schnorr` which uses separate keys for encryption and authentication.

### `aztec new` and `aztec init` now create a 2-crate workspace

`aztec new` and `aztec init` now create a workspace with two crates instead of a single contract crate:

- A `contract` crate (type = "contract") for your smart contract code
- A `test` crate (type = "lib") for Noir tests, which depends on the contract crate

The new project structure looks like:

```
my_project/
├── Nargo.toml # [workspace] members = ["contract", "test"]
├── contract/
│ ├── src/main.nr
│ └── Nargo.toml # type = "contract"
└── test/
├── src/lib.nr
└── Nargo.toml # type = "lib"
```

**What changed:**
- The `--contract` and `--lib` flags have been removed from `aztec new` and `aztec init`. These commands now always create a contract workspace.
- Contract code is now at `contract/src/main.nr` instead of `src/main.nr`.
- The `Nargo.toml` in the project root is now a workspace file. Contract dependencies go in `contract/Nargo.toml`.
- Tests should be written in the separate `test` crate (`test/src/lib.nr`) and import the contract by package name (e.g., `use my_contract::MyContract;`) instead of using `crate::`.

### Scope enforcement for private state access (TXE and PXE)

Scope enforcement is now active across both TXE (test environment) and PXE (client). Previously, private execution could implicitly access any account's keys and notes. Now, only the caller (`from`) address is in scope by default, and accessing another address's private state requires explicitly granting scope.

#### Noir developers (TXE)

TXE now enforces scope isolation, matching PXE behavior. During private execution, only the caller's keys and notes are accessible. If a Noir test accesses private state of an address other than `from`, it will fail. When `from` is the zero address, scopes are empty (deny-all).

If your TXE tests fail with key or note access errors, ensure the test is calling from the correct address, or restructure the test to match the expected access pattern.

#### Aztec.js developers (PXE/Wallet)

The wallet now passes scopes to PXE, and only the `from` address is in scope by default. Auto-expansion of scopes for nested calls to registered accounts has been removed. A new `additionalScopes` option is available on `send()`, `simulate()`, and `deploy()` for cases where private execution needs access to another address's keys or notes.

**When do you need `additionalScopes`?**

1. **Deploying contracts whose constructor initializes private storage** (e.g., account contracts, or any contract using `SinglePrivateImmutable`/`SinglePrivateMutable` in the constructor). The contract's own address must be in scope so its nullifier key is accessible during initialization.

2. **Operations that access another contract's private state** (e.g., withdrawing from an escrow contract that nullifies the contract's own token notes).

```

**Example: deploying a contract with private storage (e.g., `PrivateToken`)**

```diff
const tokenDeployment = PrivateTokenContract.deployWithPublicKeys(
tokenPublicKeys, wallet, initialBalance, sender,
);
const tokenInstance = await tokenDeployment.getInstance();
await wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey);
const token = await tokenDeployment.send({
from: sender,
+ additionalScopes: [tokenInstance.address],
});
```

**Example: withdrawing from an escrow contract**

```diff
await escrowContract.methods
.withdraw(token.address, amount, recipient)
- .send({ from: owner });
+ .send({ from: owner, additionalScopes: [escrowContract.address] });
```

### `simulateUtility` renamed to `executeUtility`

The `simulateUtility` method and related types have been renamed to `executeUtility` across the entire stack to better reflect that utility functions are executed, not simulated.
Expand Down
Loading
Loading