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
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
New to B20?

1. [B20 Overview](overview.md)
2. [How B20 Works](architecture.md)
2. [Integration architecture](integration-architecture.md)
3. [How B20 Works](architecture.md)

Building something?

Expand Down
8 changes: 4 additions & 4 deletions docs/concepts/token-types.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Token Types

*What Asset and Stablecoin are, how `createB20` seals the type into the address, and what each type adds on top of `IB20`. Address encoding and node dispatch are in [Architecture](../architecture.md). Roles and policies are shared across types; see [Roles and Pause](roles-and-pause.md) and [Policies](policies.md).*
*What Asset and Stablecoin are, how `createB20` seals the type into the address, and what each type adds on top of `IB20`. Address shape and how you recognize a live token are in [Identity and code](../integration-architecture.md#3-identity-and-code). Roles and policies are shared across types; see [Roles and Pause](roles-and-pause.md) and [Policies](policies.md).*

## 1. What a token type is

Expand Down Expand Up @@ -28,7 +28,7 @@ flowchart TD

General-purpose tokens, including RWAs, and fiat-pegged tokens need different class-defining fields. One combined surface would put a currency code on every Asset and announcements on every Stablecoin. B20 splits the surface: Asset carries configurable decimals, announcements, a scheduled UI multiplier, extra metadata, and batched mint. Stablecoin carries an immutable currency code and a fixed `6` decimal convention.

If those extras were optional flags on one binary, class rules would be runtime checks. A Stablecoin address could then execute Asset selectors. B20 compiles each variant as a separate native implementation. The node reads address byte `[10]` and runs that variant's logic. A Stablecoin address never executes Asset selectors. An Asset address never executes Stablecoin selectors.
If those extras were optional flags on one binary, class rules would be runtime checks. A Stablecoin address could then execute Asset selectors. B20 keeps the variants separate: byte `[10]` selects which selectors that address accepts. A Stablecoin address never executes Asset selectors. An Asset address never executes Stablecoin selectors.

Wallets, indexers, and issuers still need one Factory, one policy model, and one ERC-20 surface. Duplicating that stack per type would split every integration. Shared infrastructure stays shared: Factory, Policy Registry, Activation Registry, and `IB20`. Callers that only need balances, transfers, roles, or policies use `IB20`. Type-specific calls use `IB20Asset` or `IB20Stablecoin` at the same address.

Expand All @@ -38,7 +38,7 @@ The issuer chooses the variant only in Factory `createB20(variant, salt, params,

`params` carries identity. Name, symbol, and `initialAdmin` are shared. Asset adds `decimals`. Stablecoin adds `currency`. The blob is ABI-encoded with a leading `version` byte (currently `1`): `B20AssetCreateParams` or `B20StablecoinCreateParams`. Optional `initCalls` run on the new token in the same transaction. Then the Factory drops access. If that variant is not activated (`B20Asset` / `B20Stablecoin`), `createB20` reverts `FeatureNotActivated`. Deactivating a variant blocks new creation. Existing tokens keep running.

After creation, the node reads the variant byte in the address and runs that variant's logic. How the node recognizes the `0xB2` prefix and the `0xef` stub, and how it dispatches on byte `[10]`, is in [Architecture §2](../architecture.md#2-how-a-token-is-created).
After creation, byte `[10]` is what selects the variant. How you recognize a live token from its `0xB2` prefix and `0xef` code is in [Identity and code](../integration-architecture.md#3-identity-and-code).

## 4. Asset

Expand Down Expand Up @@ -111,7 +111,7 @@ After return, address byte `[10]` is `0x00`. `decimals()` is `18`. [`IB20Asset`]

### 6.3 A later call

A call to the token address does not choose the type again. The node reads byte `[10]` and runs that variant's logic.
A call to the token address does not choose the type again. Byte `[10]` already selected which selectors that address accepts.

```mermaid
flowchart TD
Expand Down
114 changes: 114 additions & 0 deletions docs/integration-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# B20 Integration Architecture

*What a caller can observe and rely on. This document describes B20's externally observable architecture and the properties third-party integrators can build against. It does not describe the internal implementation of B20 in `base/base`.*

*For the product tour, see [Overview](overview.md). For roles, policies, and token types, see [Concepts](concepts/). For the event list, see [Events](reference/events.md).*

## 1. Mental Model

A B20 is an account you call like a contract. You send ABI-encoded calldata to an address. The call returns data, reverts with a custom error, or emits events. Balances, transfers, and approvals follow ERC-20. Roles, pause, mint, burn, seize, and policies are part of the same interface.

The protocol provides that execution. You do not deploy per-token logic, and the account does not carry a program you maintain. Every token of a variant exposes the same interface, so a wallet, an issuer, or an app integrates once.

Two properties follow for a caller:

- A successful call commits state at the target address. A revert restores that call's writes, the same way a contract revert does.
- A later view returns the state those writes committed. `balanceOf`, `hasRole`, `isPaused`, and `isAuthorized` read that state directly.

Asset and Stablecoin share this shape. They differ in the extra interface at the same address. See [Token Types](concepts/token-types.md).

## 2. System Surface

Four components matter to an integrator. Three are singletons at fixed addresses. Tokens are many addresses, each created by the Factory.

| Component | Address | What you call it for |
| --- | --- | --- |
| Factory | `0xB20f000000000000000000000000000000000000` | `createB20`, `getB20Address`, `isB20`, `isB20Initialized` |
| Policy Registry | `0x8453000000000000000000000000000000000002` | Shared allowlists, blocklists, and composite policies |
| Activation Registry | `0x8453000000000000000000000000000000000001` | Whether a Factory or token feature is on |
| B20 token | Derived by `createB20` | Balances, transfers, roles, pause, mint, burn, seize, and the policy IDs bound to that token |

The Factory creates a token and then stops. After `createB20` returns, the Factory has no further access to that token.

The Policy Registry holds the lists. A token stores a policy ID, not the members. Many tokens can store the same ID. An update to that policy changes `isAuthorized` for every token that stores it. Those tokens do not need another `updatePolicy`.

The Activation Registry is a Base-operated switch. Issuers and apps read it. When a feature is inactive, writes that require it revert with `FeatureNotActivated`. Reads stay available. Turning a variant off stops new `createB20` calls for that variant. Tokens that already exist keep running.

Constants for these addresses live in [`StdPrecompiles`](../src/StdPrecompiles.sol).

## 3. Identity and Code

### 3.1 What appears at a token address

`getB20Address(variant, sender, salt)` returns the address `createB20` will use. It never reverts. If that account is already occupied, `createB20` reverts `TokenAlreadyExists`.

The address is self-describing:

- Byte `[0]` is `0xB2`.
- Bytes `[1:9]` are zero.
- Byte `[10]` is the variant. Asset is `0x00`. Stablecoin is `0x01`.
- The remaining bytes are derived from `keccak256(sender, salt)`.

Byte `[10]` is the type, and it does not change after `createB20` returns. An Asset address exposes [`IB20`](../src/interfaces/IB20.sol) and [`IB20Asset`](../src/interfaces/IB20Asset.sol). A Stablecoin address exposes `IB20` and [`IB20Stablecoin`](../src/interfaces/IB20Stablecoin.sol). A selector that belongs to the other variant does not run on that address.

`isB20(address)` reports the `0xB2` prefix only. It can return true for an address the Factory has not created, and it never reverts. `isB20Initialized(address)` is the liveness check. It flips once, when the creating `createB20` returns, and it never reverts. During `initCalls` in that same call, it is still false.

Before creation, the predicted address has no code. A call to it returns no output. It is not a token yet.

### 3.2 What `0xef` means

On creation, the Factory sets the account code to a single byte, `0xef`. That is the entire code. It is a marker, not a program. B20 tokens are not EVM contracts, so there is no bytecode to verify or upgrade at that address.

`0xef` is the [EIP-3541](https://eips.ethereum.org/EIPS/eip-3541) reserved prefix. Ordinary `CREATE` and `CREATE2` cannot deploy code that starts with it. An address with the `0xB2` prefix and this code came from the Factory.

### 3.3 How tooling should read that code

Use the code as an identity check. Call the token through its ABI.

- `eth_getCode` on a created token returns `0xef`. The size is 1. Both variants use that same byte. Read the variant from address byte `[10]`, or from `B20Created.variant`.
- Treat `isB20` as a prefix filter. Treat `isB20Initialized`, or the pair of prefix plus code `0xef`, as "this token exists."
- Decode calls and logs with `IB20`, plus `IB20Asset` or `IB20Stablecoin`. The code byte is not the interface.
- Index creation from `B20Created`. The Factory emits it once, after identity is sealed and before `initCalls`.

## 4. Protocol Evolution

Base changes B20 in protocol upgrades. On Base those upgrades are hardforks. For an integrator, a hardfork can introduce a new precompile, or it can change the logic that answers at an address you already call.

What stays stable:

- Singleton addresses do not move. The Factory, the Policy Registry, and the Activation Registry stay at the addresses in [§2](#2-system-surface).
- A token address does not move, and byte `[10]` does not change.
- A call in a past block keeps the result it had in that block. A node that replays history from genesis reaches the same state as a node that was live for those blocks. An upgrade does not rewrite blocks that already executed.
- After the upgrade, new calls use the upgraded behavior at the same address. You do not point your integration at a new token address to pick up the upgrade.
- A call the active protocol cannot serve reverts. There is no fallback to some other behavior.

Integrate against the interface of the release you are on. A later hardfork can add selectors at these same addresses. Those selectors are absent until that hardfork.

## 5. Integration Guarantees

### 5.1 Contractual

These properties are the external behavior of the interfaces in this repository. You can build on them.

- Call B20 with the contract ABI at the singleton addresses in [§2](#2-system-surface), and at token addresses from `getB20Address` and `createB20`.
- `createB20` is the only creation path. The address is determined by `(variant, sender, salt)`.
- A completed token has code `0xef` and the address shape in [§3.1](#31-what-appears-at-a-token-address). `isB20Initialized` means creation has returned. `isB20` means the prefix matches.
- Byte `[10]` is fixed after creation. It selects `IB20Asset` or `IB20Stablecoin` in addition to `IB20`.
- A revert restores the state writes of that call.
- After `createB20` returns, the Factory cannot act on that token.
- A Policy Registry update is visible to every token that stores that policy ID.
- Across a hardfork, the addresses in [§2](#2-system-surface) stay put, and historical execution stays put, as in [§4](#4-protocol-evolution).
- An inactive feature rejects the writes that require it with `FeatureNotActivated`. It does not remove existing tokens, and it does not disable reads.

### 5.2 Non-contractual

These are outside the integration contract.

- How the client decides to run the call, and any other detail of the implementation in `base/base`.
- Storage slot numbers and packing. Use the views. Account storage is readable with `eth_getStorageAt`, and the slot layout is not an interface.
- Exact gas. A call can run out of gas and revert. The amounts can change across upgrades.
- Treating `Paused.features` or `Unpaused.features` as the full paused set.
- Treating `isB20 == true` as proof that the token exists.
- Calling `activate` or `deactivate`. Base operates the Activation Registry. Integrations read `isActivated`.
- Selectors, events, or variants that a future hardfork might add. They are not available early, and this document does not promise a specific future feature.
- Executing or extending the `0xef` byte. It is the marker the Factory plants. EIP-3541 rejects a normal deployment whose code starts with that byte.
10 changes: 7 additions & 3 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ B20 is Base's native token standard for issuing and managing programmable assets

This document provides a high-level introduction to B20: what it is, why it exists, the core primitives it exposes, and how those pieces fit together.

For a deeper technical explanation, see [How B20 Works](./architecture.md).
For the contract a caller can rely on, see [Integration architecture](./integration-architecture.md). For how execution works, see [How B20 works](./architecture.md).

---

Expand Down Expand Up @@ -181,9 +181,13 @@ sequenceDiagram

## Where to Go Next

If you want to understand how B20 works internally:
If you want the contract a caller can rely on:

→ [B20 Architecture](./architecture.md)
→ [Integration architecture](./integration-architecture.md)

If you want how execution works:

→ [How B20 works](./architecture.md)

If you are integrating B20:

Expand Down
Loading