From bd12899dc9f3885624e264bb32cbfd07afd23b52 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 8 Oct 2026 13:59:45 +0100 Subject: [PATCH] docs(assets): document USDC on Base, B20 storage slots, and issuer interop --- .../network-information/ecosystem-bridges.mdx | 25 ++++++ .../issue-rwa/create-an-asset-token.mdx | 2 + docs/get-started/issue-stablecoins.mdx | 17 ++++ docs/sdks/tokenized-stocks/overview.mdx | 2 + .../predicates-and-safety.mdx | 77 ++++++++++++++++++- .../reference/smart-contracts.mdx | 6 ++ 6 files changed, 128 insertions(+), 1 deletion(-) diff --git a/docs/base-chain/network-information/ecosystem-bridges.mdx b/docs/base-chain/network-information/ecosystem-bridges.mdx index c8b269d91..4d22dccb8 100644 --- a/docs/base-chain/network-information/ecosystem-bridges.mdx +++ b/docs/base-chain/network-information/ecosystem-bridges.mdx @@ -18,6 +18,31 @@ Move assets to and from Base using the routes below. Pick the one that matches w If you hold funds in a Coinbase account, you can withdraw many assets straight onto the Base network — select **Base** as the network when withdrawing, no bridge required. This is often the fastest path for USDC and ETH. +## USDC on Base + +USDC on Base is issued natively by Circle. Use the native USDC contract for new integrations. Circle publishes the canonical address for every network on its [USDC contract addresses](https://developers.circle.com/stablecoins/usdc-contract-addresses) page. + +| Token | Network | Address | Issuer | +|---|---|---|---| +| USDC | Base Mainnet | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | Circle (native) | +| USDC | Base Sepolia | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | Circle (native) | +| USDbC (USD Base Coin) | Base Mainnet | `0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA` | Bridged from Ethereum | + +USDbC is the earlier bridged form of USDC. It is a Standard Bridge token: Ethereum USDC (`0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`) is locked on L1 and USDbC is minted on Base by the `L2StandardBridge` predeploy (`0x4200000000000000000000000000000000000010`). Circle does not issue USDbC, and it is a separate token from native USDC. To move from USDbC to native USDC, swap it onchain, or withdraw it to Ethereum through the Standard Bridge. The [Superchain token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/BridgedUSDC/data.json) records the USDbC mapping. + +To move native USDC between Base and other networks, use Circle's [Cross-Chain Transfer Protocol (CCTP)](https://developers.circle.com/cctp). CCTP burns USDC on the source chain and mints native USDC on the destination chain, so no bridged representation is created. Base is CCTP domain `6`. For the list of supported chains, see [CCTP supported blockchains](https://developers.circle.com/cctp/cctp-supported-blockchains). + +### Other Stablecoins + +Circle also issues EURC, a euro stablecoin, natively on Base: + +| Token | Network | Address | Issuer | +|---|---|---|---| +| EURC | Base Mainnet | `0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42` | Circle (native) | +| EURC | Base Sepolia | `0x808456652fdb597867f38412077A9182bf77359F` | Circle (native) | + +Confirm addresses on the issuer's own page ([EURC contract addresses](https://developers.circle.com/stablecoins/eurc-contract-addresses)) before you integrate. Stablecoins created with the [B20 Stablecoin variant](/get-started/issue-stablecoins) carry a self-declared `currency()` code; B20 checks the format of that code, not the issuer or its reserves. + ## From Ethereum Bridge ETH and supported ERC-20s between Ethereum mainnet (L1) and Base. Both providers support mainnet and Sepolia testnet. 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 a1b4c796c..5ca814d03 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 @@ -27,6 +27,8 @@ New to B20? See the [B20 Token Standard](/build-on-base/issue-rwa/create-an-asse Choose the **Asset** variant when you need configurable decimals, announcements, a scheduled UI multiplier, extra metadata, or `batchMint`. Asset decimals must be in the range `[6, 18]`; values outside that range revert `InvalidDecimals`. The type is sealed into the token address at creation and cannot change. +[`createB20`](/specifications/b20/reference/interfaces/ib20-factory/create-b20) requires no role or allowlist at the protocol level. Any account can create an Asset token once the B20 Asset variant is active in the [Activation Registry](/specifications/b20/reference/interfaces/i-activation-registry/is-activated), and the variant is active on Base Mainnet. The Factory keeps no access to the token after creation; the issuer configures and controls it. + {/* sample: stock-create-ts */} ```typescript TypeScript lines wrap expandable highlight={15} diff --git a/docs/get-started/issue-stablecoins.mdx b/docs/get-started/issue-stablecoins.mdx index fd72c3088..59110baf8 100644 --- a/docs/get-started/issue-stablecoins.mdx +++ b/docs/get-started/issue-stablecoins.mdx @@ -16,6 +16,23 @@ Issue a fiat-backed stablecoin on Base with [B20](/specifications/b20), Base's n The demo uses a local browser-generated account to submit real transactions on **Base Vibenet**. If Vibenet or its B20 features are unavailable, it automatically switches to an illustrative offline version. +## Network Status + +B20 shipped in the [Beryl upgrade](/upgrades/beryl/overview). The B20 Stablecoin and Asset variants are active on Base Mainnet. Before you deploy on any network, confirm the variant is active with [`isActivated`](/specifications/b20/reference/interfaces/i-activation-registry/is-activated) on the Activation Registry. + +[`createB20`](/specifications/b20/reference/interfaces/ib20-factory/create-b20) requires no role or allowlist. Any account can create a stablecoin once the variant is active, and the Factory keeps no access to the token after creation. + +## Integrate Stablecoins From Other Issuers + +Wallets, apps, and payment providers that hold or move stablecoins issued by others work with the same interface for every issuer: + +- **One integration.** Every B20 stablecoin exposes the same ERC-20 surface and `IB20Stablecoin` interface, so code written for one issuer's token works for any other. +- **Per-token rules.** Each token enforces its own issuer's [policies](/specifications/b20/concepts/policies) on the sender, receiver, and transfer executor. A transfer of one issuer's token succeeds only if the accounts pass that token's policies. Call `isAuthorized` on the Policy Registry to check an account before you send. +- **Shared compliance lists.** Policies live in the Policy Registry, not on the token, so one policy can back many tokens. Issuers that agree on a list can attach the same policy ID. +- **Conversion and redemption.** B20 has no protocol-level conversion between stablecoins from different issuers. Each token is a separate asset: convert through an onchain swap or a venue that supports both tokens. Fiat redemption happens offchain with each issuer, which then [burns the redeemed supply](/build-on-base/issue-stablecoins/burn-supply). + +KYC and sanctions screening happen offchain. An issuer or its compliance operator applies the result onchain by adding accounts to an allowlist or blocklist policy. See [Restrict Who Can Hold It](/build-on-base/issue-stablecoins/restrict-who-can-hold) and [Block an Account](/build-on-base/issue-stablecoins/block-an-account). + ## Guides diff --git a/docs/sdks/tokenized-stocks/overview.mdx b/docs/sdks/tokenized-stocks/overview.mdx index 281cc1c58..b06f1b38f 100644 --- a/docs/sdks/tokenized-stocks/overview.mdx +++ b/docs/sdks/tokenized-stocks/overview.mdx @@ -10,6 +10,8 @@ Use the public Tokenized Stocks API to retrieve reference data for [Coinbase-iss Coinbase tokenized stocks are available only to persons in eligible jurisdictions outside of the United States. This API provides read-only reference data only; it does not provide trading, execution, issuance, minting, redemption, or custody functionality. The availability of any token or related product may be subject to jurisdictional, eligibility, and compliance requirements. Integrators are responsible for applying applicable restrictions. +The API returns token-level data only. It does not return a wallet's positions, cost basis, trade history, or profit and loss. To show a holder's position, read the token balance onchain and convert it with the current `multiplier` (see [List Tokenized Stocks](/build-on-base/integrate-defi/list-tokenized-stocks)). Cost basis and profit and loss across trading venues must come from your own records or an onchain indexer. + ```text Base URL https://api.coinbase.com/v1/tokenized-stocks ``` diff --git a/docs/specifications/build-transaction/predicates-and-safety.mdx b/docs/specifications/build-transaction/predicates-and-safety.mdx index 52bc1fc60..314d059e3 100644 --- a/docs/specifications/build-transaction/predicates-and-safety.mdx +++ b/docs/specifications/build-transaction/predicates-and-safety.mdx @@ -66,4 +66,79 @@ State can change during block building. A condition can become true or false bef Do not use transaction placement as a source of randomness. Do not assume a zero-valued slot makes a CREATE2 address, proxy, or uninitialized contract safe to call. -Validity criteria are not recorded onchain. Do not include secrets in calldata or predicate values. \ No newline at end of file +Validity criteria are not recorded onchain. Do not include secrets in calldata or predicate values. + +## Read B20 and Policy Registry State + +[B20](/specifications/b20) tokens and the Policy Registry run as precompiles, but they keep their state in ordinary account storage. Each B20 token stores its state at the token address. The Policy Registry stores its state at `0x8453000000000000000000000000000000000002`. A `storage` predicate reads these slots the same way it reads any contract's storage. + +The `balance` predicate reads an account's native ETH balance. To gate on a B20 token balance, use a `storage` predicate on the token's balance slot. + +### Slot Derivation + +B20 storage uses [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) namespaces. A top-level field lives at `root + offset`. A mapping value lives at `keccak256(abi.encode(key, baseSlot))`, where `baseSlot` is `root + offset`. For a nested mapping, hash the outer key first, then hash the inner key against the result. Packed fields follow Solidity packing: offset `0` is the lowest-order byte of the word. + +| Namespace | Root slot | Used by | +|---|---|---| +| `base.b20` | `0xc78b71fee795ddd74aff64ea9b2474194c938c3196430e10bb5f01ed48434000` | Every B20 token | +| `base.b20.asset` | `0xfdc6d4552d1286ade4d9facdbf0fb50d2ec9b89a90e104f26fd277585e374b00` | Asset variant fields | +| `base.b20.stablecoin` | `0x35827975a06ca0e9367ea3129b19441d45d0ca58e30b7693f09e73d0943d6200` | Stablecoin variant fields | +| `base.policy_registry` | `0x00503aeb06982fa1fe3151dc68f90b3946c55c449dfd447e49dcaece71ba4a00` | Policy Registry | + +### B20 Token Fields + +Offsets from the `base.b20` root, read at the token address: + +| Offset | Field | Encoding | +|---|---|---| +| `3` | `totalSupply` | `uint256` | +| `4` | `balances[account]` | `mapping(address => uint256)` | +| `5` | `allowances[owner][spender]` | `mapping(address => mapping(address => uint256))` | +| `6` | `roles[role][account]` | `mapping(bytes32 => mapping(address => bool))` | +| `9` | Transfer policy IDs | Packed `uint64` values: sender at bytes 0–7, receiver at bytes 8–15, executor at bytes 16–23 | +| `10` | Mint receiver policy ID | `uint64` at bytes 0–7 | +| `11` | Paused features | Bitmask by `PausableFeature` ordinal: `TRANSFER` bit 0, `MINT` bit 1, `BURN` bit 2, `SEIZE` bit 3 | +| `12` | Supply cap | `uint256` | +| `14` | Seize policy IDs | Packed `uint64` values: seize-exempt at bytes 0–7, seize-receiver at bytes 8–15 | + +### Policy Registry Fields + +Offsets from the `base.policy_registry` root, read at the Policy Registry address. Policy IDs are `uint64` keys. + +| Offset | Field | Encoding | +|---|---|---| +| `0` | `policies[policyId]` | Packed word: admin address in bits 0–159, exists flag in bit 255 | +| `1` | `members[policyId][account]` | `mapping(uint64 => mapping(address => bool))` | +| `2` | `pendingAdmins[policyId]` | `address` | +| `4` | `children[policyId]` | Dynamic `uint64[]` of composite child policy IDs | + +A `members` flag records list membership, not the `isAuthorized` result. On an allowlist, `true` means the account is authorized. On a blocklist, `true` means the account is blocked. Composite (`UNION` and `INTERSECT`) policies have no member set of their own, and a predicate cannot evaluate them. To watch a composite policy, read the member slots of its child policies. An [inverted policy ID](/specifications/b20/concepts/policies#23-inverting-a-policy) has no record of its own; clear bit 63, read the base policy's slots, and invert the condition you check. + +### Example: Wait for an Allowlist Addition + +Compute the `members[policyId][account]` slot with `cast index`, then wait for it to become `1`: + +```bash Compute a member slot lines wrap highlight={4} +# members base slot = base.policy_registry root + 1 +MEMBERS=0x00503aeb06982fa1fe3151dc68f90b3946c55c449dfd447e49dcaece71ba4a01 +PER_POLICY=$(cast index uint64 $MEMBERS) +cast index address $PER_POLICY +``` + +```json Allowlist predicate lines wrap expandable highlight={5,7} +{ + "type": "storage", + "params": { + "address": "0x8453000000000000000000000000000000000002", + "slot": "", + "op": "=", + "value": "0x1" + } +} +``` + +To wait until a token's transfers are unpaused, read offset `11` (`0xc78b71fee795ddd74aff64ea9b2474194c938c3196430e10bb5f01ed4843400b`) at the token address with `mask` `0x1` and `value` `0x0`. When you mask a packed field, shift the expected `value` into the same byte position as the mask. + + +The storage layout is part of the precompile implementation. Network upgrades append fields without reordering existing ones (for example, Cobalt added offset `14` to `base.b20`), but check the [B20 changelog](/specifications/b20/changelog) and the [base-std changelog](https://github.com/base/base-std/tree/main/changelog), which records each storage layout change, before you rely on a slot. The [base-std storage layout tests](https://github.com/base/base-std/tree/main/test/unit/storage) are the reference that the node implementation must match. + \ No newline at end of file diff --git a/docs/specifications/reference/smart-contracts.mdx b/docs/specifications/reference/smart-contracts.mdx index d8f9fe3ab..250c930b9 100644 --- a/docs/specifications/reference/smart-contracts.mdx +++ b/docs/specifications/reference/smart-contracts.mdx @@ -5,6 +5,12 @@ description: "How smart contracts work on Base, why Base needs its own Foundry b Base is an EVM-equivalent Ethereum L2. Smart contracts are written in Solidity or Vyper and deploy exactly as they do on Ethereum — the same bytecode, ABIs, and tooling apply. +## Token Standards + +Any token standard implemented as a smart contract deploys and behaves on Base as it does on Ethereum. That includes [ERC-20](https://eips.ethereum.org/EIPS/eip-20), [ERC-721](https://eips.ethereum.org/EIPS/eip-721), [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155), [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) vaults, and permissioned-token standards such as [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643). These are contracts that you or your provider deploy, upgrade, and maintain; the Base node does not implement them. + +[B20](/specifications/b20) is Base's native token standard. It runs as precompiles in the node and keeps full selector and behavior parity with ERC-20, so ERC-20 tooling works with B20 tokens unchanged. B20 logic changes only through [network upgrades](/upgrades/overview), and each change is listed in the [B20 changelog](/specifications/b20/changelog). + ## 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](/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`.