> ## Documentation Index
> Fetch the complete documentation index at: https://starkware-9575960b-eitan-accounts-update-0-14-1.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounts

## Table of contents

* [Overview](#overview)
* [Starknet vs. Ethereum](#starknet-vs-ethereum)
* [Account structure](#account-structure)
  * [Transaction flow](#transaction-flow)
  * [Validation limitations](#validation-limitations)
  * [Validation failures and statuses](#validation-failures-and-statuses)
* [Account nonces](#account-nonces)
* [Deploying a new account](#deploying-a-new-account)
* [Recommended standards](#recommended-standards)
  * [SNIP-6](#snip-6)
  * [SNIP-9: Outside Execution](#snip-9-outside-execution)
  * [SNIP-29: Paymaster](#snip-29-paymaster)
* [Legacy and deprecated](#legacy-and-deprecated)
  * [Old transaction versions](#old-transaction-versions)
  * [Legacy accounts](#legacy-accounts)
  * [Internal calls to `__execute__`](#internal-calls-to-__execute__)
* [Version history](#version-history)

## Overview

An account is your identity on a blockchain. It holds your funds, tracks your activity, and authorizes every action you take — such as transferring tokens or interacting with applications. Without an account, you cannot interact with the network.

On most blockchains (including Ethereum), accounts are simple: they are controlled by a private key, and transactions are valid if they are correctly signed by that key. While this model is straightforward, it is also rigid. The rules for authorization, fee payment, and replay protection are fixed at the protocol level and cannot be customized.

Starknet takes a fundamentally different approach: **account abstraction is built into the protocol**.

Every account on Starknet is a smart contract. Instead of the protocol enforcing a single definition of a "valid transaction," each account defines its own validation logic. This allows accounts to implement custom behaviors such as:

* Multi-signature authorization
* Social recovery mechanisms
* Alternative signature schemes (e.g. hardware-based signing)
* Flexible fee payment via third parties (paymasters)

This design shifts control from the protocol to the account itself, enabling significantly more flexibility without changing core protocol rules.

## Starknet vs. Ethereum

| Feature             | Ethereum (EOA)          | Starknet                  |
| ------------------- | ----------------------- | ------------------------- |
| Account type        | Key pair                | Smart contract            |
| Signature logic     | Fixed (secp256k1 ECDSA) | Programmable              |
| Fee payment         | Native token only       | Flexible (via paymasters) |
| Account logic       | Protocol-defined        | Account-defined           |
| Account abstraction | Via ERC-4337 add-on     | Native and mandatory      |

Ethereum is evolving toward account abstraction via [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), where accounts are managed through a dedicated smart contract. On Starknet, this model is **native and mandatory** — there are no key-pair EOAs.

## Account structure

Starknet's account structure is inspired by Ethereum's EIP-4337, where smart contract accounts with arbitrary verification logic are used instead of EOAs.

A valid account contract in Starknet is a contract that includes the following functions:

| Function name          | When required                                                                                                                                                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `__validate__`         | Always required                                                                                                                                                                                                                                                                                         |
| `__execute__`          | Always required. The signatures of `__validate__` and `__execute__` must be identical. See critical validation requirements below.                                                                                                                                                                      |
| `__validate_declare__` | Required for the account to be able to send a `DECLARE` transaction. This function must receive exactly one argument, which is the class hash of the declared class.                                                                                                                                    |
| `__validate_deploy__`  | Required to allow deploying an instance of the account contract with a `DEPLOY_ACCOUNT` transaction. The arguments of `__validate_deploy__` must be the class hash of the account to be deployed and the salt used for computing the account's contract address, followed by the constructor arguments. |
| `constructor`          | All contracts have a `constructor` function. It can be explicitly defined in the contract, or if not explicitly defined, the sequencer uses a default `constructor` function, which is empty.                                                                                                           |

<Note>
  In practice, use an audited account implementation such as [OpenZeppelin](https://github.com/OpenZeppelin/cairo-contracts), [Argent](https://github.com/argentlabs/argent-contracts-starknet), or [Braavos](https://github.com/myBraavos/braavos-account-cairo). Writing a custom account contract is advanced work with significant security implications.
</Note>

<Danger>
  Two critical validations must happen in `__execute__`, and their absence can lead to draining of the account's funds:

  (1) `assert!(get_caller_address().is_zero())`

  This asserts that the account's `__execute__` is not called from another contract, thus skipping validations. As of Starknet v0.14.0, transactions with internal calls to an entry point named `__execute__` are **reverted** at the protocol level. However, this check remains important for defense-in-depth during validation.

  (2) `assert!(get_tx_info().unbox().version.into() >= 1_u32)`

  This asserts that the transaction's version is at least 1, blocking deprecated INVOKE v0 transactions. v0 transactions assume signature verification happens inside `__execute__`, skipping `__validate__` entirely. This check also blocks invocations via the `meta_tx_v0` syscall (introduced in v0.14.0 to preserve access to legacy accounts without `__validate__`), which presents the transaction version as 0 internally.

  <Note>
    Although Starknet v0.14.0 removed support for transaction versions 0, 1, and 2 at the protocol level (making v3 the only valid version), the canonical minimum version check in the [OpenZeppelin reference implementation](https://github.com/OpenZeppelin/cairo-contracts/blob/v3.0.0/packages/account/src/utils.cairo) remains `>= 1`. This is because simulation and query transactions use a large version offset (`0x100000000000000000000000000000000 + version`), so the check must accommodate both real and simulation transactions with a single lower bound rather than hardcoding `>= 3`.
  </Note>
</Danger>

<Note>
  You can only use the `__validate_deploy__` function in an account contract to validate the `DEPLOY_ACCOUNT` transaction for that same contract. That is, this function runs at most once throughout the lifecycle of the account.
</Note>

### Transaction flow

When the sequencer receives a transaction, it calls the corresponding function with the appropriate input from the transaction's data, as follows:

* For a `DECLARE` transaction, the sequencer validates the transaction by calling the `__validate_declare__` function.

* For an `INVOKE` transaction, the sequencer calls the `__validate__` function with the transaction's calldata as input, after being deserialized to the arguments in the `__validate__` function's signature. After successfully completing validation, the sequencer calls the `__execute__` function with the same arguments.

* For a `DEPLOY_ACCOUNT` transaction, the sequencer calls the `constructor` function with the transaction's `constructor_calldata` as input, after being deserialized to the arguments in the constructor signature. After the successful execution of the constructor, the sequencer validates the transaction by calling the `__validate_deploy__` function.

<Note>
  Separating the validation and execution stages guarantees payment to sequencers for work completed and protects them from DoS attacks.
</Note>

<Tip>
  For more information on transaction types and lifecycle, see [Transactions](/learn/protocol/transactions/).
</Tip>

### Validation limitations

The logic of the `__validate__`, `__validate_deploy__`, `__validate_declare__`, and `constructor` functions can be mostly arbitrary, except for the following limitations:

<Note>
  For the `constructor` function, limitations apply only when run in a `DEPLOY_ACCOUNT` transaction (in particular, **not** when an account is deployed from an existing class via the `deploy` syscall).
</Note>

* You cannot use more than 1,000,000 Cairo steps

* You cannot use more than 100,000,000 gas

* You cannot call the following syscalls:

  * `get_class_hash_at`

  * `get_sequencer_address` (this syscall is only supported for Cairo 0 contracts)

  <Warning>
    As of Starknet version 0.14.0, calling the `deploy` syscall from the `__validate__`, `__validate_deploy__`, `__validate_declare__`, and `constructor` functions is also not possible.
  </Warning>

* You cannot call functions in external contracts

  <Note>
    This restriction enforces a single storage update being able to invalidate only transactions from a single account. However, be aware that an account can always invalidate its own past transactions by, for example, changing its public key. This limitation implies that the fees you need to pay to invalidate transactions in the mempool are directly proportional to the number of unique accounts whose transactions you want to invalidate.
  </Note>

* When calling the `get_execution_info` syscall:

  * `sequencer_address` is set to zero

  * `block_timestamp` returns the time (in UTC), rounded to the most recent hour

  * `block_number` returns the block number, rounded down to the nearest multiple of 100

These limitations are designed to prevent DoS attacks on the sequencer:

* An attacker could cause the sequencer to perform a large amount of work before a transaction fails validation, for example by spamming `INVOKE` transactions whose `__validate__` requires many steps but eventually fails, or by spamming `DEPLOY_ACCOUNT` transactions whose constructor or `__validate_deploy__` fails. Capping the number of steps keeps this work bounded.

* Even with a simple validation step, a "mempool pollution" attack is possible: an attacker fills the mempool with valid-looking transactions, then sends a single transaction that invalidates all of them (e.g. flipping a storage slot that `__validate__` depends on). Restricting `__validate__` from calling external contracts prevents this, because it ensures that a single storage update can only invalidate transactions from the account that owns that storage.

### Validation failures and statuses

When the `__validate__`, `__validate_deploy__`, or `__validate_declare__` functions fail, the account does not pay any fee and the transaction is dropped from the mempool without being included in a block. When `__execute__` fails, the transaction is included in a block as `REVERTED` and the sequencer charges a fee for work done up to the point of failure.

<Note>
  **Changed in Starknet v0.14.0 (RPC v0.9):** The `REJECTED` transaction status has been removed. Transactions that fail validation are no longer surfaced as `REJECTED`; they are simply dropped.
</Note>

<Tip>
  For the full list of transaction statuses and their meanings, see [Transaction statuses](/learn/protocol/transactions#transaction_statuses).
</Tip>

## Account nonces

Similar to Ethereum, every contract in Starknet, including account contracts, has a nonce. The nonce of a transaction sent from an account must match the nonce of that account, which changes after the transaction is executed — even if it was reverted. Nonces serve two important roles:

* They guarantee transaction hash uniqueness, which is important for a good user experience

* They provide replay protection to the account, by binding the signature to a particular nonce and preventing a malicious party from replaying the transaction

Starknet currently determines the nonce structure at the protocol level to be sequential (i.e., the nonce of a transaction sent from an account is incremented by one after the transaction is executed). In the future, Starknet will consider a more flexible design, extending account abstraction to nonce abstraction.

However, unlike Ethereum, only the nonce of account contracts — that is, those adhering to Starknet's account structure — can be non-zero in Starknet. In contrast, in Ethereum, regular smart contracts can also increment their nonce by deploying contracts (i.e., executing the `CREATE` and `CREATE2` opcodes).

## Deploying a new account

New accounts can be deployed in the following ways:

* Sending a `DEPLOY_ACCOUNT` transaction, which does not require a preexisting account.

* Using the [Universal Deployer Contract (UDC)](https://old-docs.openzeppelin.com/contracts-cairo/3.0.0-alpha.2/udc), which requires an existing account to send the `INVOKE` transaction.

Upon receiving one of these transactions, the sequencer performs the following steps:

1. Runs the respective validation function in the contract, as follows:

   * When deploying with the `DEPLOY_ACCOUNT` transaction type, the sequencer executes the `__validate_deploy__` function in the deployed contract.

   * When deploying using the UDC, the sequencer executes the `__validate__` function in the contract of the sender's address.

2. Executes the constructor with the given arguments.

3. Charges fees from the new account address.

<Note>
  If you use a `DEPLOY_ACCOUNT` transaction, the fees are paid from the address of the deployed account. If you use the UDC, which requires an `INVOKE` transaction, the fees are paid from the sender's account.
</Note>

4. Sets the account's nonce as follows:

   * `1`, when deployed with a `DEPLOY_ACCOUNT` transaction

   * `0`, when deployed with the UDC

## Recommended standards

### SNIP-6

While not mandatory at the protocol level, [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) defines a richer standard interface for accounts. It was developed by community members at [OpenZeppelin](https://www.openzeppelin.com/), in close collaboration with wallet teams and other core Starknet developers. SNIP-6 adds an `is_valid_signature` function on top of the protocol-required functions, enabling DApps and Sign-In-with-Starknet flows to verify off-chain signatures without submitting a transaction.

Recommended for compatibility with wallets, tools, and standards across the ecosystem.

### SNIP-9: Outside Execution

[SNIP-9](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md), also known as *outside execution* or *meta-transactions*, allows a third party (such as a protocol or relayer) to submit a transaction on behalf of a user account. The user signs an "outside execution" object off-chain; the relayer then submits it on-chain, paying the gas fees on the user's behalf.

This enables several use cases:

* **Delayed or conditional orders:** A user pre-signs a trade to execute at a future time or price.
* **Fee sponsorship:** A dApp or protocol pays gas fees on behalf of a user (the signing account doesn't need to hold STRK at execution time).
* **Gasless onboarding:** New users can interact with Starknet before funding their account.

SNIP-9 requires the user's account to implement the `execute_from_outside` function (as defined in the SNIP). Wallet support as of early 2025:

| Wallet       | Minimum version | SNIP-9 version |
| ------------ | --------------- | -------------- |
| ArgentX      | v0.4.0          | v2             |
| Braavos      | v1.1.0          | v2             |
| OpenZeppelin | v1.0.0          | v2             |

### SNIP-29: Paymaster

[SNIP-29](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-29.md) introduces a standard interface for *paymaster* services. A paymaster is a third-party service that pays gas fees on behalf of a user, allowing the user to pay in alternative tokens (such as ETH or USDC) or to have fees sponsored entirely by a dApp.

<Note>
  Using a paymaster requires the account to be SNIP-9 compatible.
</Note>

With the removal of ETH fee payment in v0.14.0 (which made v3 transactions mandatory, with STRK as the only native fee token), paymasters became the primary mechanism for paying fees in other tokens. Paymaster services such as [AVNU](https://avnu.fi/) are available on both Mainnet and Sepolia.

### Reference implementation

To review a concrete implementation of all the above standards, see OpenZeppelin's [account component](https://github.com/OpenZeppelin/cairo-contracts/blob/v3.0.0/packages/account/src/account.cairo), which adheres to [SNIP-6](/learn/protocol/accounts#snip-6) and [SNIP-9](/learn/protocol/accounts#snip-9-outside-execution).

## Legacy and deprecated

### Old transaction versions

Transaction versions 0, 1, and 2 were **removed in Starknet v0.14.0**. Only version 3 is valid. Sending a v0, v1, or v2 transaction will be rejected by the network.

<Tip>
  For the full version history of each transaction type, see [Transaction types](/learn/protocol/transactions#transaction_types).
</Tip>

### Legacy accounts

Older accounts do not implement `__validate__` and relied on version 0 transactions for authorization. These accounts can still be accessed using the `meta_tx_v0` syscall, introduced in v0.14.0. This syscall wraps the call in a v3 transaction but presents it to the called account as a v0 transaction internally — replacing the transaction hash, version, and caller address — so that the legacy account can verify its original v0-style signature.

**Do not use `meta_tx_v0` for new account implementations.** It exists solely to preserve access to already-deployed legacy accounts.

### Internal calls to `__execute__`

Disallowed at the protocol level as of **Starknet v0.14.0**.

Transactions that contain an internal `call_contract` syscall targeting an entry point named `__execute__` are **reverted**. Despite this protocol-level enforcement, it remains best practice to keep the following check in your `__execute__` implementation for defense-in-depth:

```rust theme={null}
assert!(get_caller_address().is_zero());
```

## Version history

The following table summarizes key account-related changes across Starknet versions:

| Starknet version | Key account-related changes                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| v0.10.0          | Introduced the `__validate__`/`__execute__` separation. Transaction version 1 added for `INVOKE` and `DECLARE`. Version 0 transactions deprecated.                                                                                                                                                                                                                                                                                         |
| v0.10.1          | `DEPLOY_ACCOUNT` transaction type introduced, replacing the old `deploy` transaction for deploying account contracts. `__validate_deploy__` entry point added.                                                                                                                                                                                                                                                                             |
| v0.12.3          | `sequencer_address` restricted to zero in `get_execution_info` when called from `__validate__`, `__validate_deploy__`, `__validate_declare__`, or `constructor` (in a `DEPLOY_ACCOUNT` transaction).                                                                                                                                                                                                                                       |
| v0.13.0          | Transaction version 3 introduced, with STRK fee payment support and reserved fields for future features including paymasters.                                                                                                                                                                                                                                                                                                              |
| v0.13.1          | `block_timestamp` and `block_number` return rounded values when called from validation functions (hour and nearest multiple of 100, respectively).                                                                                                                                                                                                                                                                                         |
| v0.14.0          | Transaction versions 0, 1, and 2 removed ([SNIP-16](https://community.starknet.io/t/snip-16-deprecation-of-transaction-versions-0-1-2/114443)). `meta_tx_v0` syscall introduced for legacy account access. Transactions with internal calls to `__execute__` are now reverted. `REJECTED` transaction status removed (RPC v0.9). `CANDIDATE` and `PRE_CONFIRMED` statuses introduced. `deploy` syscall prohibited in validation functions. |
