Builder Codes
A Builder Code ties onchain activity back to whoever built the thing that caused it. Codes live as an ERC-721 collection, one NFT per code, each holding a short identifier such as abc123. The onchain metadata carries a payout address — where rewards would go if the program pays out. Name, site, and similar details sit offchain.
Register at base.dev to get a code. It appears under Settings → Builder Code.
The mechanism, in one paragraph
Section titled “The mechanism, in one paragraph”Attribution rides on the tail of the calldata. Your code is encoded as an ERC-8021 suffix and appended after the real arguments; indexers strip it back off later, offchain. Nothing onchain reads it, so contracts run exactly as before. Three consequences follow, and they are the reason this design was picked:
- Any deployed contract already works with ERC-8021. There is nothing to upgrade and nothing to redeploy.
- Execution is unaffected, because the trailing bytes are never decoded by the callee.
- The cost is 16 gas per non-zero byte, which is negligible against any real transaction.
EOAs and smart contract wallets both work. Attribution links a transaction to an application — it does not surface wallet information that was not already public onchain.
For app developers
Section titled “For app developers”An app registered on base.dev gets attribution inside the Base App for free — the Base App appends your code to transactions its users make through your app or its browser. That feeds your analytics and counts toward any future rewards.
Everything outside the Base App is on you. If users reach your app on the web or through another client, wire up dataSuffix yourself to capture that traffic.
Registration hands you a Builder Code — a random string along the lines of bc_b7k3p9da. Configure it once at the client level and every transaction that client sends carries it.
Both examples need viem 2.45.0 or newer.
npm i ox wagmi viemSet dataSuffix on the config and leave your hooks alone:
import { createConfig, http } from "wagmi";import { base } from "wagmi/chains";import { Attribution } from "ox/erc8021";
// Get your Builder Code from base.dev > Settings > Builder Codesconst DATA_SUFFIX = Attribution.toDataSuffix({ codes: ["YOUR-BUILDER-CODE"],});
export const config = createConfig({ chains: [base], transports: { [base.id]: http(), }, dataSuffix: DATA_SUFFIX,});With that in place, useSendTransaction and useSendCalls both pick the suffix up. No component changes:
import { useSendTransaction } from "wagmi";import { parseEther } from "viem";
function SendButton() { const { sendTransaction } = useSendTransaction();
return ( <button onClick={() => sendTransaction({ to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", value: parseEther("0.01"), }) } > Send ETH </button> );}npm i ox viemThe wallet client takes the same option:
import { createWalletClient, http } from "viem";import { base } from "viem/chains";import { Attribution } from "ox/erc8021";
// Get your Builder Code from base.dev > Settings > Builder Codesconst DATA_SUFFIX = Attribution.toDataSuffix({ codes: ["YOUR-BUILDER-CODE"],});
export const walletClient = createWalletClient({ chain: base, transport: http(), dataSuffix: DATA_SUFFIX,});Send as usual and the suffix goes along:
import { parseEther } from "viem";import { walletClient } from "./client";
const hash = await walletClient.sendTransaction({ to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", value: parseEther("0.01"),});Per-transaction, if you must
Section titled “Per-transaction, if you must”Client-level configuration is the recommended path. Where you genuinely need per-call control, dataSuffix can be passed straight to useSendTransaction. With useSendCalls it goes through capabilities instead, which requires the connected wallet to advertise the dataSuffix capability:
sendCalls({ calls: [ { to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", value: parseEther("1"), }, ], capabilities: { dataSuffix: { value: DATA_SUFFIX, optional: true, }, },});Managed wallet providers
Section titled “Managed wallet providers”- CDP Wallets — Coinbase Developer Platform smart accounts accept
dataSuffixon user operations, across the React hooks (useSendUserOperation), Node, and Python SDKs. Generate the suffix withAttribution.toDataSuffixfromox/erc8021; setup notes are under Builder Codes in their docs. - Privy — ships a
dataSuffixplugin that covers plain EOA transactions and ERC-4337 user operations alike. See the Privy integration guide. - Turnkey — infrastructure for programmatic wallets, also supported.
For wallet developers
Section titled “For wallet developers”A wallet enables attribution by honoring the dataSuffix capability: accept it, then append the bytes before signing.
Accept a dataSuffix object inside the capabilities object of wallet_sendCalls:
type DataSuffixCapability = { value: `0x${string}`; // hex-encoded bytes provided by the app optional?: boolean; // whether the capability is optional}Where the bytes land depends on the transaction type. For an EOA, extend tx.data:
// Minimal example for EOAfunction applySuffixToEOA(tx, capabilities) { const suffix = capabilities.dataSuffix?.value if (!suffix) return tx
return { ...tx, // Append suffix bytes (remove 0x prefix from suffix if tx.data has it) data: tx.data + suffix.slice(2) }}For ERC-4337 the target is userOp.callData, not the outer transaction calldata:
// Minimal example for ERC-4337function applySuffixToUserOp(userOp, capabilities) { const suffix = capabilities.dataSuffix?.value if (!suffix) return userOp
return { ...userOp, // Append suffix bytes to the UserOp callData callData: userOp.callData + suffix.slice(2) }}A wallet can claim attribution of its own at the same time. ERC-8021 carries multiple codes natively, so prepend your suffix ahead of the app’s and both parties are credited:
finalSuffix = walletSuffix + appSuffixApps need to do nothing for this — the wallet handles it alone.
For agent developers
Section titled “For agent developers”Agents transact on their own schedule, with no human pressing a button per transaction. Without a code that activity is anonymous; with one it rolls up under your identity in the Base registry, feeding analytics in base.dev and making the agent eligible for discovery surfaces such as the App Leaderboard.
The mechanics are the ones described above — an ERC-8021 suffix, ignored by contracts, recovered by indexers, 16 gas per non-zero byte.
Registration API
Section titled “Registration API”POST /v1/agents/builder-codesNo authentication required.
| Field | Type | Required | Description |
|---|---|---|---|
walletAddress | string | Yes | Your agent’s EVM wallet address (0x...) |
curl -X POST https://api.base.dev/v1/agents/builder-codes \ -H "Content-Type: application/json" \ -d '{ "walletAddress": "<your-wallet-address>" }'{ "builderCode": "bc_a1b2c3d4", "walletAddress": "0x..."}The endpoint is idempotent on wallet address: one address always maps to the same code, and calling again returns the code you already have. That makes it safe to run on every deploy rather than gating it behind a first-run check.
Doing it from a coding agent
Section titled “Doing it from a coding agent”Working in Claude Code, Cursor, or Codex? The Base skills package handles the whole path:
npx skills add base/base-skillsThen ask for a builder code registration. The skill validates the wallet, calls the API, writes the result to src/constants/builderCode.ts, installs ox, and threads the ERC-8021 dataSuffix into whichever client you use — viem, ethers.js, or a managed service.
Confirming attribution works
Section titled “Confirming attribution works”Three independent checks, cheapest first:
- base.dev — select Onchain in the transaction type dropdown. Attribution counts under Total Transactions climb as transactions carrying your code are processed.
- A block explorer — pull up the transaction on Basescan or Etherscan, open the input data field, and confirm the last 16 bytes are the repeating
8021pattern. Decode the suffix to see your code. - The validation tool — paste a transaction or UserOperation hash into Builder Code Validation, pick the transaction type, and press Check Attribution.
Wallet support today
Section titled “Wallet support today”Every EOA wallet supports dataSuffix as a matter of course. Smart wallets that implement ERC-5792 can carry it through the DataSuffixCapability:
await wallet.sendCalls({ calls: [ // your transaction calls ], capabilities: { dataSuffix: { value: "0x07626173656170700080218021802180218021802180218021", optional: true } }});Privy and Turnkey both cover the embedded wallet case.
Related resources
Section titled “Related resources”- ERC-8021 proposal — the specification itself.
- Builder Code Validation tool — check a hash for attribution.
- base.dev — registration, analytics, and your code.