Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion boxes/boxes/vanilla/app/embedded-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
EmbeddedWallet as EmbeddedWalletBase,
type EmbeddedWalletOptions,
} from '@aztec/wallets/embedded';
import { NO_FROM } from '@aztec/aztec.js/account';

const logger = createLogger('wallet');
const LocalStorageKey = 'aztec-account';
Expand Down Expand Up @@ -153,7 +154,7 @@ export class EmbeddedWallet extends EmbeddedWalletBase {
const sponsoredFPCAddress = await this.#getSponsoredFPCAddress();

const deployOpts: DeployAccountOptions<InteractionWaitOptions> = {
from: AztecAddress.ZERO,
from: NO_FROM,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NO_FROM sounds so strange, isn't there some other way to name this hinting at its effects? maybe something like PAYLOAD_DEFINED_FROM (which is probably wronger and horribler, but I'm trying to convey my objection)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

seeing the changes to the docs, maybe CREATED_BY_TX

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not in love with the naming too, I went for parallelism with NO_WAIT...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is weird, yes, but I'm also having trouble coming up with something better. Perhaps from: DIRECT_CALL? idk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree that NO_FROM sounds strange. Now that Claude is back up, it suggested a few options:

  • DIRECT or DIRECT_EXECUTION -- signals that the tx bypasses the account contract entrypoint
  • NO_ACCOUNT -- clearer that you're opting out of account contract wrapping
  • RAW -- short, implies no wrapping/mediation
  • SELF -- the transaction acts on its own behalf without an intermediary account contract
  • UNMEDIATED -- explicitly says "no account contract mediation"
  • ENTRYPOINTLESS -- describes the mechanism being skipped (the account entrypoint)
  • NO_SENDER -- closer to the domain: there's no "sender" account wrapping the call

And it even provided a ranking 😅

My ranking would be: DIRECT > NO_ACCOUNT > NO_SENDER > SELF > UNMEDIATED > ENTRYPOINTLESS > RAW > NO_FROM.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sorry but all of those are worse IMO xD

from is pretty generic, it has no concept of account (wallet just happens to make it so from is an account, but we had the exception of the multicall entrypoint...which is not one). The NO_WAIT parallel is also a pro in my book

fee: {
paymentMethod: new SponsoredFeePaymentMethod(sponsoredFPCAddress),
},
Expand Down
8 changes: 6 additions & 2 deletions boxes/boxes/vanilla/scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import fs from 'fs';
import path from 'path';
// @ts-ignore
import { PrivateVotingContract } from '../artifacts/PrivateVoting.ts';
import { NO_FROM } from '@aztec/aztec.js/account';

const AZTEC_NODE_URL = process.env.AZTEC_NODE_URL || 'http://localhost:8080';
const WRITE_ENV_FILE = process.env.WRITE_ENV_FILE === 'false' ? false : true;
Expand Down Expand Up @@ -51,7 +52,7 @@ async function createAccount(wallet: EmbeddedWallet) {
const deployMethod = await accountManager.getDeployMethod();
const sponsoredPFCContract = await getSponsoredPFCContract();
const deployOpts: DeployAccountOptions<InteractionWaitOptions> = {
from: AztecAddress.ZERO,
from: NO_FROM,
fee: {
paymentMethod: new SponsoredFeePaymentMethod(
sponsoredPFCContract.address
Expand All @@ -71,7 +72,10 @@ 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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ If your account already has Fee Juice (for example, [bridged from L1](./how_to_p

#include_code deploy_account_fee_juice /docs/examples/ts/aztecjs_connection/index.ts typescript

The `from: AztecAddress.ZERO` is required because there's no existing account to send from—the transaction itself creates the account.
The `from: NO_FROM` signals that this transaction should be executed without account contract mediation—the transaction itself creates the account.
Comment thread
Thunkar marked this conversation as resolved.
Outdated

## Verify deployment

Expand Down
41 changes: 41 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,47 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.js] Use `NO_FROM` instead of `AztecAddress.ZERO` to bypass account contract entrypoint

When sending transactions that should not be mediated by an account contract (e.g., account contract self-deployments), use the explicit `NO_FROM` sentinel instead of `AztecAddress.ZERO`.

`NO_FROM` signals that the transaction should be executed directly via the `DefaultEntrypoint`. This replaces the brittle convention of passing `AztecAddress.ZERO` as the `from` field.

**Migration:**

```diff
- import { AztecAddress } from '@aztec/aztec.js';
+ import { NO_FROM } from '@aztec/aztec.js/account';

await contract.methods.my_method().send({
- from: AztecAddress.ZERO,
+ from: NO_FROM,
});
```

Note that `DefaultEntrypoint` only accepts a single call. If you need to execute multiple calls without account contract mediation (e.g., deploying an account contract and paying a fee in the same transaction), wrap them through `DefaultMultiCallEntrypoint` on the app side before sending:

```typescript
import { NO_FROM } from "@aztec/aztec.js/account";
import { DefaultMultiCallEntrypoint } from "@aztec/entrypoints/multicall";
import { mergeExecutionPayloads } from "@aztec/stdlib/tx";

// Merge multiple execution payloads into one
const merged = mergeExecutionPayloads([deployPayload, feePayload]);

// Wrap through multicall so it becomes a single call for DefaultEntrypoint
const multicall = new DefaultMultiCallEntrypoint();
const chainInfo = await wallet.getChainInfo();
const wrappedPayload = await multicall.wrapExecutionPayload(merged, chainInfo);

// Send without account contract mediation
await wallet.sendTx(wrappedPayload, { from: NO_FROM });
```

Using other contracts for wrapping (for example, supporting more calls) is also supported, as long as the contract is registered in the wallet. This opens the door to different flows that do not use account entrypoints as the first call in the chain, including app sponsored FPCs.

**Impact**: Any code that passes `AztecAddress.ZERO` as the `from` option in `.send()`, `.simulate()`, or deploy options must switch to `NO_FROM`. Wallets use `DefaultEntrypoint` directly for `NO_FROM` transactions, instead of the `DefaultMultiCallEntrypoint` that was used internally before when specifying `AztecAddress.ZERO`.

### [Aztec.nr] `attempt_note_discovery` now takes two separate functions instead of one

The `attempt_note_discovery` function (and related discovery functions like `do_sync_state`, `process_message_ciphertext`) now takes separate `compute_note_hash` and `compute_note_nullifier` arguments instead of a single combined `compute_note_hash_and_nullifier`. The corresponding type aliases are now `ComputeNoteHash` and `ComputeNoteNullifier` (instead of `ComputeNoteHashAndNullifier`).
Expand Down
6 changes: 3 additions & 3 deletions docs/examples/ts/aztecjs_connection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ console.log("New account address:", newAccount.address.toString());

// docs:start:deploy_account_sponsored_fpc
// Additional imports needed for account deployment examples
import { AztecAddress } from "@aztec/aztec.js/addresses";
import { NO_FROM } from "@aztec/aztec.js/account";
import { SponsoredFeePaymentMethod } from "@aztec/aztec.js/fee/testing";
import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC";
import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract";
Expand All @@ -76,7 +76,7 @@ const sponsoredPaymentMethod = new SponsoredFeePaymentMethod(
// newAccount is the account created in the previous section
const deployMethod = await newAccount.getDeployMethod();
await deployMethod.send({
from: AztecAddress.ZERO,
from: NO_FROM,
fee: { paymentMethod: sponsoredPaymentMethod },
});
// docs:end:deploy_account_sponsored_fpc
Expand Down Expand Up @@ -144,7 +144,7 @@ console.log(`Alice's token balance: ${balance}`);
// Deploy the account using the bridged Fee Juice
const deployMethodFeeJuice = await feeJuiceAccount.getDeployMethod();
await deployMethodFeeJuice.send({
from: AztecAddress.ZERO,
from: NO_FROM,
fee: {
paymentMethod: new FeeJuicePaymentMethodWithClaim(
feeJuiceAccount.address,
Expand Down
24 changes: 15 additions & 9 deletions docs/examples/ts/recursive_verification/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import { getSponsoredFPCInstance } from "./scripts/sponsored_fpc.js";
import { SponsoredFPCContract } from "@aztec/noir-contracts.js/SponsoredFPC";
import { ValueNotEqualContract } from "./artifacts/ValueNotEqual.js";
import { EmbeddedWallet } from "@aztec/wallets/embedded";
import { AztecAddress } from "@aztec/aztec.js/addresses";
import { NO_FROM } from "@aztec/aztec.js/account";
import { Fr } from "@aztec/aztec.js/fields";
import assert from "node:assert";
import fs from "node:fs";
// docs:end:imports

// docs:start:sample_data
if (!fs.existsSync("data.json")) {
console.error("data.json not found. Run 'yarn data' first to generate proof data.");
console.error(
"data.json not found. Run 'yarn data' first to generate proof data.",
);
Comment on lines +17 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is your formatter configured to fewer than 120 chars?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the doc examples are...weird. I cannot get the ts server to behave with them and editing them does whatever. They also take forever to build...we have to talk to devrel about this

process.exit(1);
}
const data = JSON.parse(fs.readFileSync("data.json", "utf-8"));
Expand Down Expand Up @@ -57,7 +59,7 @@ async function main() {
// Deploy the account contract
const deployMethod = await manager.getDeployMethod();
await deployMethod.send({
from: AztecAddress.ZERO,
from: NO_FROM,
fee: { paymentMethod: sponsoredPaymentMethod },
});

Expand All @@ -84,9 +86,11 @@ async function main() {

// Step 3: Read initial counter value
// simulate() executes without submitting a transaction
let counterValue = (await valueNotEqual.methods
.get_counter(accounts[0].item)
.simulate({ from: accounts[0].item })).result;
let counterValue = (
await valueNotEqual.methods
.get_counter(accounts[0].item)
.simulate({ from: accounts[0].item })
).result;
console.log(`Counter value: ${counterValue}`); // Should be 10

// Step 4: Call increment() with proof data
Expand All @@ -107,9 +111,11 @@ async function main() {
await interaction.send(opts);

// Step 6: Read updated counter
counterValue = (await valueNotEqual.methods
.get_counter(accounts[0].item)
.simulate({ from: accounts[0].item })).result;
counterValue = (
await valueNotEqual.methods
.get_counter(accounts[0].item)
.simulate({ from: accounts[0].item })
).result;
console.log(`Counter value: ${counterValue}`); // Should be 11

assert(counterValue === 11n, "Counter should be 11 after verification");
Expand Down
5 changes: 4 additions & 1 deletion noir-projects/aztec-nr/aztec/src/authwit/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ struct CallAuthorization {
struct CallAuthorizationRequest {
selector: AuthorizationSelector,
inner_hash: Field,
on_behalf_of: AztecAddress,
msg_sender: AztecAddress,
fn_selector: FunctionSelector,
args_hash: Field,
Expand All @@ -221,11 +222,13 @@ struct CallAuthorizationRequest {
unconstrained fn emit_authorization_as_offchain_effect<let N: u32>(
authorization: CallAuthorization,
inner_hash: Field,
on_behalf_of: AztecAddress,
) {
let args: [Field; N] = load(authorization.args_hash);
let authorization_request = CallAuthorizationRequest {
selector: authorization.get_authorization_selector(),
inner_hash: inner_hash,
on_behalf_of: on_behalf_of,
msg_sender: authorization.msg_sender,
fn_selector: authorization.selector,
args_hash: authorization.args_hash,
Expand Down Expand Up @@ -253,7 +256,7 @@ pub fn assert_current_call_valid_authwit<let N: u32>(context: &mut PrivateContex
let inner_hash = compute_inner_authwit_hash(authorization.serialize());
// Safety: Offchain effects are by definition unconstrained. They are emitted via an oracle which we don't use for
// anything besides its side effects, therefore this is safe to call.
unsafe { emit_authorization_as_offchain_effect::<N>(authorization, inner_hash) };
unsafe { emit_authorization_as_offchain_effect::<N>(authorization, inner_hash, on_behalf_of) };

assert_inner_hash_valid_authwit(context, on_behalf_of, inner_hash);
}
Expand Down
3 changes: 2 additions & 1 deletion playground/src/components/home/components/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { trackButtonClick } from '../../../utils/matomo';
import type { EmbeddedWallet } from '@aztec/wallets/embedded';
import { prepareForFeePayment } from '../../../utils/sponsoredFPC';
import { colors, commonStyles } from '../../../global.styles';
import { NO_FROM } from '@aztec/aztec.js/account';

const container = css({
display: 'flex',
Expand Down Expand Up @@ -399,7 +400,7 @@ export function Landing() {

const deployMethod = await accountManager.getDeployMethod();
const opts: DeployAccountOptions = {
from: AztecAddress.ZERO,
from: NO_FROM,
fee: {
paymentMethod,
},
Expand Down
3 changes: 2 additions & 1 deletion playground/src/wallet/components/CreateAccountDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { INFO_TEXT } from '../../constants';
import { Box, DialogContent } from '@mui/material';
import { DialogActions } from '@mui/material';
import type { EmbeddedWallet } from '@aztec/wallets/embedded';
import { NO_FROM } from '@aztec/aztec.js/account';

export function CreateAccountDialog({
wallet,
Expand Down Expand Up @@ -77,7 +78,7 @@ export function CreateAccountDialog({
if (publiclyDeploy) {
deployMethod = await accountManager.getDeployMethod();
opts = {
from: AztecAddress.ZERO,
from: NO_FROM,
fee: {
paymentMethod: feePaymentMethod,
},
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export {
export type { AuthWitnessProvider, ChainInfo } from '@aztec/entrypoints/interfaces';
export { ChainInfoSchema } from '@aztec/entrypoints/interfaces';

export { NO_FROM, type NoFrom } from '../contract/interaction_options.js';
export { SignerlessAccount } from '../account/signerless_account.js';
Comment thread
Thunkar marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export class CallAuthorizationRequest {
* poseidon2([msg_sender, selector, args_hash])
*/
public innerHash: Fr,
/**
* The address on whose behalf the auth witness should be created.
* This is the account that must sign the authorization.
*/
public onBehalfOf: AztecAddress,
Comment thread
Thunkar marked this conversation as resolved.
/**
* The address performing the call
*/
Expand Down Expand Up @@ -75,11 +80,12 @@ export class CallAuthorizationRequest {
}
const request = new CallAuthorizationRequest(
selector,
reader.readField(),
AztecAddress.fromField(reader.readField()),
FunctionSelector.fromField(reader.readField()),
reader.readField(),
reader.readFieldArray(reader.remainingFields()),
reader.readField(), // inner_hash
AztecAddress.fromField(reader.readField()), // on_behalf_of
AztecAddress.fromField(reader.readField()), // msg_sender
FunctionSelector.fromField(reader.readField()), // fn_selector
reader.readField(), // args_hash
reader.readFieldArray(reader.remainingFields()), // args
);
await request.validate();
return request;
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/aztec.js/src/contract/batch_call.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { type FunctionCall, FunctionType, decodeFromAbi } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { ExecutionPayload, TxSimulationResult, UtilityExecutionResult, mergeExecutionPayloads } from '@aztec/stdlib/tx';

import type { BatchedMethod, Wallet } from '../wallet/wallet.js';
import { BaseContractInteraction } from './base_contract_interaction.js';
import {
NO_FROM,
type RequestInteractionOptions,
type SimulateInteractionOptions,
extractOffchainOutput,
Expand Down Expand Up @@ -78,7 +80,10 @@ export class BatchCall extends BaseContractInteraction {
for (const [call] of utility) {
batchRequests.push({
name: 'executeUtility' as const,
args: [call, { scope: options.from, authWitnesses: options.authWitnesses }],
args: [
call,
{ scope: options.from === NO_FROM ? AztecAddress.ZERO : options.from, authWitnesses: options.authWitnesses },
],
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { Wallet } from '../wallet/wallet.js';
import { BaseContractInteraction } from './base_contract_interaction.js';
import { getGasLimits } from './get_gas_limits.js';
import {
NO_FROM,
type ProfileInteractionOptions,
type RequestInteractionOptions,
type SimulateInteractionOptions,
Expand Down Expand Up @@ -130,7 +131,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction {
if (this.functionDao.functionType == FunctionType.UTILITY) {
const call = await this.getFunctionCall();
const utilityResult = await this.wallet.executeUtility(call, {
scope: options.from,
scope: options.from === NO_FROM ? AztecAddress.ZERO : options.from,
Comment thread
Thunkar marked this conversation as resolved.
Outdated
authWitnesses: options.authWitnesses,
});

Expand Down
13 changes: 9 additions & 4 deletions yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { ContractBase } from './contract_base.js';
import { ContractFunctionInteraction } from './contract_function_interaction.js';
import { getGasLimits } from './get_gas_limits.js';
import {
NO_FROM,
NO_WAIT,
type NoWait,
type OffchainOutput,
Expand Down Expand Up @@ -222,10 +223,14 @@ export class DeployMethod<TContract extends ContractBase = ContractBase> extends
}

convertDeployOptionsToRequestOptions(options: DeployOptionsWithoutWait): RequestDeployOptions {
return {
...options,
deployer: !options?.universalDeploy ? options.from : undefined,
};
const { from } = options;
let deployer: AztecAddress | undefined;
if (options?.universalDeploy) {
deployer = undefined;
} else {
deployer = from === NO_FROM ? AztecAddress.ZERO : from;
}
return { ...options, deployer };
}

/**
Expand Down
18 changes: 16 additions & 2 deletions yarn-project/aztec.js/src/contract/interaction_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ export const NO_WAIT = 'NO_WAIT' as const;
*/
export type NoWait = typeof NO_WAIT;

/**
* Constant for explicitly opting out of account contract mediation.
* When used as the `from` parameter, the wallet executes the payload directly
* via the DefaultEntrypoint without wrapping it in an account contract entrypoint.
* The app is responsible for assembling the complete execution payload, including
* any entrypoint wrapping (e.g. multicall) if needed.
*/
export const NO_FROM = 'NO_FROM' as const;
Comment thread
Thunkar marked this conversation as resolved.

/**
* Type for the NO_FROM constant.
*/
export type NoFrom = typeof NO_FROM;

/**
* Type for wait options in interactions.
* - NO_WAIT symbol: Don't wait for confirmation, return TxHash immediately
Expand All @@ -86,8 +100,8 @@ export type InteractionWaitOptions = NoWait | WaitOpts | undefined;
* Base options for calling a (constrained) function in a contract, without wait parameter.
*/
export type SendInteractionOptionsWithoutWait = RequestInteractionOptions & {
/** The sender's Aztec address. */
from: AztecAddress;
/** The sender's Aztec address, or NO_FROM to execute without account contract mediation. */
from: AztecAddress | NoFrom;
/** The fee options for the transaction. */
fee?: InteractionFeeOptions;
/**
Expand Down
Loading
Loading