Skip to content
BaseHub by wbnns Updated

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 SettingsBuilder Code.

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.

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.

Terminal window
npm i ox wagmi viem

Set dataSuffix on the config and leave your hooks alone:

config.ts
import { createConfig, http } from "wagmi";
import { base } from "wagmi/chains";
import { Attribution } from "ox/erc8021";
// Get your Builder Code from base.dev > Settings > Builder Codes
const 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:

App.tsx
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>
);
}
Terminal window
npm i ox viem

The wallet client takes the same option:

client.ts
import { createWalletClient, http } from "viem";
import { base } from "viem/chains";
import { Attribution } from "ox/erc8021";
// Get your Builder Code from base.dev > Settings > Builder Codes
const 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:

send-transaction.ts
import { parseEther } from "viem";
import { walletClient } from "./client";
const hash = await walletClient.sendTransaction({
to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
value: parseEther("0.01"),
});

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,
},
},
});
  • CDP WalletsCoinbase Developer Platform smart accounts accept dataSuffix on user operations, across the React hooks (useSendUserOperation), Node, and Python SDKs. Generate the suffix with Attribution.toDataSuffix from ox/erc8021; setup notes are under Builder Codes in their docs.
  • Privy — ships a dataSuffix plugin that covers plain EOA transactions and ERC-4337 user operations alike. See the Privy integration guide.
  • Turnkey — infrastructure for programmatic wallets, also supported.

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 EOA
function 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-4337
function 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 + appSuffix

Apps need to do nothing for this — the wallet handles it alone.

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.

POST /v1/agents/builder-codes

No authentication required.

FieldTypeRequiredDescription
walletAddressstringYesYour agent’s EVM wallet address (0x...)
Terminal window
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.

Working in Claude Code, Cursor, or Codex? The Base skills package handles the whole path:

Terminal window
npx skills add base/base-skills

Then 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.

Three independent checks, cheapest first:

  1. base.dev — select Onchain in the transaction type dropdown. Attribution counts under Total Transactions climb as transactions carrying your code are processed.
  2. 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 8021 pattern. Decode the suffix to see your code.
  3. The validation tool — paste a transaction or UserOperation hash into Builder Code Validation, pick the transaction type, and press Check Attribution.

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.