Skip to content
Draft
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
6 changes: 6 additions & 0 deletions api/rpc/contracts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ Returns basic account information.
```
</Accordion>

<Info>
**Extra fields on an uninitialized [universal account](/protocol/accounts-contracts/account-id#universal-address)**

A `0u` account that's been funded by a transfer but hasn't run its `UniversalStateInit` yet additionally returns `state: "uninitialized"` and `bootstrap_nonce` (the value its self-signed init transaction's nonce is based on — that transaction must use `bootstrap_nonce + 1`). Both fields are omitted once the account initializes — see [Creating Universal Accounts](/protocol/accounts-contracts/creating-universal-accounts#the-uninitialized-account) for a full example response.
</Info>

---

## View Account Changes
Expand Down
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@
"pages": [
"protocol/accounts-contracts/account-model",
"protocol/accounts-contracts/account-id",
"protocol/accounts-contracts/access-keys"
"protocol/accounts-contracts/access-keys",
"protocol/accounts-contracts/creating-universal-accounts"
]
},
{
Expand Down
6 changes: 5 additions & 1 deletion protocol/accounts-contracts/access-keys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,13 @@ To recognize one of your own post-quantum keys in such a list, derive the same h
You can create and manage `ml-dsa-65` keys today with the [NEAR CLI](/tools/cli#keys), and from your app with the [`near-kit`](/tools/near-api#post-quantum-keys-ml-dsa-65) (TypeScript) and [`near-kit-rs`](https://github.com/r-near/near-kit-rs) (Rust) libraries. Contracts can add them via [`near-sdk-rs`](/smart-contracts/anatomy/actions) with no code changes.

<Tip>
Post-quantum support currently covers **transaction signing and access keys**. Validator (staking) keys, block production, and [implicit account](./account-id#implicit-address) addresses continue to use `ed25519`.
Post-quantum support currently covers **transaction signing and access keys, on any account type** — an implicit account can add an `ml-dsa-65` key exactly like it can add any other key. What can't change is an implicit account's *address-defining* key: its address **is** that key's own bytes, so it's permanently `ed25519`. For an address that isn't tied to a classical key at all, use a [universal account](./account-id#universal-address) instead, whose `0u` address is a SHA3-256 hash and whose access keys can include `ml-dsa-65` from the moment it's created.
</Tip>

<Note>
**Staking doesn't depend on account type.** A universal account can stake, and can sign the [`Stake`](/protocol/transactions/transaction-anatomy#actions) transaction itself with an `ml-dsa-65` key, exactly like it can sign any other action. The one thing that stays `ed25519` regardless of account type is the separate `public_key` field *named inside* that action, which becomes the validator node's own block-production key — a different piece of consensus math, unrelated to whichever key signed the transaction that proposed it.
</Note>

---

## Limited Access Key Caveats
Expand Down
45 changes: 45 additions & 0 deletions protocol/accounts-contracts/account-id.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ NEAR accounts are identified by a unique address, which can take multiple forms:
2. [**Named address**](#named-address), which act as domains (e.g. `alice.near`, `sub.account.testnet`)
3. An ethereum-like account (e.g. `0x85f17cf997934a597031b2e18a9ab6ebd4b9f6a4`)
4. [**Deterministic address**](#deterministic-address), which start with `0s` (e.g. `0s85f17cf997934a597031b2e18a9ab6ebd4b9f6a4`)
5. [**Universal address**](#universal-address), which start with `0u` (e.g. `0u0000000000000000000000000000000000000000000000000000`)

<Accordion title="Valid Account IDs">

Expand Down Expand Up @@ -183,4 +184,48 @@ Any NEAR attached beyond the required storage deposit is refunded to the sender.
**Deterministic accounts cannot be deleted** and certain account modifications available to regular accounts are restricted. Once created, the deployed code and pre-initialized storage are permanent.


</Info>

---

## Universal Address

Universal accounts are a new kind of account which combines features of deterministic and implicit accounts. Like a deterministic account, a universal account's id is the hash of its initial state, so you always know the id before the account exists. However, the initial state can also contain access keys, which allows universal accounts to be used the same way as implicit accounts. They are identified by an address starting with `0u` followed by 52 lowercase [Crockford base32](https://www.crockford.com/base32.html) characters (54 characters total).

For example: `0u0000000000000000000000000000000000000000000000000000`

<Info>
**How is this different from a deterministic (`0s`) address?**

A deterministic address hashes only code and storage, so the only way to control one is through the contract's own logic. A universal address hashes code, storage, **and** a set of access keys together, so a universal account can be a plain, key-controlled account with no contract at all, similar to an implicit account. One important difference though is that its access keys can include a post-quantum [`ml-dsa-65`](./access-keys#post-quantum-keys-ml-dsa-65) key from the moment it's created, which an implicit account's address structurally can't support (its address **is** the key's own bytes).
</Info>

<Accordion title="🧑‍💻 Technical: How is the address derived?">

The universal account ID is computed by:
1. Constructing a `UniversalStateInit` value containing:
- **Code** (optional): a reference to the contract code via a code hash or an existing account ID, omitted for a key-only account
- **Data**: a key-value mapping pre-populating the contract's storage, empty for a key-only account
- **Access keys**: a set of access-key handles the account starts with, empty for a contract-only account. A handle is the full public key for `ed25519`/`secp256k1`, or the 32-byte hash for `ml-dsa-65` — the same [compact form](./access-keys#post-quantum-keys-ml-dsa-65) used everywhere else
2. Borsh-encoding that value into bytes
3. Applying the formula: `'0u' + crockford_base32(sha3_256(bytes))`

Unlike the `0s` scheme, the protocol does not re-serialize the bytes into a canonical form before hashing: the id commits to the **exact bytes supplied**, so two different byte-level encodings of an otherwise identical state produce two different accounts.

</Accordion>

<Accordion title="🧑‍💻 Technical: How are universal accounts created?">

Universal accounts are created using the `UniversalStateInit` action — like `DeterministicStateInit`, a distinct action from the standard `CreateAccount` action. See [Creating Universal Accounts](./creating-universal-accounts) for the three ways to submit one, with code and RPC examples.

Smart contracts can build this action using two host functions:
- `universal_state_init_to_account_id` — derives the `0u` account id from a state init, with no side effects
- `promise_batch_action_universal_state_init` — appends a `UniversalStateInit` action to a promise, with an attached deposit that tops up the account's storage stake if needed (any unused amount is refunded, it doesn't just add to the account's balance — see [Creating Universal Accounts](./creating-universal-accounts#relayer-assisted-creation) for how funding actually works)

</Accordion>

<Info>

A NEAR transfer to a `0u` address that does not exist yet **auto-creates it as uninitialized**: it holds the transferred balance but has no code, keys, or storage until a matching `UniversalStateInit` arrives. See [The uninitialized account](./creating-universal-accounts#the-uninitialized-account) for what an uninitialized account can and can't do.

</Info>
4 changes: 3 additions & 1 deletion protocol/accounts-contracts/account-model.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ Let's take a closer look at the different elements that compose the NEAR account
NEAR **natively** implements multiple types of accounts, including:
1. **Named accounts** such as `alice.near`, which are simple to remember and share
2. **Implicit accounts** such as `fb9243ce...`, which are derived from a private key
2. **Ethereum-like accounts** which are compatible with Ethereum wallets
3. **Ethereum-like accounts** which are compatible with Ethereum wallets
4. **Deterministic accounts** such as `0s85f17...`, whose address is computed from their initial code and storage
5. **Universal accounts** such as `0u0000...`, whose address is computed from their initial code, storage, and (optionally post-quantum) access keys

<hr className="subsection" />

Expand Down
Loading