> ## Documentation Index
> Fetch the complete documentation index at: https://base-a060aa97-docs-use-case-ia-overhaul.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# B20 Token Standard

> What B20 is — Base's native token standard — how its factory, roles, policies, and variants work, and how to launch a B20 token in a single transaction.

B20 is Base's native token standard: an ERC-20 superset that runs as a native precompile, which makes transfers cheaper and higher-throughput than a standard contract token while keeping full ERC-20 compatibility. Roles, supply caps, pausing, policy gating, memos, and `permit` are built into the chain, so you don't build, audit, or maintain that logic yourself.

## How B20 works

Every B20 token is created by the singleton [**B20 Factory**](/base-chain/specs/upgrades/beryl/b20#factory), which returns a fully configured token in a single transaction. Two variants cover the common cases:

* **Asset** — configurable decimals (6–18), plus a rebase multiplier, onchain announcements, and batched issuance. Use it for real-world assets, equities, and long-tail tokens.
* **Stablecoin** — fixed at 6 decimals with a self-declared ISO currency code. Use it for fiat-backed tokens.

Both variants share the same core surface: [role-based access control](/base-chain/specs/upgrades/beryl/b20#roles-model), optional [supply caps](/base-chain/specs/upgrades/beryl/b20#supply-cap), [policy-gated transfers and mints](/base-chain/specs/upgrades/beryl/b20#policy-integration), [granular pause](/base-chain/specs/upgrades/beryl/b20#pause), [memos](/base-chain/specs/upgrades/beryl/b20#memos), and ERC-2612 `permit`. For the complete ABIs and precompile behavior, see the [B20 native token standard](/base-chain/specs/upgrades/beryl/b20) spec.

The rest of this page walks through launching a B20 Asset token: create it, mint its initial supply, and verify the balance onchain. To accept the token as payment in an app, continue with [Accept a B20 payment](/build-on-base/accept-payments/from-humans#accept-b20-with-a-memo).

## Before you begin

You need **Base's Foundry build** (`base-forge`, `base-cast`, [`base-anvil`](https://github.com/base/base-anvil)). Install the Beryl-compatible `v1.1.1` release via `base-foundryup`:

```bash Terminal theme={null} theme={null}
curl -L https://raw.githubusercontent.com/base/base-anvil/HEAD/foundryup/install | bash
base-foundryup --install v1.1.1
```

<Note>
  Standard `forge` cannot simulate calls to B20 precompile addresses (they hold no contract bytecode) and aborts with `call to non-contract address`. Base's `base-forge` registers the precompiles into its EVM. It installs alongside your existing Foundry toolchain without overwriting it — use `base-forge`, `base-cast`, and `base-anvil` for all commands in this guide.
</Note>

## Verify the Activation Registry is enabled

<Warning>
  Attempting to deploy before the Activation Registry is enabled will revert with `FeatureNotActivated`. Run the check for the variant you plan to deploy and confirm it returns `true` before proceeding:
</Warning>

```bash Terminal theme={null} theme={null}
REG=0x8453000000000000000000000000000000000001  # Activation Registry precompile
RPC=https://mainnet.base.org  # replace with your target network (e.g. https://sepolia.base.org, https://rpc.vibes.base.org)

base-cast call $REG "isActivated(bytes32)(bool)" $(base-cast keccak "base.b20_asset") --rpc-url $RPC
base-cast call $REG "isActivated(bytes32)(bool)" $(base-cast keccak "base.b20_stablecoin") --rpc-url $RPC
```

<Warning>
  The examples in this guide and the use-case guides target the released Beryl interface in `base-std@v1.0.0`. Do not install from the moving `main` branch: unreleased interfaces can compile while targeting functions that are not part of the documented Beryl surface.
</Warning>

## Set up Foundry

```bash Terminal theme={null} theme={null}
base-forge init b20-quickstart && cd b20-quickstart
base-forge install base/base-std@v1.0.0 --no-git
```

This installs the [Base Standard Library v1.0.0](https://github.com/base/base-std/tree/v1.0.0), which provides the released Beryl interfaces, constants, and encoding helpers used below.

Add the remappings and the `base = true` flag to `foundry.toml` (under `[profile.default]`). `base = true` tells Base's `forge` build to run the B20 precompiles inside its EVM, so the deploy script's local simulation can call the factory:

```toml foundry.toml theme={null} theme={null}
base = true
remappings = [
    "base-std/=lib/base-std/src/",
    "base-std-test/=lib/base-std/test/",
]
```

<Note>
  The interfaces compile with any Solidity `>=0.8.20 <0.9.0`.
</Note>

## Set up TypeScript and CLI clients

The task guides use `viem@2.55.11` for application code and `base-cast@v1.1.1` for operational commands. Install viem in a separate project and keep signing keys in environment variables:

```bash Terminal lines wrap theme={null}
mkdir b20-viem && cd b20-viem
npm init -y
npm install viem@2.55.11
```

```typescript clients.ts lines wrap expandable theme={null}
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';

if (!process.env.PRIVATE_KEY) throw new Error('Set PRIVATE_KEY');
export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
export const publicClient = createPublicClient({
  chain: baseSepolia,
  transport: http(process.env.RPC_URL ?? 'https://sepolia.base.org'),
});
export const walletClient = createWalletClient({
  account,
  chain: baseSepolia,
  transport: http(process.env.RPC_URL ?? 'https://sepolia.base.org'),
});
```

For CLI examples, export the same values and install `jq` so policy-creation scripts can read `PolicyCreated` from transaction receipts:

```bash .env lines wrap theme={null}
export RPC_URL="https://sepolia.base.org"
export PRIVATE_KEY="0x..."
export ACCOUNT_ADDRESS="0x..."
```

<Warning>
  Never put a funded private key in browser code, source control, shell history, or a client-visible environment variable. The task guides use a throwaway Base Sepolia signer for reproducible scripts.
</Warning>

The [verified B20 fixture projects](https://github.com/base/docs/tree/master/examples/verified-doc-samples) contain the complete imports, ABIs, simulation helper, Foundry scripts, and CLI workflows used by the byte guides.

## Choose a network

Pick a network with the B20 precompiles active, then create a `.env` **inside your `b20-quickstart` project directory**. For full network details, see [Connecting to Base](/base-chain/quickstart/connecting-to-base).

<Tabs>
  <Tab title="Mainnet">
    | Setting  | Value                                |
    | -------- | ------------------------------------ |
    | RPC URL  | `https://mainnet.base.org`           |
    | Chain ID | `8453`                               |
    | Explorer | [basescan.org](https://basescan.org) |

    ```bash .env theme={null} theme={null}
    export RPC_URL="https://mainnet.base.org"
    export PRIVATE_KEY="0x..."
    export ACCOUNT_ADDRESS="0x..."
    export CHAIN_ID="8453"
    ```

    <Note>
      If you don't have an account, `base-cast wallet new` prints a fresh address and key.
    </Note>

    Confirm your account has ETH for gas:

    ```bash Terminal theme={null} theme={null}
    source .env
    base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
    ```

    <Check>
      The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
    </Check>
  </Tab>

  <Tab title="Base Sepolia">
    | Setting  | Value                                                                                                                               |
    | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
    | RPC URL  | `https://sepolia.base.org`                                                                                                          |
    | Chain ID | `84532`                                                                                                                             |
    | Faucet   | [CDP Faucet](https://portal.cdp.coinbase.com/products/faucet) or [other providers](/base-chain/network-information/network-faucets) |
    | Explorer | [sepolia.basescan.org](https://sepolia.basescan.org)                                                                                |

    ```bash .env theme={null} theme={null}
    export RPC_URL="https://sepolia.base.org"
    export PRIVATE_KEY="0x..."
    export ACCOUNT_ADDRESS="0x..."
    export CHAIN_ID="84532"
    ```

    <Note>
      If you don't have an account, `base-cast wallet new` prints a fresh address and key.
    </Note>

    Request testnet ETH from the faucet, then confirm it arrived:

    ```bash Terminal theme={null} theme={null}
    source .env
    base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
    ```

    <Check>
      The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
    </Check>
  </Tab>

  <Tab title="Vibenet">
    | Setting  | Value                                                                      |
    | -------- | -------------------------------------------------------------------------- |
    | RPC URL  | `https://rpc.vibes.base.org/`                                              |
    | Chain ID | `84538453`                                                                 |
    | Faucet   | [chain.base.org/vibenet/faucet](https://chain.base.org/vibenet/faucet)     |
    | Explorer | [chain.base.org/vibenet/explorer](https://chain.base.org/vibenet/explorer) |

    ```bash .env theme={null} theme={null}
    export RPC_URL="https://rpc.vibes.base.org/"
    export PRIVATE_KEY="0x..."
    export ACCOUNT_ADDRESS="0x..."
    export CHAIN_ID="84538453"
    ```

    <Note>
      If you don't have an account, `base-cast wallet new` prints a fresh address and key.
    </Note>

    Request testnet ETH from the faucet, then confirm it arrived:

    ```bash Terminal theme={null} theme={null}
    source .env
    base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
    ```

    <Check>
      The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
    </Check>
  </Tab>

  <Tab title="Local (base-anvil)">
    Start a local Base node in a separate terminal:

    ```bash Terminal theme={null} theme={null}
    base-anvil
    ```

    Create a `.env` using anvil's pre-funded account #0:

    ```bash .env theme={null} theme={null}
    export RPC_URL="http://127.0.0.1:8545"
    export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
    export ACCOUNT_ADDRESS="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
    export CHAIN_ID="31337"
    ```

    Confirm the node is running:

    ```bash Terminal theme={null} theme={null}
    source .env
    base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
    ```

    <Check>
      The command prints `10000000000000000000000` — anvil's default pre-funded balance of 10,000 ETH.
    </Check>
  </Tab>
</Tabs>

## Create your token

The factory's single entry point is `createB20(variant, salt, params, initCalls)`:

* `variant`: `ASSET` or `STABLECOIN`. This guide uses `ASSET`.
* `salt`: caller-chosen entropy that fixes the deterministic token address.
* `params`: ABI-encoded name, symbol, initial admin, and decimals.
* `initCalls`: optional batch of config calls applied at creation.

<Steps>
  <Step title="Write the create script">
    Use `B20FactoryLib` to encode `params` and `initCalls`. Create `script/CreateToken.s.sol`:

    ```solidity script/CreateToken.s.sol highlight={26} theme={null} theme={null}
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;

    import {Script, console} from "forge-std/Script.sol";

    import {B20Constants} from "base-std/lib/B20Constants.sol";
    import {B20FactoryLib} from "base-std/lib/B20FactoryLib.sol";
    import {IB20Factory} from "base-std/interfaces/IB20Factory.sol";
    import {StdPrecompiles} from "base-std/StdPrecompiles.sol";

    contract CreateToken is Script {
        function run() external returns (address token) {
            // For the quickstart, one account is admin + minter.
            address account = vm.envAddress("ACCOUNT_ADDRESS");
            bytes32 salt = keccak256("my-first-b20");

            // Name, symbol, initial DEFAULT_ADMIN_ROLE holder, decimals (6-18).
            bytes memory params = B20FactoryLib.encodeAssetCreateParams("My Token", "MYT", account, 18);

            // Configuration applied atomically at creation.
            bytes[] memory initCalls = new bytes[](2);
            initCalls[0] = B20FactoryLib.encodeGrantRole(B20Constants.MINT_ROLE, account);
            initCalls[1] = B20FactoryLib.encodeUpdateSupplyCap(1_000_000e18);

            vm.startBroadcast();
            token = StdPrecompiles.B20_FACTORY.createB20(IB20Factory.B20Variant.ASSET, salt, params, initCalls);
            vm.stopBroadcast();

            console.log("B20 token created at:", token);
        }
    }
    ```

    <Warning>
      **Encode with `B20FactoryLib`.** The native implementation rejects non-canonical calldata with `AbiDecodeFailed`; the helpers produce canonical encoding.
    </Warning>

    <Info>
      Asset decimals are fixed at creation and must be in `[6, 18]`. The supply cap is optional; the no-cap sentinel is `type(uint128).max` (the cap can never exceed `uint128.max`).
    </Info>

    <Accordion title="Want a stablecoin instead?">
      Use the `STABLECOIN` variant and its params encoder. A stablecoin fixes decimals at 6 and carries an immutable ISO currency code (uppercase `A`–`Z`) instead of a configurable decimals value:

      ```solidity theme={null} theme={null}
      bytes memory params = B20FactoryLib.encodeStablecoinCreateParams("My USD", "MUSD", account, "USD");

      token = StdPrecompiles.B20_FACTORY.createB20(IB20Factory.B20Variant.STABLECOIN, salt, params, initCalls);
      ```

      Everything else in this guide — roles, supply cap, minting, and verification — works identically.
    </Accordion>
  </Step>

  <Step title="Deploy the factory call">
    ```bash Terminal theme={null} theme={null}
    source .env
    base-forge script script/CreateToken.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast
    ```

    On success the script logs the new token's address. The factory is the fixed precompile at `0xB20f000000000000000000000000000000000000` (the same on every network); the tokens it creates start `0xB200...`:

    <Note>
      If you see `TokenAlreadyExists`, the salt `keccak256("my-first-b20")` is already registered on this network or anvil instance. Either restart `base-anvil` for a fresh state, or change the salt in the script to a unique value.
    </Note>

    ```text Output theme={null} theme={null}
    == Logs ==
      B20 token created at: 0xB200...
    ```
  </Step>

  <Step title="Capture the token address">
    Save the address to an environment variable so the next step needs no copy-paste. The broadcast artifact holds the return value:

    ```bash Terminal theme={null} theme={null}
    TOKEN_ADDRESS=$(jq -er '.returns.token.value' \
      broadcast/CreateToken.s.sol/$CHAIN_ID/run-latest.json) \
      && echo "export TOKEN_ADDRESS=$TOKEN_ADDRESS" >> .env \
      && source .env \
      && echo "TOKEN_ADDRESS=$TOKEN_ADDRESS"
    ```

    Appending to `.env` keeps `TOKEN_ADDRESS` available in later steps, even in a new terminal session.

    <Note>
      The broadcast path includes the chain ID, which the `CHAIN_ID` value in your `.env` supplies: `84532` for Sepolia, `84538453` for Vibenet, `31337` for local base-anvil.
    </Note>
  </Step>
</Steps>

## Mint and verify

Minting requires `MINT_ROLE`, which `initCalls` granted to your account.

<Steps>
  <Step title="Mint supply">
    ```bash Terminal theme={null} theme={null}
    base-cast send $TOKEN_ADDRESS "mint(address,uint256)" $ACCOUNT_ADDRESS 1000000000000000000000 \
      --rpc-url $RPC_URL --private-key $PRIVATE_KEY
    ```

    `base-cast send` prints a receipt with `status 1 (success)`.
  </Step>

  <Step title="Confirm the balance">
    ```bash Terminal theme={null} theme={null}
    base-cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL
    # 1000000000000000000000 [1e21]
    ```

    <Check>
      The token now holds minted supply onchain. Search `$TOKEN_ADDRESS` in the explorer to view it.
    </Check>
  </Step>
</Steps>

## What you built

In this guide you:

* Created a B20 Asset token with one `createB20` call
* Configured its admin, minter, and supply cap atomically via `initCalls`
* Minted supply
* Verified the balance onchain

All without writing, deploying, or auditing a token contract.

## Next steps

* [Accept a B20 payment in an app](/build-on-base/accept-payments/from-humans#accept-b20-with-a-memo): wire this token into a checkout flow that tags each payment with an order ID and reconciles it from onchain events.
* Gate transfers or mints with PolicyRegistry policies, add granular pause, or manage roles. See the [B20 token standard](/base-chain/specs/upgrades/beryl/b20).
