When you register on base.dev, we give you:
- Builder Code → a random string (e.g.,
"k3p9da") - ERC-8021 Suffix → already fully encoded as a hex string (e.g.,
"0x…")
You do not build, validate, or encode anything — you just use the suffix we provide.
If your app sends a normal eth_sendTransaction:
tx.data = tx.data + dataSuffix;
If your app constructs User Operations:
userOp.callData = userOp.callData + dataSuffix;
Smart accounts require putting the suffix inside the callData of the userOp.
The simplest and most reliable way.
Pass the suffix through the capability:
{
"capabilities": [
{ "dataSuffix": "0x..." }
]
}
Wallets automatically attach it to the correct place (EOA or 4337).
You do not need to:
- Understand the random code
- Decode or encode suffix bytes
- Check schema IDs
- Interact with the registry
- Change your smart contracts
- Modify backend systems
Just attach the suffix. Everything else is standardized and handled by wallets + Base.dev.
const calls = [{
to: contract,
data: calldata
}];
wallet_sendCalls({
calls,
capabilities: [
{ dataSuffix } // provided by base.dev
]
});
Once you add the suffix:
- Your app is fully ERC-8021 compliant
- Your activity is attributed onchain
- You unlock analytics, rewards, rankings, and future incentives
- Wallets and chains can reliably recognize your app
We give you a random code.
We give you the suffix.
You attach it.
That’s it.
Below is a dead-simple example of how an app attaches the ERC-8021 suffix using wagmi.
Assume:
const dataSuffix = "0x1234abcd..."; // provided by base.dev
(Recommended path — shortest, safest, wallet-native)
import { useSendCalls } from 'wagmi/experimental'
const dataSuffix = "0x1234abcd..." // provided by base.dev
function SendTx() {
const { sendCalls } = useSendCalls()
async function submit() {
await sendCalls({
calls: [
{
to: "0xYourContract",
data: "0xYourCalldata"
}
],
capabilities: [
{
dataSuffix // ERC-8021 capability
}
]
})
}
return <button onClick={submit}>Send</button>
}
That’s it. No suffix encoding, no registry lookups, no 4337 branching — wagmi + the wallet handle all of it.
import { walletClient } from './client'
const dataSuffix = "0x1234abcd..."
await walletClient.sendCalls({
calls: [
{
to: "0xYourContract",
data: "0xYourCalldata"
}
],
capabilities: [
{ dataSuffix }
]
})
(Not recommended, but supported)
You can manually append to calldata:
await writeContract({
address: "0xYourContract",
abi,
functionName: "yourFn",
args: [a, b, c],
// WAGMI builds calldata for you; we append the suffix
dataSuffix,
})
If your wagmi version doesn’t expose a dataSuffix field, simply concatenate manually:
const encoded = encodeFunctionData({
abi,
functionName: "yourFn",
args: [a, b, c],
})
const dataWithSuffix = encoded + dataSuffix.slice(2)
await sendTransaction({
to: "0xYourContract",
data: dataWithSuffix,
})
Almost no apps do this directly. But if they do:
userOp.callData = userOp.callData + dataSuffix.slice(2)
And send it through your bundler or wallet SDK.
You don’t need to understand ERC-8021.
You only need your suffix.
Then:
capabilities: [{ dataSuffix }]
Add support for attribution codes with minimal changes.
This guide shows exactly how to:
-
Accept the
dataSuffixcapability from apps -
Append the suffix correctly to outgoing transactions
-
Support both EOAs and ERC-4337 smart accounts
-
Do all of the above with almost zero logic or schema awareness
Wallets should accept:
interface DataSuffixCapability {
dataSuffix: string; // hex-encoded bytes
}
This appears inside the capabilities array of wallet_sendCalls.
What you must do:
-
Look for any capability object that includes
dataSuffix -
Extract the hex string
-
Store it for use when constructing the transaction or user operation
No decoding, no validation, no schema handling is required.
Wallets do not need to understand what the suffix means—just pass it through verbatim.
Wallets only need one conditional branch.
Append the suffix to the transaction data:
tx.data = concat(tx.data, dataSuffix)
This is literally bytes concatenation.
Append the suffix to the userOp.callData, not the transaction-level calldata:
userOp.callData = concat(userOp.callData, dataSuffix)
This ensures parsers see the suffix where the call is actually executed.
If your wallet supports ERC-5792, this is the recommended path.
Steps:
-
Read
capabilities[] -
Extract
dataSuffix -
For each call in
calls[]:-
If EOA → append to
tx.data -
If 4337 → append to
userOp.callData
-
That’s the entire integration.
Wallets may also include their own attribution code (their own ERC-8021 suffix):
-
Simply prepend the wallet’s own suffix before the app’s
-
No interaction is required with apps
-
Multi-code support is built into ERC-8021
Example:
finalSuffix = walletSuffix + appSuffix
This is fully supported by the standard.
Wallets do not need to:
-
Validate the encoding
-
Parse or interpret the suffix
-
Query registries
-
Reject malformed codes
If the suffix is malformed, apps are the ones losing attribution—not the wallet.
Safe failure mode: append whatever is provided.
function applySuffixToEOA(tx, capabilities) {
const suffix = capabilities.find(c => c.dataSuffix)?.dataSuffix
if (!suffix) return tx
return {
...tx,
data: tx.data + suffix.slice(2)
}
}
function applySuffixToUserOp(userOp, capabilities) {
const suffix = capabilities.find(c => c.dataSuffix)?.dataSuffix
if (!suffix) return userOp
return {
...userOp,
callData: userOp.callData + suffix.slice(2)
}
}
-
EOAs →
tx.data -
4337 →
userOp.callData
“If a call has a dataSuffix, append it to calldata before sending.
Use transaction.data for EOAs, userOp.callData for 4337. That’s all.”