From 64fd250dddb9fddeb2f6a4df7c17d525eaf7bf49 Mon Sep 17 00:00:00 2001 From: sohey Date: Thu, 18 Jun 2026 19:40:51 +0200 Subject: [PATCH 1/2] added spec --- docs/apps/guides/accept-b20-payments.mdx | 60 +++++ docs/docs.json | 4 +- docs/get-started/base.mdx | 2 +- docs/get-started/launch-b20-token.mdx | 286 +++++++++++++++++++++++ 4 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 docs/apps/guides/accept-b20-payments.mdx create mode 100644 docs/get-started/launch-b20-token.mdx diff --git a/docs/apps/guides/accept-b20-payments.mdx b/docs/apps/guides/accept-b20-payments.mdx new file mode 100644 index 000000000..d6dae4e18 --- /dev/null +++ b/docs/apps/guides/accept-b20-payments.mdx @@ -0,0 +1,60 @@ +--- +title: "Accept B20 payments" +description: "Accept B20 token payments in your app and match each transaction to an order with onchain memos." +--- + +B20 is an ERC-20 superset. Standard `transfer`, `transferFrom`, `approve`, `balanceOf`, and ERC-2612 `permit` all work, so an app that accepts ERC-20 tokens accepts B20 with no code changes. + +B20's new features include transfer policies, pausing, supply caps, and memos. This guide uses the memo: `transferWithMemo` works like `transfer`, but also attaches a `bytes32` reference such as an order ID and emits a `Memo` event right after the standard `Transfer`. Read that event to tie each payment to an order. + +## Tag a payment with a memo + +This uses your configured viem `walletClient` and `publicClient`. It reads the token's decimals, sends a payment tagged with an order ID, then reads the memo back from the receipt: + +```js +import { parseUnits, stringToHex, hexToString, parseEventLogs } from 'viem'; + +const TOKEN = '0xB20f...'; // the B20 token you accept +const MERCHANT = '0x...'; // where payments land + +const ABI = [ + { type: 'function', name: 'decimals', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] }, + { type: 'function', name: 'transferWithMemo', stateMutability: 'nonpayable', + inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }, { name: 'memo', type: 'bytes32' }], + outputs: [{ type: 'bool' }] }, + { type: 'event', name: 'Memo', inputs: [ + { name: 'caller', type: 'address', indexed: true }, + { name: 'memo', type: 'bytes32', indexed: true }, + ] }, +]; + +// Read decimals because B20 tokens range from 6 to 18. +const decimals = await publicClient.readContract({ address: TOKEN, abi: ABI, functionName: 'decimals' }); + +// Pay 10 tokens, tagging the transfer with an order ID. +const hash = await walletClient.writeContract({ + address: TOKEN, abi: ABI, functionName: 'transferWithMemo', + args: [MERCHANT, parseUnits('10', decimals), stringToHex('order-42', { size: 32 })], +}); + +// The Memo event carries the order ID back. Read it from the receipt. +const receipt = await publicClient.waitForTransactionReceipt({ hash }); +const [memo] = parseEventLogs({ abi: ABI, logs: receipt.logs, eventName: 'Memo' }); +console.log(hexToString(memo.args.memo, { size: 32 }).replace(/\0+$/, '')); // "order-42" +``` + +To collect with an allowance instead of a direct transfer, use `transferFromWithMemo`. It emits the same `Memo` event. + +## Handle B20-specific reverts + +A B20 transfer can revert where a standard ERC-20 would not. Surface these so a failed payment is visible, not silent: + +- `PolicyForbids`: the sender or recipient is not authorized by the token's transfer policy. Most tokens are open by default, but a regulated issuer can gate transfers with an allowlist or blocklist. +- A paused transfer: the issuer paused the token's `TRANSFER` feature. + +Call `publicClient.simulateContract` with the same arguments before sending. It raises these as typed errors before the user signs, so you can show the reason instead of a failed transaction. + +## Related pages + +- [B20 token standard](/base-chain/specs/upgrades/beryl/b20): the full interface, including memos, policies, pausing, and roles. +- [Launch a B20 Token](/get-started/launch-b20-token): create your own B20 token. \ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json index bc19b3cf8..582fd72da 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -40,6 +40,7 @@ "get-started/resources-for-ai-agents", "get-started/build-app", "get-started/launch-token", + "get-started/launch-b20-token", "get-started/deploy-smart-contracts", "get-started/learning-resources" ] @@ -665,7 +666,8 @@ "group": "Guides", "pages": [ "apps/technical-guides/base-notifications", - "apps/guides/migrate-to-standard-web-app" + "apps/guides/migrate-to-standard-web-app", + "apps/guides/accept-b20-payments" ] }, { diff --git a/docs/get-started/base.mdx b/docs/get-started/base.mdx index d672db9a9..95c53bf98 100644 --- a/docs/get-started/base.mdx +++ b/docs/get-started/base.mdx @@ -20,8 +20,8 @@ mode: "wide"
### Tokens [Launch a Token](/get-started/launch-token) + [Launch a B20 Token](/get-started/launch-b20-token) [Bridge from Solana](/base-chain/quickstart/base-solana-bridge) - [Bridge from Ethereum](/base-chain/network-information/bridges)
diff --git a/docs/get-started/launch-b20-token.mdx b/docs/get-started/launch-b20-token.mdx new file mode 100644 index 000000000..2de35e341 --- /dev/null +++ b/docs/get-started/launch-b20-token.mdx @@ -0,0 +1,286 @@ +--- +title: "Launch a B20 Token" +description: "Launch a B20 token on Base by calling the B20 Factory precompile." +--- + +B20 is an ERC-20 superset that runs as a native precompile on Base, 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. + +A standard ERC-20 leaves that logic for you to build, audit, and maintain. With B20, you call the singleton **B20 Factory** to create a token, fully configured, in a single transaction. + +This guide creates an Asset token, mints its initial supply, and verifies the balance onchain. To accept the token as payment in an app, continue with [Accept B20 payments](/apps/guides/accept-b20-payments). + +## Before you begin + +You need **Base's Foundry build** (`base-forge`, `base-cast`, `base-anvil`). Install it 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.0 + ``` + + + 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. + + +## Set up your project + +```bash Terminal theme={null} +base-forge init b20-quickstart && cd b20-quickstart +base-forge install base/base-std --no-git +``` + +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`. + + +## Choose a network + +Pick a network with the B20 precompiles active, then create a `.env` **inside your `b20-quickstart` project directory**: + + + + | 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 | [faucet.vibes.base.org](https://faucet.vibes.base.org/) | + | Explorer | [explorer.vibes.base.org](https://explorer.vibes.base.org/) | + + ```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's address starts `0xB20f...`. 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 + +You did all of this without writing, deploying, or auditing a token contract. + +## Next steps + +* [Accept B20 payments in an app](/apps/guides/accept-b20-payments): 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). +* Issue a stablecoin variant (fixed 6 decimals, immutable currency code). \ No newline at end of file From fd3d0816139c3f77701c40d5afd78e93018684c3 Mon Sep 17 00:00:00 2001 From: sohey Date: Thu, 18 Jun 2026 19:40:51 +0200 Subject: [PATCH 2/2] chore: regenerate docs/agents.md, docs/llms-full.txt, docs/llms.txt (post-commit of 64fd250) --- docs/agents.md | 4 ++-- docs/llms-full.txt | 2 ++ docs/llms.txt | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/agents.md b/docs/agents.md index 819611e3c..b7fd5f12f 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -49,7 +49,7 @@ npx skills add base/base-skills |apps:index |apps/builder-codes:agent-developers,app-developers,builder-codes,wallet-developers |apps/growth:rewards -|apps/guides:migrate-to-standard-web-app +|apps/guides:accept-b20-payments,migrate-to-standard-web-app |apps/quickstart:build-app |apps/resources:design-resources,templates |apps/technical-guides:base-notifications @@ -101,6 +101,6 @@ npx skills add base/base-skills |base-chain/specs/upgrades/holocene:derivation,exec-engine,overview,system-config |base-chain/specs/upgrades/isthmus:derivation,exec-engine,l1-attributes,overview,predeploys,system-config |base-chain/specs/upgrades/jovian:derivation,exec-engine,l1-attributes,overview,system-config -|get-started:base-mentorship-program,base-services-hub,base,block-explorers,concepts,country-leads-and-ambassadors,data-indexers,deploy-smart-contracts,docs-llms,docs-mcp,get-funded,launch-token,learning-resources,prompt-library,resources-for-ai-agents +|get-started:base-mentorship-program,base-services-hub,base,block-explorers,concepts,country-leads-and-ambassadors,data-indexers,deploy-smart-contracts,docs-llms,docs-mcp,get-funded,launch-b20-token,launch-token,learning-resources,prompt-library,resources-for-ai-agents |ledgers:deposit-flow,how-it-works,overview,withdraw-flow |root:agents,changes,cookie-policy,privacy-policy,terms-of-service,tone_of_voice diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 7258a832b..80e300d21 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -102,6 +102,7 @@ const client = createPublicClient({ chain: base, transport: http() }) - [Static Docs Files](https://docs.base.org/get-started/docs-llms): Use llms.txt and llms-full.txt to give AI assistants access to Base documentation. - [MCP Server](https://docs.base.org/get-started/docs-mcp): Connect your AI coding assistant to Base documentation using Model Context Protocol for real-time access. - [Get Funded](https://docs.base.org/get-started/get-funded): The Base ecosystem offers multiple funding pathways designed specifically for builders at every stage—from weekend experiments to full-scale ventures. +- [Launch a B20 Token](https://docs.base.org/get-started/launch-b20-token): Launch a B20 token on Base by calling the B20 Factory precompile. - [Launch a Token](https://docs.base.org/get-started/launch-token) - [Learning Resources](https://docs.base.org/get-started/learning-resources): Find educational content for learning Solidity, Ethereum, and blockchain development - [Developer's Guide to Effective AI Prompting](https://docs.base.org/get-started/prompt-library): Learn practical AI prompting techniques to enhance your coding workflow and get better results from AI coding assistants. @@ -403,6 +404,7 @@ const client = createPublicClient({ chain: base, transport: http() }) - [Base Builder Codes](https://docs.base.org/apps/builder-codes/builder-codes): Attribute onchain activity to your app, wallet or agent with Builder Codes. - [Builder Codes for Wallet Developers](https://docs.base.org/apps/builder-codes/wallet-developers): Implement the dataSuffix capability in your wallet to enable Builder Code attribution. - [Rewards](https://docs.base.org/apps/growth/rewards): Earn financial incentives for building high-quality Apps that drive user engagement and onchain transactions +- [Accept B20 payments](https://docs.base.org/apps/guides/accept-b20-payments): Accept B20 token payments in your app and match each transaction to an order with onchain memos. - [Migrate to a Standard Web App](https://docs.base.org/apps/guides/migrate-to-standard-web-app): Migrate your Farcaster mini-app to work in the Base App. Covers replacing deprecated SDK methods, and registering on Base.dev. - [Build an app on Base](https://docs.base.org/apps/quickstart/build-app): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. - [Add notifications](https://docs.base.org/apps/technical-guides/base-notifications): Send in-app notifications to your app's users through the Base Dashboard REST API. diff --git a/docs/llms.txt b/docs/llms.txt index 175ca7613..ad93af27d 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -14,6 +14,7 @@ - [Static Docs Files](https://docs.base.org/get-started/docs-llms): Use llms.txt and llms-full.txt to give AI assistants access to Base documentation. - [MCP Server](https://docs.base.org/get-started/docs-mcp): Connect your AI coding assistant to Base documentation using Model Context Protocol for real-time access. - [Get Funded](https://docs.base.org/get-started/get-funded): The Base ecosystem offers multiple funding pathways designed specifically for builders at every stage—from weekend experiments to full-scale ventures. +- [Launch a B20 Token](https://docs.base.org/get-started/launch-b20-token): Launch a B20 token on Base by calling the B20 Factory precompile. - [Launch a Token](https://docs.base.org/get-started/launch-token) - [Learning Resources](https://docs.base.org/get-started/learning-resources): Find educational content for learning Solidity, Ethereum, and blockchain development - [Developer's Guide to Effective AI Prompting](https://docs.base.org/get-started/prompt-library): Learn practical AI prompting techniques to enhance your coding workflow and get better results from AI coding assistants. @@ -315,6 +316,7 @@ - [Base Builder Codes](https://docs.base.org/apps/builder-codes/builder-codes): Attribute onchain activity to your app, wallet or agent with Builder Codes. - [Builder Codes for Wallet Developers](https://docs.base.org/apps/builder-codes/wallet-developers): Implement the dataSuffix capability in your wallet to enable Builder Code attribution. - [Rewards](https://docs.base.org/apps/growth/rewards): Earn financial incentives for building high-quality Apps that drive user engagement and onchain transactions +- [Accept B20 payments](https://docs.base.org/apps/guides/accept-b20-payments): Accept B20 token payments in your app and match each transaction to an order with onchain memos. - [Migrate to a Standard Web App](https://docs.base.org/apps/guides/migrate-to-standard-web-app): Migrate your Farcaster mini-app to work in the Base App. Covers replacing deprecated SDK methods, and registering on Base.dev. - [Build an app on Base](https://docs.base.org/apps/quickstart/build-app): A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support. - [Add notifications](https://docs.base.org/apps/technical-guides/base-notifications): Send in-app notifications to your app's users through the Base Dashboard REST API.