Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,29 @@ The secret is used to derive the account's encryption keys, and the salt ensures
Save the `secret` and `salt` values securely. You need both to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds.
:::

## Create an initializerless account

Alternatively, create an [initializerless account](../foundational-topics/accounts/deployment.md), which needs no deployment transaction at all:

```typescript
const secret = Fr.random();
const salt = Fr.random();
const account = await wallet.createSchnorrInitializerlessAccount(secret, salt);
console.log("Account address:", account.address.toString());
```

An initializerless account commits its signing public key into the address itself (through the instance's `immutables_hash`), so there is no onchain state to initialize. Creating the account registers it locally in the PXE, and it is ready to use immediately: skip the deployment section below entirely. Fees are only needed for the account's first real transaction, paid with any of the usual [payment methods](./how_to_pay_fees.md).

Two things to keep in mind:

- The signing key cannot be changed later. A new key means a new address.
- Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

See [account deployment](../foundational-topics/accounts/deployment.md) for how this works and how to choose between the two account types.

## Deploy the account

New accounts must be deployed before they can send transactions. Deployment requires paying fees.
Accounts created with `createSchnorrAccount` must be deployed before they can send transactions (initializerless accounts skip this step). Deployment requires paying fees.

### Using the Sponsored FPC

Expand Down Expand Up @@ -144,5 +164,5 @@ console.log("Account deployed:", metadata.initializationStatus);

- [Deploy contracts](./how_to_deploy_contract.md) with your new account
- [Send transactions](./how_to_send_transaction.md) from an account
- Learn about [account abstraction](../foundational-topics/accounts/index.md)
- Learn about [account abstraction](../foundational-topics/accounts/index.md) and [account deployment](../foundational-topics/accounts/deployment.md)
- Implement [authentication witnesses](./how_to_use_authwit.md)
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ Initialization cost is completely eliminated (no constructor transaction). The p
## Getting started

For installation instructions, usage examples, and a reference implementation of an initializerless Schnorr account contract, see the [aztec-immutables-macro README](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev).

The protocol ships a built-in account contract using the same pattern: see [account deployment](../../foundational-topics/accounts/deployment.md) for how initializerless accounts work and [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account) for using them from Aztec.js.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Account Deployment
tags: [accounts]
description: How Aztec accounts come into existence, from the standard initializer plus deployment flow to initializerless accounts that need no onchain transaction at all.
references:
[
"noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr",
"noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr",
]
---

## How accounts come into existence

Every account in Aztec is a [contract instance](../contract_creation.md), and its address is computed deterministically from its instantiation parameters. There are two ways to make an account usable:

1. **The standard flow**: create the account locally, then send a deployment transaction that runs its initializer.
2. **The initializerless flow**: create the account locally, and that is it. No deployment transaction is ever sent.

This page explains both flows and when to choose each.

## The standard flow: initialize and deploy

A regular account contract (for example the default Schnorr account) stores its signing public key in private state. Writing that state requires running the contract's constructor, a [private initializer function](../../aztec-nr/framework-description/functions/attributes.md#initializer-functions-initializer), which in turn requires sending a transaction to the network and paying [fees](../fees.md) for it.

The address commits to that pending initialization: the constructor call's selector and arguments are hashed into the instance's `initialization_hash`, which is folded into the address derivation. This is why the address can be computed, shared, and even receive funds before the deployment transaction is sent, and why the account only becomes able to send transactions after it.

## Initializerless accounts

An initializerless account removes the deployment transaction entirely. Instead of storing the signing key in private state through an initializer, the key is committed directly into the address: the instance's `immutables_hash` field is set to the hash of the signing public key, and `immutables_hash` participates in address derivation just like `initialization_hash` does.

Since the address itself is the commitment to the signing key, there is no onchain state to create:

- The account contract has no initializer. Its `constructor` is an unconstrained utility function that runs only in the PXE: it checks that the provided public key hashes to the instance's `immutables_hash` and stores the key in the PXE's local store.
- When the account later authorizes a transaction, its entrypoint loads the key from the local store and proves in the private kernel that the key's hash matches the `immutables_hash` committed in the address. Tampering with the local key is not possible without changing the address.

Creating an initializerless account is therefore a purely local operation: the wallet computes the address, registers the contract instance in the PXE, and runs the utility constructor through a simulation. No transaction is sent, no fee is paid, and the account can start transacting as soon as it has a way to pay for its first real transaction (for example a [Sponsored FPC](../fees.md#payment-methods) or a Fee Juice balance at its address).

The genesis-funded test accounts in the local network are initializerless Schnorr accounts: their addresses receive Fee Juice at genesis, and wallets materialize them locally without any deployment.

## Trade-offs

Initializerless accounts are a good default when the account's authorization logic only depends on parameters that never change:

- **No deployment cost or delay.** The account is usable the moment it is created, which makes onboarding flows and receive-only addresses cheap.
- **The signing key is fixed forever.** The key is baked into the address, so there is no way to rotate it. A new key means a new address. The default Schnorr account offers no key rotation either, but account contracts that keep keys in state can be designed to support it.
- **Local setup is still required per PXE.** A fresh PXE does not know about the account until the wallet registers the instance and runs the utility constructor again. This is a purely offline step with no fee, but it must happen in every new environment before the account can be used.
- **There is no deployment method.** Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

Use the standard flow when the account contract needs an initializer: for example when it takes arbitrary constructor arguments or stores its authorization material in private notes.

## Using initializerless accounts

- In Aztec.js, call `createSchnorrInitializerlessAccount(secret, salt)` on your wallet. See [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account).
- In the CLI, pass the account type: `aztec-wallet create-account -t schnorr_initializerless`. The command registers the account locally and returns immediately, with no deployment transaction.
- In Aztec.nr, the same address-immutables pattern can be applied to your own contracts. See [immutables](../../aztec-nr/framework-description/immutables.md).

## Next steps

- [Create an account in Aztec.js](../../aztec-js/how_to_create_account.md)
- [Understand fees and payment methods](../fees.md)
- [Contract creation and address derivation](../contract_creation.md)
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ A contract instance includes:
- `deployer`: Optional address of the contract deployer. Zero for universal deployment
- `original_contract_class_id`: Identifier of the contract class the instance was deployed with. Updating the instance to a new class via the ContractInstanceRegistry does not change this value, since it is part of the address preimage
- `initialization_hash`: Hash of the selector and arguments to the constructor
- `immutables_hash`: Hash of the contract's compile-time immutable state
- `immutables_hash`: Hash of the contract's compile-time immutable state. [Initializerless accounts](./accounts/deployment.md) use it to commit the signing key into the address
- `public_keys`: Public keys participating in address derivation (nullifier, incoming viewing, outgoing viewing, tagging, message-signing, and fallback keys). Only the incoming viewing key is held as an elliptic curve point; the other five are held as their `hash_public_key` digests.

### Instance Address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Fee Juice uses an enshrined `FeeJuicePortal` contract on Ethereum for bridging,

### Payment methods

An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment.
An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment. [Initializerless accounts](./accounts/deployment.md) skip the deployment transaction entirely, so they only need fees once they send their first real transaction.

Alternatively, accounts can use [fee-paying contracts (FPCs)](../aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. An FPC holds its own Fee Juice balance to pay the protocol, and can accept other tokens from users in exchange.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ A wallet must support at least one specific account contract implementation, whi

Note that users must be able to receive funds in Aztec before deploying their account. A wallet should let a user generate a [deterministic complete address](./accounts/keys.md#address-derivation) without having to interact with the network, so they can share it with others to receive funds. This requires that the wallet pins a specific contract implementation, its initialization arguments, a deployment salt, and the user's keys. These values yield a deterministic address, so when the account contract is actually deployed, it is available at the precalculated address. Once the account contract is deployed, the user can start sending transactions using it as the transaction origin.

Some account contracts avoid deployment altogether: [initializerless accounts](./accounts/deployment.md) commit their signing key into the address itself, so the wallet only needs to register them locally before they can start transacting.

## Transaction lifecycle

Every transaction in Aztec is broadcast to the network as a zero-knowledge proof of correct execution, in order to preserve privacy. This means that transaction proofs are generated on the wallet and not on a remote node. This is one of the biggest differences with regard to EVM chain wallets.
Expand Down
24 changes: 22 additions & 2 deletions docs/docs-developers/docs/aztec-js/how_to_create_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,29 @@ The secret is used to derive the account's encryption keys, and the salt ensures
Save the `secret` and `salt` values securely. You need both to recover access to your account. If you lose them, you will permanently lose access to the account and any assets it holds.
:::

## Create an initializerless account

Alternatively, create an [initializerless account](../foundational-topics/accounts/deployment.md), which needs no deployment transaction at all:

```typescript
const secret = Fr.random();
const salt = Fr.random();
const account = await wallet.createSchnorrInitializerlessAccount(secret, salt);
console.log("Account address:", account.address.toString());
```

An initializerless account commits its signing public key into the address itself (through the instance's `immutables_hash`), so there is no onchain state to initialize. Creating the account registers it locally in the PXE, and it is ready to use immediately: skip the deployment section below entirely. Fees are only needed for the account's first real transaction, paid with any of the usual [payment methods](./how_to_pay_fees.md).

Two things to keep in mind:

- The signing key cannot be changed later. A new key means a new address.
- Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

See [account deployment](../foundational-topics/accounts/deployment.md) for how this works and how to choose between the two account types.

## Deploy the account

New accounts must be deployed before they can send transactions. Deployment requires paying fees.
Accounts created with `createSchnorrAccount` must be deployed before they can send transactions (initializerless accounts skip this step). Deployment requires paying fees.

### Using the Sponsored FPC

Expand Down Expand Up @@ -70,5 +90,5 @@ Confirm the account was deployed successfully. Substitute the account variable f

- [Deploy contracts](./how_to_deploy_contract.md) with your new account
- [Send transactions](./how_to_send_transaction.md) from an account
- Learn about [account abstraction](../foundational-topics/accounts/index.md)
- Learn about [account abstraction](../foundational-topics/accounts/index.md) and [account deployment](../foundational-topics/accounts/deployment.md)
- Implement [authentication witnesses](./how_to_use_authwit.md)
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ Initialization cost is completely eliminated (no constructor transaction). The p
## Getting started

For installation instructions, usage examples, and a reference implementation of an initializerless Schnorr account contract, see the [aztec-immutables-macro README](https://github.com/defi-wonderland/aztec-immutables-macro/tree/dev).

The protocol ships a built-in account contract using the same pattern: see [account deployment](../../foundational-topics/accounts/deployment.md) for how initializerless accounts work and [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account) for using them from Aztec.js.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
title: Account Deployment
tags: [accounts]
description: How Aztec accounts come into existence, from the standard initializer plus deployment flow to initializerless accounts that need no onchain transaction at all.
references:
[
"noir-projects/noir-contracts/contracts/account/schnorr_initializerless_account_contract/src/main.nr",
"noir-projects/noir-contracts/contracts/account/schnorr_account_contract/src/main.nr",
]
---

## How accounts come into existence

Every account in Aztec is a [contract instance](../contract_creation.md), and its address is computed deterministically from its instantiation parameters. There are two ways to make an account usable:

1. **The standard flow**: create the account locally, then send a deployment transaction that runs its initializer.
2. **The initializerless flow**: create the account locally, and that is it. No deployment transaction is ever sent.

This page explains both flows and when to choose each.

## The standard flow: initialize and deploy

A regular account contract (for example the default Schnorr account) stores its signing public key in private state. Writing that state requires running the contract's constructor, a [private initializer function](../../aztec-nr/framework-description/functions/attributes.md#initializer-functions-initializer), which in turn requires sending a transaction to the network and paying [fees](../fees.md) for it.

The address commits to that pending initialization: the constructor call's selector and arguments are hashed into the instance's `initialization_hash`, which is folded into the address derivation. This is why the address can be computed, shared, and even receive funds before the deployment transaction is sent, and why the account only becomes able to send transactions after it.

## Initializerless accounts

An initializerless account removes the deployment transaction entirely. Instead of storing the signing key in private state through an initializer, the key is committed directly into the address: the instance's `immutables_hash` field is set to the hash of the signing public key, and `immutables_hash` participates in address derivation just like `initialization_hash` does.

Since the address itself is the commitment to the signing key, there is no onchain state to create:

- The account contract has no initializer. Its `constructor` is an unconstrained utility function that runs only in the PXE: it checks that the provided public key hashes to the instance's `immutables_hash` and stores the key in the PXE's local store.
- When the account later authorizes a transaction, its entrypoint loads the key from the local store and proves in the private kernel that the key's hash matches the `immutables_hash` committed in the address. Tampering with the local key is not possible without changing the address.

Creating an initializerless account is therefore a purely local operation: the wallet computes the address, registers the contract instance in the PXE, and runs the utility constructor through a simulation. No transaction is sent, no fee is paid, and the account can start transacting as soon as it has a way to pay for its first real transaction (for example a [Sponsored FPC](../fees.md#payment-methods) or a Fee Juice balance at its address).

The genesis-funded test accounts in the local network are initializerless Schnorr accounts: their addresses receive Fee Juice at genesis, and wallets materialize them locally without any deployment.

## Trade-offs

Initializerless accounts are a good default when the account's authorization logic only depends on parameters that never change:

- **No deployment cost or delay.** The account is usable the moment it is created, which makes onboarding flows and receive-only addresses cheap.
- **The signing key is fixed forever.** The key is baked into the address, so there is no way to rotate it. A new key means a new address. The default Schnorr account offers no key rotation either, but account contracts that keep keys in state can be designed to support it.
- **Local setup is still required per PXE.** A fresh PXE does not know about the account until the wallet registers the instance and runs the utility constructor again. This is a purely offline step with no fee, but it must happen in every new environment before the account can be used.
- **There is no deployment method.** Calling `getDeployMethod()` on an initializerless account throws, since there is nothing to deploy.

Use the standard flow when the account contract needs an initializer: for example when it takes arbitrary constructor arguments or stores its authorization material in private notes.

## Using initializerless accounts

- In Aztec.js, call `createSchnorrInitializerlessAccount(secret, salt)` on your wallet. See [creating accounts](../../aztec-js/how_to_create_account.md#create-an-initializerless-account).
- In the CLI, pass the account type: `aztec-wallet create-account -t schnorr_initializerless`. The command registers the account locally and returns immediately, with no deployment transaction.
- In Aztec.nr, the same address-immutables pattern can be applied to your own contracts. See [immutables](../../aztec-nr/framework-description/immutables.md).

## Next steps

- [Create an account in Aztec.js](../../aztec-js/how_to_create_account.md)
- [Understand fees and payment methods](../fees.md)
- [Contract creation and address derivation](../contract_creation.md)
Loading
Loading