diff --git a/.github/workflows/base-std-docs-sync.yml b/.github/workflows/base-std-docs-sync.yml
index 0388f331e..a3182fdff 100644
--- a/.github/workflows/base-std-docs-sync.yml
+++ b/.github/workflows/base-std-docs-sync.yml
@@ -115,6 +115,9 @@ jobs:
# scripts/sync-from-base-std, then commits the result and opens a PR.
# --------------------------------------------------------------------------
apply:
+ # A streamed page edit takes seconds to a few minutes; a run that is still
+ # going after this long is stuck, not working.
+ timeout-minutes: 30
name: Open docs PR from base-std dispatch
needs: authorize
# Kill switch: see "Emit kill-switch notice" step in the authorize job
diff --git a/docs/AGENTS.md b/docs/AGENTS.md
index 19f2ef4fd..c8d4e5835 100644
--- a/docs/AGENTS.md
+++ b/docs/AGENTS.md
@@ -56,7 +56,7 @@ npx skills add base/base-skills
|Specifications/Specifications/Base Protocol/Execution:specifications/base-protocol/execution/l2-execution-engine,specifications/base-protocol/execution/precompiles,specifications/base-protocol/execution/predeploys,specifications/base-protocol/execution/preinstalls
|Specifications/Specifications/Base Protocol/Bridging:specifications/base-protocol/bridging/standard-bridges,specifications/base-protocol/bridging/deposits,specifications/base-protocol/bridging/withdrawals,specifications/base-protocol/bridging/cross-domain-messengers,specifications/base-protocol/bridging/base-solana-bridge
|Specifications/Specifications/Base Protocol/Proofs:specifications/base-protocol/proofs/overview,specifications/base-protocol/proofs/challenger,specifications/base-protocol/proofs/proposer,specifications/base-protocol/proofs/registrar,specifications/base-protocol/proofs/tee-prover,specifications/base-protocol/proofs/zk-prover,specifications/base-protocol/proofs/proof-contracts
-|Specifications/Specifications/B20:specifications/b20/specification-overview,specifications/b20/launch-a-b20-token,specifications/b20/tokenized-stocks-on-base,specifications/b20/changelog
+|Specifications/Specifications/B20:specifications/b20/specification-overview,specifications/b20/tokenized-stocks-on-base,specifications/b20/changelog
|Specifications/Specifications/B20/Reference:specifications/b20/reference/constants-addresses,specifications/b20/reference/errors-events,specifications/b20/reference/invariants-tests
|Specifications/Specifications/B20/Reference/Interfaces:specifications/b20/reference/interfaces/i-activation-registry,specifications/b20/reference/interfaces/ib20,specifications/b20/reference/interfaces/ib20-asset,specifications/b20/reference/interfaces/ib20-factory,specifications/b20/reference/interfaces/ib20-stablecoin,specifications/b20/reference/interfaces/i-policy-registry
|Specifications/Specifications/Transactions:specifications/transactions/transaction-ordering,specifications/transactions/transaction-finality,specifications/transactions/network-fees,specifications/transactions/throughput-and-limits,specifications/transactions/troubleshooting-transactions
diff --git a/docs/base-chain/quickstart/connecting-to-base.mdx b/docs/base-chain/quickstart/connecting-to-base.mdx
index 4ace17c49..f1c15ed0c 100644
--- a/docs/base-chain/quickstart/connecting-to-base.mdx
+++ b/docs/base-chain/quickstart/connecting-to-base.mdx
@@ -71,7 +71,7 @@ Base is an EVM-compatible Layer 2 network. Use the details below to connect your
| Block Explorer | [sepolia.basescan.org](https://sepolia.basescan.org) |
- Vibenet is Base's experimental preview network where new chain-level features are available before they roll out to Sepolia or Mainnet. It currently hosts [B20 tokens](/specifications/b20/launch-a-b20-token) — an ERC-20 superset with built-in roles, supply caps, pausing, policy gating, and `permit` implemented as a native precompile.
+ Vibenet is Base's experimental preview network where new chain-level features are available before they roll out to Sepolia or Mainnet. It currently hosts [B20 tokens](/build-on-base/issue-rwa/create-an-asset-token) — an ERC-20 superset with built-in roles, supply caps, pausing, policy gating, and `permit` implemented as a native precompile.
Use Vibenet to build against cutting-edge Base features. It is not intended for production or user-facing applications. Learn more at [chain.base.org/vibenet](https://chain.base.org/vibenet).
diff --git a/docs/build-on-base/accept-payments/request-a-payment.mdx b/docs/build-on-base/accept-payments/request-a-payment.mdx
index 07d915465..e78a34c7b 100644
--- a/docs/build-on-base/accept-payments/request-a-payment.mdx
+++ b/docs/build-on-base/accept-payments/request-a-payment.mdx
@@ -105,7 +105,7 @@ function pay(bytes32 orderId, uint256 amount) external {
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete B20 interface and memo details.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete B20 interface and memo details.
## Verify Before Fulfillment
diff --git a/docs/build-on-base/issue-rwa/announce-a-distribution.mdx b/docs/build-on-base/issue-rwa/announce-a-distribution.mdx
index edaf5951a..e194c57ee 100644
--- a/docs/build-on-base/issue-rwa/announce-a-distribution.mdx
+++ b/docs/build-on-base/issue-rwa/announce-a-distribution.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Announce and Distribute the Stock Dividend
@@ -66,7 +66,7 @@ export async function announceStockDividend(token: Address, holders: Address[])
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
The unique announcement ID is marked used and the batch mint executes between `Announcement` and `EndAnnouncement`.
diff --git a/docs/build-on-base/issue-rwa/apply-a-multiplier.mdx b/docs/build-on-base/issue-rwa/apply-a-multiplier.mdx
index 556b91e60..ebaa2d113 100644
--- a/docs/build-on-base/issue-rwa/apply-a-multiplier.mdx
+++ b/docs/build-on-base/issue-rwa/apply-a-multiplier.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Apply and Verify the Split Multiplier
@@ -57,7 +57,7 @@ base-cast call "$TOKEN_ADDRESS" "scaledBalanceOf(address)(uint256)" "$HOLDER" --
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
`multiplier()` returns `2e18`; raw balances remain unchanged while scaled balances double.
diff --git a/docs/build-on-base/issue-rwa/cancel-blocked-units.mdx b/docs/build-on-base/issue-rwa/cancel-blocked-units.mdx
index a87b40c10..8ac651ea1 100644
--- a/docs/build-on-base/issue-rwa/cancel-blocked-units.mdx
+++ b/docs/build-on-base/issue-rwa/cancel-blocked-units.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Cancel and Verify Blocked Shares
@@ -53,7 +53,7 @@ base-cast call "$TOKEN_ADDRESS" "balanceOf(address)(uint256)" "$BLOCKED_HOLDER"
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
The holder balance and total supply each fall by 100 EXM.
diff --git a/docs/build-on-base/issue-rwa/create-an-asset-token.mdx b/docs/build-on-base/issue-rwa/create-an-asset-token.mdx
index 754b4ba7c..7f605f403 100644
--- a/docs/build-on-base/issue-rwa/create-an-asset-token.mdx
+++ b/docs/build-on-base/issue-rwa/create-an-asset-token.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Create and Verify the Stock Token
@@ -71,7 +71,7 @@ export async function createStockToken() {
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
The factory emits `B20Created` for the Asset variant and returns the deterministic token address.
diff --git a/docs/build-on-base/issue-rwa/issue-units.mdx b/docs/build-on-base/issue-rwa/issue-units.mdx
index 972ef77b8..9241081f7 100644
--- a/docs/build-on-base/issue-rwa/issue-units.mdx
+++ b/docs/build-on-base/issue-rwa/issue-units.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Issue and Verify the Shares
@@ -60,7 +60,7 @@ base-cast call "$TOKEN_ADDRESS" "balanceOf(address)(uint256)" "$ALICE" --rpc-url
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
Alice holds 600 EXM and Bob holds 400 EXM after the all-or-nothing batch.
diff --git a/docs/build-on-base/issue-rwa/pause-transfers.mdx b/docs/build-on-base/issue-rwa/pause-transfers.mdx
index 9b464a2d7..7b1b29b5d 100644
--- a/docs/build-on-base/issue-rwa/pause-transfers.mdx
+++ b/docs/build-on-base/issue-rwa/pause-transfers.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Pause and Resume Stock Transfers
@@ -49,7 +49,7 @@ base-cast send "$TOKEN_ADDRESS" "unpause(uint8[])" "[0]" \
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
Transfer pause state changes without pausing mint or burn.
diff --git a/docs/build-on-base/issue-rwa/restrict-eligible-holders.mdx b/docs/build-on-base/issue-rwa/restrict-eligible-holders.mdx
index de2e3ed6a..7cc9a82b1 100644
--- a/docs/build-on-base/issue-rwa/restrict-eligible-holders.mdx
+++ b/docs/build-on-base/issue-rwa/restrict-eligible-holders.mdx
@@ -20,7 +20,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asset-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Create and Bind the Eligibility Policy
@@ -67,7 +67,7 @@ done
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token) for the complete interface, roles, and policies.
The same allowlist policy gates mint recipients, transfer senders, and transfer recipients.
diff --git a/docs/build-on-base/issue-stablecoins/block-an-account.mdx b/docs/build-on-base/issue-stablecoins/block-an-account.mdx
index 6281aacd1..d5e27684b 100644
--- a/docs/build-on-base/issue-stablecoins/block-an-account.mdx
+++ b/docs/build-on-base/issue-stablecoins/block-an-account.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Block and Verify an Account
@@ -64,7 +64,7 @@ base-cast call "$POLICY_REGISTRY" "isAuthorized(uint64,address)(bool)" \
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
`isAuthorized(policyId, account)` returns `false` while the account is blocked.
diff --git a/docs/build-on-base/issue-stablecoins/burn-supply.mdx b/docs/build-on-base/issue-stablecoins/burn-supply.mdx
index c191840ed..beafaff0b 100644
--- a/docs/build-on-base/issue-stablecoins/burn-supply.mdx
+++ b/docs/build-on-base/issue-stablecoins/burn-supply.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Burn and Verify Supply
@@ -54,7 +54,7 @@ base-cast call "$TOKEN_ADDRESS" "totalSupply()(uint256)" --rpc-url "$RPC_URL"
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
`totalSupply()` decreases by exactly 400 MUSD.
diff --git a/docs/build-on-base/issue-stablecoins/issue-your-stablecoin.mdx b/docs/build-on-base/issue-stablecoins/issue-your-stablecoin.mdx
index 89d6794b8..751c9851a 100644
--- a/docs/build-on-base/issue-stablecoins/issue-your-stablecoin.mdx
+++ b/docs/build-on-base/issue-stablecoins/issue-your-stablecoin.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Create and Verify the Stablecoin
@@ -84,7 +84,7 @@ export async function createStablecoin() {
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
The factory emits `B20Created`, and the returned token address is ready for minting.
diff --git a/docs/build-on-base/issue-stablecoins/mint-supply.mdx b/docs/build-on-base/issue-stablecoins/mint-supply.mdx
index ac4de9780..ad06d98f6 100644
--- a/docs/build-on-base/issue-stablecoins/mint-supply.mdx
+++ b/docs/build-on-base/issue-stablecoins/mint-supply.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Mint and Verify Supply
@@ -53,7 +53,7 @@ base-cast call "$TOKEN_ADDRESS" "balanceOf(address)(uint256)" "$HOLDER" --rpc-ur
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
The holder balance increases by `1,000,000,000` base units, or 1,000 MUSD.
diff --git a/docs/build-on-base/issue-stablecoins/pause-activity.mdx b/docs/build-on-base/issue-stablecoins/pause-activity.mdx
index b91461251..a1eea085a 100644
--- a/docs/build-on-base/issue-stablecoins/pause-activity.mdx
+++ b/docs/build-on-base/issue-stablecoins/pause-activity.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Pause and Resume Transfers
@@ -51,7 +51,7 @@ base-cast send "$TOKEN_ADDRESS" "unpause(uint8[])" "[0]" \
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
`isPaused(0)` returns `true` after pausing and `false` after resuming.
diff --git a/docs/build-on-base/issue-stablecoins/reconcile-with-memos.mdx b/docs/build-on-base/issue-stablecoins/reconcile-with-memos.mdx
index e28faa522..7045913e8 100644
--- a/docs/build-on-base/issue-stablecoins/reconcile-with-memos.mdx
+++ b/docs/build-on-base/issue-stablecoins/reconcile-with-memos.mdx
@@ -19,7 +19,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Attach and Read an Invoice Memo
@@ -52,7 +52,7 @@ base-cast receipt "$TX" --rpc-url "$RPC_URL"
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
The receipt contains `Transfer` followed immediately by `Memo`, with `invoice-8842` encoded as `bytes32`.
diff --git a/docs/build-on-base/issue-stablecoins/recover-funds.mdx b/docs/build-on-base/issue-stablecoins/recover-funds.mdx
index a12f65040..450046d24 100644
--- a/docs/build-on-base/issue-stablecoins/recover-funds.mdx
+++ b/docs/build-on-base/issue-stablecoins/recover-funds.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Burn and Reissue the Blocked Balance
@@ -56,7 +56,7 @@ base-cast call "$TOKEN_ADDRESS" "balanceOf(address)(uint256)" "$REPLACEMENT" --r
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
The blocked balance falls and the replacement address receives the same amount, leaving circulating supply unchanged.
diff --git a/docs/build-on-base/issue-stablecoins/restrict-who-can-hold.mdx b/docs/build-on-base/issue-stablecoins/restrict-who-can-hold.mdx
index 92c49f5a0..f59035f15 100644
--- a/docs/build-on-base/issue-stablecoins/restrict-who-can-hold.mdx
+++ b/docs/build-on-base/issue-stablecoins/restrict-who-can-hold.mdx
@@ -17,7 +17,7 @@ The demo uses a local browser-generated account to submit real transactions on *
-New to B20? See the [B20 Token Standard](/specifications/b20/launch-a-b20-token) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
+New to B20? See the [B20 Token Standard](/specifications/b20/specification-overview) for the concepts and a full launch walkthrough. These samples target `base-std@v1.0.0`, `viem@2.55.11`, and Base Foundry `v1.1.1`.
## Create and Bind a Holder Allowlist
@@ -73,7 +73,7 @@ done
```
-See the [B20 token standard](/specifications/b20/launch-a-b20-token) for the complete interface, roles, and policies.
+See the [B20 token standard](/specifications/b20/specification-overview) for the complete interface, roles, and policies.
The token returns the new policy ID for both transfer policy scopes.
diff --git a/docs/build-on-base/test-on-vibenet.mdx b/docs/build-on-base/test-on-vibenet.mdx
index 54fb5c052..15bf5aa99 100644
--- a/docs/build-on-base/test-on-vibenet.mdx
+++ b/docs/build-on-base/test-on-vibenet.mdx
@@ -53,7 +53,7 @@ curl -X POST https://api.vibes.base.org/api/vibenet/faucet/drip \
## What's Available Now
-
+
Base's native token standard: roles, supply caps, policy gating, and memos built into the chain.
@@ -70,7 +70,7 @@ curl -X POST https://api.vibes.base.org/api/vibenet/faucet/drip \
Add Vibenet to your wallet and development environment.
-
+
Deploy a token on Vibenet with one factory call.
diff --git a/docs/docs.json b/docs/docs.json
index 93b063f0d..52dd7c64a 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -228,7 +228,6 @@
"group": "B20",
"pages": [
"specifications/b20/specification-overview",
- "specifications/b20/launch-a-b20-token",
"specifications/b20/tokenized-stocks-on-base",
{
"group": "Reference",
@@ -936,7 +935,11 @@
},
{
"source": "/get-started/launch-b20-token",
- "destination": "/specifications/b20/launch-a-b20-token"
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
+ },
+ {
+ "source": "/specifications/b20/launch-a-b20-token",
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
},
{
"source": "/build-on-base/issue-stablecoins",
@@ -3184,7 +3187,7 @@
},
{
"source": "/cookbook/launch-tokens",
- "destination": "/specifications/b20/launch-a-b20-token"
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
},
{
"source": "/cookbook/onchain-social",
@@ -4076,7 +4079,7 @@
},
{
"source": "/get-started/launch-token",
- "destination": "/specifications/b20/launch-a-b20-token"
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
},
{
"source": "/get-started/learning-resources",
@@ -4096,7 +4099,7 @@
},
{
"source": "/base-chain/specs/upgrades/beryl/b20-playground",
- "destination": "/specifications/b20/launch-a-b20-token"
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
},
{
"source": "/base-chain/security/bug-bounty",
@@ -4240,7 +4243,7 @@
},
{
"source": "/base-chain/network-information/b20-token-standard",
- "destination": "/specifications/b20/launch-a-b20-token"
+ "destination": "/build-on-base/issue-rwa/create-an-asset-token"
},
{
"source": "/base-chain/specs/reference/b20/tokenized-stocks-on-base",
diff --git a/docs/get-started/base-chain.mdx b/docs/get-started/base-chain.mdx
index ef239cbd1..2dfaec5ca 100644
--- a/docs/get-started/base-chain.mdx
+++ b/docs/get-started/base-chain.mdx
@@ -26,7 +26,7 @@ Base is an EVM-compatible chain with its own native primitives, transaction pipe
## Use Chain-Native Primitives
-
+
Use Base's native token standard for cheaper transfers, built-in controls, memos, and asset variants.
diff --git a/docs/llms-full.txt b/docs/llms-full.txt
index 9af404938..1a92e21f5 100644
--- a/docs/llms-full.txt
+++ b/docs/llms-full.txt
@@ -276,8 +276,6 @@ const client = createPublicClient({ chain: base, transport: http() })
- [B20 Specification](https://docs.base.org/specifications/b20/specification-overview): Normative Beryl specification for Base's B20 native token standard, precompile registries, policy scopes, factory, and variants.
-- [B20 Token Standard](https://docs.base.org/specifications/b20/launch-a-b20-token): 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.
-
- [Tokenized Stocks on Base](https://docs.base.org/specifications/b20/tokenized-stocks-on-base): Technical guide to integrating tokenized stocks on Base, including B20 contracts, multipliers, compliance policies, and price feeds.
##### Reference
diff --git a/docs/llms.txt b/docs/llms.txt
index 690f01388..5bfff0be4 100644
--- a/docs/llms.txt
+++ b/docs/llms.txt
@@ -210,8 +210,6 @@
- [B20 Specification](https://docs.base.org/specifications/b20/specification-overview): Normative Beryl specification for Base's B20 native token standard, precompile registries, policy scopes, factory, and variants.
-- [B20 Token Standard](https://docs.base.org/specifications/b20/launch-a-b20-token): 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.
-
- [Tokenized Stocks on Base](https://docs.base.org/specifications/b20/tokenized-stocks-on-base): Technical guide to integrating tokenized stocks on Base, including B20 contracts, multipliers, compliance policies, and price feeds.
##### Reference
diff --git a/docs/sdks/base-anvil.mdx b/docs/sdks/base-anvil.mdx
index 789b3636c..d6a3c74f1 100644
--- a/docs/sdks/base-anvil.mdx
+++ b/docs/sdks/base-anvil.mdx
@@ -3,7 +3,7 @@ title: "base-anvil CLI"
description: "Base's Foundry build: install base-forge, base-cast, and base-anvil, which teach Foundry about Base's native precompiles."
---
-[base-anvil](https://github.com/base/base-anvil) is a fork of Foundry that teaches `forge`, `cast`, `anvil`, and `chisel` about Base's native precompiles — the [B20 token factory](/specifications/b20/launch-a-b20-token), B20 tokens, the PolicyRegistry, and the ActivationRegistry. Stock Foundry can't reach these precompile addresses and aborts with `call to non-contract address`; base-anvil registers them into its EVM so you can build and test Base apps locally.
+[base-anvil](https://github.com/base/base-anvil) is a fork of Foundry that teaches `forge`, `cast`, `anvil`, and `chisel` about Base's native precompiles — the [B20 token factory](/build-on-base/issue-rwa/create-an-asset-token), B20 tokens, the PolicyRegistry, and the ActivationRegistry. Stock Foundry can't reach these precompile addresses and aborts with `call to non-contract address`; base-anvil registers them into its EVM so you can build and test Base apps locally.
## Install
@@ -43,7 +43,7 @@ Everything else is inherited from upstream [Foundry](https://book.getfoundry.sh/
Deploy a contract to Base Sepolia with base-anvil, end to end.
-
+
Launch Base's native token standard from the command line.
diff --git a/docs/specifications/b20/launch-a-b20-token.mdx b/docs/specifications/b20/launch-a-b20-token.mdx
deleted file mode 100644
index bf602e8e8..000000000
--- a/docs/specifications/b20/launch-a-b20-token.mdx
+++ /dev/null
@@ -1,384 +0,0 @@
----
-title: "B20 Token Standard"
-sidebarTitle: "Launch a B20 Token"
-description: "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**](/specifications/b20/specification-overview#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](/specifications/b20/specification-overview#roles-model), optional [supply caps](/specifications/b20/specification-overview#supply-cap), [policy-gated transfers and mints](/specifications/b20/specification-overview#policy-integration), [granular pause](/specifications/b20/specification-overview#pause), [memos](/specifications/b20/specification-overview#memos), and ERC-2612 `permit`. For the complete ABIs and precompile behavior, see the [B20 native token standard](/specifications/b20/specification-overview) 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/request-a-payment#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}
- curl -L https://raw.githubusercontent.com/base/base-anvil/HEAD/foundryup/install | bash
- base-foundryup --install v1.1.1
- ```
-
-
- 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.
-
-
-## Verify the Activation Registry Is Enabled
-
-
-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:
-
-
-```bash Terminal 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
-```
-
-
-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.
-
-
-## Set Up Foundry
-
-```bash Terminal 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}
-base = true
-remappings = [
- "base-std/=lib/base-std/src/",
- "base-std-test/=lib/base-std/test/",
-]
-```
-
-
-The interfaces compile with any Solidity `>=0.8.20 <0.9.0`.
-
-
-## 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
-mkdir b20-viem && cd b20-viem
-npm init -y
-npm install viem@2.55.11
-```
-
-```typescript clients.ts lines wrap expandable
-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
-export RPC_URL="https://sepolia.base.org"
-export PRIVATE_KEY="0x..."
-export ACCOUNT_ADDRESS="0x..."
-```
-
-
-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.
-
-
-The [B20 specification](/specifications/b20/specification-overview) documents the complete interface, ABIs, errors, and events used by these 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).
-
-
-
- | Setting | Value |
- |---|---|
- | RPC URL | `https://mainnet.base.org` |
- | Chain ID | `8453` |
- | Explorer | [basescan.org](https://basescan.org) |
-
- ```bash .env theme={null}
- export RPC_URL="https://mainnet.base.org"
- export PRIVATE_KEY="0x..."
- export ACCOUNT_ADDRESS="0x..."
- export CHAIN_ID="8453"
- ```
-
-
- If you don't have an account, `base-cast wallet new` prints a fresh address and key.
-
-
- Confirm your account has ETH for gas:
-
- ```bash Terminal theme={null}
- source .env
- base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
- ```
-
-
- The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
-
-
-
- | 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}
- export RPC_URL="https://sepolia.base.org"
- export PRIVATE_KEY="0x..."
- export ACCOUNT_ADDRESS="0x..."
- export CHAIN_ID="84532"
- ```
-
-
- If you don't have an account, `base-cast wallet new` prints a fresh address and key.
-
-
- Request testnet ETH from the faucet, then confirm it arrived:
-
- ```bash Terminal theme={null}
- source .env
- base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
- ```
-
-
- The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
-
-
-
- | 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}
- export RPC_URL="https://rpc.vibes.base.org/"
- export PRIVATE_KEY="0x..."
- export ACCOUNT_ADDRESS="0x..."
- export CHAIN_ID="84538453"
- ```
-
-
- If you don't have an account, `base-cast wallet new` prints a fresh address and key.
-
-
- Request testnet ETH from the faucet, then confirm it arrived:
-
- ```bash Terminal theme={null}
- source .env
- base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
- ```
-
-
- The command prints a non-zero balance. This account signs the deploy and the mint, and receives the minted supply.
-
-
-
- Start a local Base node in a separate terminal:
-
- ```bash Terminal theme={null}
- base-anvil
- ```
-
- Create a `.env` using anvil's pre-funded account #0:
-
- ```bash .env 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}
- source .env
- base-cast balance $ACCOUNT_ADDRESS --rpc-url $RPC_URL
- ```
-
-
- The command prints `10000000000000000000000` — anvil's default pre-funded balance of 10,000 ETH.
-
-
-
-
-## 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.
-
-
-
- Use `B20FactoryLib` to encode `params` and `initCalls`. Create `script/CreateToken.s.sol`:
-
- ```solidity script/CreateToken.s.sol highlight={26} 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);
- }
- }
- ```
-
-
- **Encode with `B20FactoryLib`.** The native implementation rejects non-canonical calldata with `AbiDecodeFailed`; the helpers produce canonical encoding.
-
-
-
- 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`).
-
-
-
- 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}
- 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.
-
-
-
-
- ```bash Terminal 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...`:
-
-
- 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.
-
-
- ```text Output theme={null}
- == Logs ==
- B20 token created at: 0xB200...
- ```
-
-
-
- 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}
- 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.
-
-
- 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.
-
-
-
-
-## Mint and Verify
-
-Minting requires `MINT_ROLE`, which `initCalls` granted to your account.
-
-
-
- ```bash Terminal 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)`.
-
-
-
- ```bash Terminal theme={null}
- base-cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL
- # 1000000000000000000000 [1e21]
- ```
-
-
- The token now holds minted supply onchain. Search `$TOKEN_ADDRESS` in the explorer to view it.
-
-
-
-
-## 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/request-a-payment#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](/specifications/b20/specification-overview).
diff --git a/docs/specifications/b20/specification-overview.mdx b/docs/specifications/b20/specification-overview.mdx
index 9388aee90..ae44d7610 100644
--- a/docs/specifications/b20/specification-overview.mdx
+++ b/docs/specifications/b20/specification-overview.mdx
@@ -220,6 +220,6 @@ Stablecoin tokens add `currency()`, set once at creation. The value must contain
## Developer Documentation
-- [B20 token standard](/specifications/b20/launch-a-b20-token): conceptual introduction to B20 and when to use it
+- [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token): conceptual introduction to B20 and when to use it
- [Generated reference](/specifications/b20/reference/interfaces/ib20)
- [B20 changelog](/specifications/b20/changelog)
diff --git a/docs/specifications/b20/tokenized-stocks-on-base.mdx b/docs/specifications/b20/tokenized-stocks-on-base.mdx
index d22432712..4e00e59be 100644
--- a/docs/specifications/b20/tokenized-stocks-on-base.mdx
+++ b/docs/specifications/b20/tokenized-stocks-on-base.mdx
@@ -3,7 +3,7 @@ title: "Tokenized Stocks on Base"
description: "Technical guide to integrating tokenized stocks on Base, including B20 contracts, multipliers, compliance policies, and price feeds."
---
-Tokenized Stocks on Base are built on the Base-native token standard, [B20](/specifications/b20/launch-a-b20-token). B20 is an extension of the ERC-20 token standard and is intended to be asset-agnostic, with tokenized stocks being one of several possible use cases.
+Tokenized Stocks on Base are built on the Base-native token standard, [B20](/build-on-base/issue-rwa/create-an-asset-token). B20 is an extension of the ERC-20 token standard and is intended to be asset-agnostic, with tokenized stocks being one of several possible use cases.
Product-specific details about Tokenized Stocks on Base can be found within [coinbase.com/tokenize](https://coinbase.com/tokenize).
diff --git a/docs/specifications/reference/smart-contracts.mdx b/docs/specifications/reference/smart-contracts.mdx
index 460ed6062..d8f9fe3ab 100644
--- a/docs/specifications/reference/smart-contracts.mdx
+++ b/docs/specifications/reference/smart-contracts.mdx
@@ -7,12 +7,12 @@ Base is an EVM-equivalent Ethereum L2. Smart contracts are written in Solidity o
## How Base Differs
-Base adds **native precompiles** at fixed addresses that extend the EVM with functionality built into the node itself, such as the [B20 token standard](/specifications/b20/launch-a-b20-token), the PolicyRegistry, and the B20 Factory. These addresses hold no contract bytecode, so standard Foundry (`forge`, `cast`, `anvil`) cannot simulate calls to them and aborts with `call to non-contract address`.
+Base adds **native precompiles** at fixed addresses that extend the EVM with functionality built into the node itself, such as the [B20 token standard](/build-on-base/issue-rwa/create-an-asset-token), the PolicyRegistry, and the B20 Factory. These addresses hold no contract bytecode, so standard Foundry (`forge`, `cast`, `anvil`) cannot simulate calls to them and aborts with `call to non-contract address`.
Base publishes [base-anvil](https://github.com/base/base-anvil), a fork of Foundry that registers Base's precompiles into its EVM. It installs `base-forge`, `base-cast`, and `base-anvil` alongside your existing Foundry toolchain without overwriting it, so contracts behave the same locally as they do on Base.
-Use Base's Foundry build (`base-forge`, `base-cast`, `base-anvil`) rather than standard Foundry (`forge`, `cast`, `anvil`) for Base development. A contract that reads or writes a Base precompile — for example one that deploys or calls a [B20 token](/specifications/b20/launch-a-b20-token) — will fail under standard Foundry.
+Use Base's Foundry build (`base-forge`, `base-cast`, `base-anvil`) rather than standard Foundry (`forge`, `cast`, `anvil`) for Base development. A contract that reads or writes a Base precompile — for example one that deploys or calls a [B20 token](/build-on-base/issue-rwa/create-an-asset-token) — will fail under standard Foundry.
## Set Up Your Environment
@@ -115,7 +115,7 @@ This returns the initial value of the `Counter` contract's `number` variable, `0
## Next Steps
-
+
Launch Base's native token standard, built in at the precompile level.
diff --git a/docs/upgrades/beryl/b20.mdx b/docs/upgrades/beryl/b20.mdx
index 0dbaac5c8..092b46ead 100644
--- a/docs/upgrades/beryl/b20.mdx
+++ b/docs/upgrades/beryl/b20.mdx
@@ -5,10 +5,10 @@ description: "Learn how B20, Base's native token standard, serves stablecoin iss
B20 is the Base ecosystem's own version of [ERC-20](https://eips.ethereum.org/EIPS/eip-20). It ships with a built-in compliance toolkit: transfer policies, freeze-and-reissue controls, role-based access control, memos, and supply caps. The released Beryl interfaces are available in [Base Standard Library v1.0.0](https://github.com/base/base-std/tree/v1.0.0).
-To deploy your first B20 token, see the [Launch a B20 token](/specifications/b20/launch-a-b20-token) quickstart.
+To deploy your first B20 token, see the [Launch a B20 token](/build-on-base/issue-rwa/create-an-asset-token) quickstart.
-[Verify the Activation Registry is enabled](/specifications/b20/launch-a-b20-token#verify-the-activation-registry-is-enabled) before attempting to deploy.
+[Verify the Activation Registry is enabled](/build-on-base/issue-rwa/create-an-asset-token) before attempting to deploy.
B20 supports two variants:
diff --git a/scripts/sync-from-base-std/__tests__/base-std-routing.test.mjs b/scripts/sync-from-base-std/__tests__/base-std-routing.test.mjs
index c1f3daa9c..93c4a203e 100644
--- a/scripts/sync-from-base-std/__tests__/base-std-routing.test.mjs
+++ b/scripts/sync-from-base-std/__tests__/base-std-routing.test.mjs
@@ -22,7 +22,7 @@ const REPO_ROOT = path.resolve(
const B20_REFERENCE_ROOT = "docs/specifications/b20";
const B20_MANUAL_UPDATE_PAGES = [
"docs/specifications/b20/specification-overview.mdx",
- "docs/specifications/b20/launch-a-b20-token.mdx",
+ "docs/build-on-base/issue-rwa/create-an-asset-token.mdx",
"docs/build-on-base/accept-payments/request-a-payment.mdx",
];
@@ -280,3 +280,29 @@ test("buildProvenanceComment cannot inject a second HTML comment boundary", () =
assert.match(comment, /\n-->$/);
assert.doesNotMatch(body, /[<>]/);
});
+
+import { validateMdx } from "../index.mjs";
+
+test("validateMdx: components already on the page or defined as snippets are allowed", () => {
+ const page = "---\ntitle: x\n---\n\n\n\nBody.\n";
+ const routes = new Set();
+ // unknown component, not on the page, not a snippet → rejected
+ assert.match(validateMdx(page, "docs/a.mdx", routes) || "", /not registered.*StablecoinDemo/);
+ // same component already used by the page being edited → allowed
+ assert.equal(validateMdx(page, "docs/a.mdx", routes, { current: page }), null);
+ // defined as a snippet → allowed even on a page that did not use it before
+ assert.equal(validateMdx(page, "docs/a.mdx", routes, { current: "---\ntitle: x\n---\n", snippetComponents: new Set(["StablecoinDemo"]) }), null);
+ // a genuinely unknown component is still rejected
+ assert.match(validateMdx(page.replace("StablecoinDemo", "Nope"), "docs/a.mdx", routes, { current: page }) || "", /Nope/);
+});
+
+import { loadKnownRoutes } from "../index.mjs";
+
+test("loadKnownRoutes: index pages are reachable at their directory route", async () => {
+ const routes = await loadKnownRoutes();
+ assert.ok(routes.has("/specifications/b20/reference/interfaces/ib20/index"));
+ assert.ok(routes.has("/specifications/b20/reference/interfaces/ib20"), "directory route for index.mdx");
+ // and the validator accepts a link to it
+ const page = "---\ntitle: x\n---\n\nSee [IB20](/specifications/b20/reference/interfaces/ib20).\n";
+ assert.equal(validateMdx(page, "docs/a.mdx", routes, { current: page }), null);
+});
diff --git a/scripts/sync-from-base-std/__tests__/release-prompts.test.mjs b/scripts/sync-from-base-std/__tests__/release-prompts.test.mjs
index 6d9fde9ca..c8c4224ca 100644
--- a/scripts/sync-from-base-std/__tests__/release-prompts.test.mjs
+++ b/scripts/sync-from-base-std/__tests__/release-prompts.test.mjs
@@ -134,9 +134,3 @@ test("codeChangePrompt: create=true adds the new-page instruction; diff path unc
assert.match(ref, /\n\+natspec/);
assert.match(ref, /role: function-reference/);
});
-
-test("releaseSelectionPrompt: event_description replaces the release header for code-change selection", () => {
- const p = _rsp({ event_description: "A code change landed on base/base-std@abc1234: fix seize.", candidates: [{ path: "docs/a.mdx" }] });
- assert.match(p, /A code change landed on base\/base-std@abc1234/);
- assert.doesNotMatch(p, /New tag:/);
-});
diff --git a/scripts/sync-from-base-std/__tests__/release-utils.test.mjs b/scripts/sync-from-base-std/__tests__/release-utils.test.mjs
index 54be0c8b7..3fd225446 100644
--- a/scripts/sync-from-base-std/__tests__/release-utils.test.mjs
+++ b/scripts/sync-from-base-std/__tests__/release-utils.test.mjs
@@ -207,7 +207,7 @@ test("manifestEntrySymbols: qualified subject, its parts, and rename before/afte
after: "seizeWithMemo(address from, address to)",
});
assert.equal(syms[0], "IB20.seizeWithMemo");
- assert.ok(syms.includes("IB20"));
+ assert.ok(!syms.includes("IB20"), "the qualifier is on every page of the interface — not evidence");
assert.ok(syms.includes("seizeWithMemo"));
assert.ok(syms.includes("transferFromBlockedWithMemo"));
// generic tokens never count as evidence
@@ -432,3 +432,92 @@ test("firstHeading: first H1 or empty", () => {
assert.equal(firstHeading("intro\n# Seize surface + burnBlocked deprecation\n## Summary"), "Seize surface + burnBlocked deprecation");
assert.equal(firstHeading("no heading"), "");
});
+
+test("manifestForPage: requireSymbolMatch ignores the shared-file rule (one-callable pages)", () => {
+ const manifest = [
+ { file: "src/interfaces/IB20.sol", kind: "other", subject: "IB20.seizeWithMemo", summary: "a" },
+ { file: "src/interfaces/IB20.sol", kind: "other", subject: "IB20.SEIZE_HOLDER_POLICY", summary: "b" },
+ ];
+ const transferPage = "## Signature\n`function transfer(address to, uint256 amount)`";
+ const seizePage = "## Policy Interaction\nChecks `SEIZE_HOLDER_POLICY` before `seizeWithMemo` proceeds.";
+ const src = ["src/interfaces/IB20.sol"];
+ assert.equal(manifestForPage(manifest, { sourceFiles: src, pageContent: transferPage }).length, 2, "file rule alone attaches everything");
+ assert.equal(manifestForPage(manifest, { sourceFiles: src, pageContent: transferPage, requireSymbolMatch: true }).length, 0);
+ assert.equal(manifestForPage(manifest, { sourceFiles: src, pageContent: seizePage, requireSymbolMatch: true }).length, 2);
+});
+
+test("routingSymbols: before/after contribute only the identifiers that changed", () => {
+ const syms = routingSymbols([
+ { file: "src/lib/B20Constants.sol", kind: "field_renamed", subject: "B20Constants.SEIZE_EXEMPT_POLICY",
+ before: 'keccak256("SEIZE_HOLDER_POLICY")', after: 'keccak256("SEIZE_EXEMPT_POLICY")', summary: "" },
+ ]);
+ assert.ok(syms.includes("SEIZE_HOLDER_POLICY"), "old name routes stale pages");
+ assert.ok(syms.includes("SEIZE_EXEMPT_POLICY"));
+ assert.ok(!syms.includes("keccak256"), "shared context is not a routing symbol");
+});
+
+test("manifestEntrySymbols: subject prose and () are ignored; only the identifier path counts", () => {
+ const syms = manifestEntrySymbols({ subject: "IB20.AccountNotSeizable error documentation" });
+ assert.deepEqual(syms, ["IB20.AccountNotSeizable", "AccountNotSeizable"]);
+ const syms2 = manifestEntrySymbols({ subject: "IB20.SEIZE_HOLDER_POLICY()" });
+ assert.deepEqual(syms2, ["IB20.SEIZE_HOLDER_POLICY", "SEIZE_HOLDER_POLICY"]);
+});
+
+test("routingSymbols: prose words in before/after never route; mock/test entries are ignored", () => {
+ const syms = routingSymbols([
+ { file: "src/lib/B20Constants.sol", kind: "field_renamed", subject: "B20Constants.SEIZE_HOLDER_POLICY",
+ before: "internal constant SEIZE_HOLDER_POLICY documented against the member", after: "internal constant SEIZE_EXEMPT_POLICY", summary: "" },
+ { file: "test/lib/mocks/MockB20.sol", kind: "other", subject: "MockB20.policyId() implementation", before: "TRANSFER_SENDER_POLICY", summary: "" },
+ ]);
+ assert.ok(syms.includes("SEIZE_HOLDER_POLICY"));
+ assert.ok(syms.includes("SEIZE_EXEMPT_POLICY"));
+ for (const junk of ["internal", "constant", "documented", "against", "member", "policyId", "TRANSFER_SENDER_POLICY"]) {
+ assert.ok(!syms.includes(junk), `${junk} must not be a routing symbol`);
+ }
+});
+
+// ------------------------------------------------------------- decideCall
+
+import { decideCall, pageTitle, MAX_REGENERABLE_CHARS } from "../release-utils.mjs";
+
+const fn = (title, body = "") => `---\ntitle: "${title}"\n---\n${body}`;
+
+test("decideCall: function page is called only when its own symbol changed", () => {
+ const page = fn("IB20.seizeWithMemo", "## Signature\n`function seizeWithMemo(...)`");
+ assert.equal(decideCall({ role: "function-reference", content: page, diffSlice: "+ /// seizeWithMemo reverts when", symbols: [] }), null);
+ assert.equal(decideCall({ role: "function-reference", content: page, diffSlice: "", symbols: ["seizeWithMemo"] }), null);
+ assert.match(decideCall({ role: "function-reference", content: page, diffSlice: "+ // transfer changed", symbols: ["transfer"] }), /own symbol seizeWithMemo/);
+});
+
+test("decideCall: interface index is called on inventory changes or a symbol on the page", () => {
+ const page = fn("IB20 Reference", "| [`SEIZE_HOLDER_POLICY`](/x) | `0xb2` |");
+ assert.equal(decideCall({ role: "interface-index", content: page, manifest: [{ kind: "field_renamed", subject: "IB20.SEIZE_EXEMPT_POLICY" }], symbols: [] }), null);
+ assert.equal(decideCall({ role: "interface-index", content: page, manifest: [{ kind: "other", subject: "IB20.seizeWithMemo" }], symbols: ["SEIZE_HOLDER_POLICY"] }), null);
+ assert.match(decideCall({ role: "interface-index", content: page, manifest: [{ kind: "other", subject: "IB20.seizeWithMemo" }], symbols: ["seizeWithMemo"] }), /no member of IB20/);
+ assert.match(decideCall({ role: "interface-index", content: page, manifest: [{ kind: "field_added", subject: "IPolicyRegistry.foo" }], symbols: [] }), /no member of IB20/);
+});
+
+test("decideCall: shared reference and guides need a changed symbol in a code span; no symbols means call", () => {
+ const page = fn("Errors", "| error | `AccountNotSeizable` |");
+ assert.equal(decideCall({ role: "shared-reference", content: page, symbols: ["AccountNotSeizable"] }), null);
+ assert.match(decideCall({ role: "guide", content: page, symbols: ["seizeWithMemo"] }), /no changed symbol/);
+ assert.equal(decideCall({ role: "guide", content: page, symbols: [] }), null, "no manifest → cannot judge → call");
+ assert.match(decideCall({ role: "guide", content: fn("G", "prose mentions AccountNotSeizable"), symbols: ["AccountNotSeizable"] }), /no changed symbol/, "prose is not a code span");
+});
+
+test("decideCall: pages over the regenerable budget are skipped before the call; changelog roles always run", () => {
+ const big = fn("IB20 Reference", "x".repeat(MAX_REGENERABLE_CHARS + 1));
+ assert.match(decideCall({ role: "interface-index", content: big, manifest: [{ kind: "field_added", subject: "IB20.x" }] }), /exceeds/);
+ assert.equal(decideCall({ role: "changelog-entry", content: fn("E", "short") }), null);
+ assert.equal(decideCall({ role: "changelog-index", content: big }), null, "summary never goes to the model");
+ assert.equal(pageTitle(fn("IB20.transfer")), "IB20.transfer");
+});
+
+import { changedLines } from "../release-utils.mjs";
+
+test("changedLines: keeps +/- lines only, drops headers and context", () => {
+ const diff = "diff --git a/x b/x\n--- a/x\n+++ b/x\n@@ -1,3 +1,3 @@\n context allowance()\n-old seizeWithMemo\n+new seizeWithMemo\n";
+ const out = changedLines(diff);
+ assert.equal(out, "-old seizeWithMemo\n+new seizeWithMemo");
+ assert.ok(!out.includes("allowance"));
+});
diff --git a/scripts/sync-from-base-std/__tests__/validate-safety.test.mjs b/scripts/sync-from-base-std/__tests__/validate-safety.test.mjs
index a8a6da01e..801a9ac90 100644
--- a/scripts/sync-from-base-std/__tests__/validate-safety.test.mjs
+++ b/scripts/sync-from-base-std/__tests__/validate-safety.test.mjs
@@ -21,8 +21,24 @@ import assert from "node:assert/strict";
import {
validateSafety,
extractExternalUrls,
+ stripAuthorAttribution,
} from "../safety.mjs";
+// ------------------------------------------------------ stripAuthorAttribution
+
+test("stripAuthorAttribution: removes standalone author and contributor fields", () => {
+ const out = stripAuthorAttribution(
+ "---\\ntitle: Example\\n---\\n\\nAuthors: Alice Example, Bob Example\\nContributor: Casey Example\\n\\nBody text.\\n",
+ );
+ assert.doesNotMatch(out, /^(Authors?|Contributors?):/im);
+ assert.match(out, /Body text\./);
+});
+
+test("stripAuthorAttribution: preserves prose that mentions authors", () => {
+ const input = "The author of this proposal describes the change.\\n";
+ assert.equal(stripAuthorAttribution(input), input);
+});
+
// ---------------------------------------------------------------- validateSafety
test("validateSafety: rejects