Base Builder Codes - Relay
How to add a builder code
A builder code is an ERC-8021dataSuffix appended to the end of a deposit transaction’s calldata. The called contract ignores the trailing bytes, but indexers read them to credit your application. Relay matches an onchain transaction to its quote by requestId. Appending bytes to arbitrary calldata changes the transaction, so the solver can no longer match it and the request stops tracking — even though the transaction succeeds onchain. To avoid this, request an explicit deposit quote: set explicitDeposit to true. The explicit flow routes funds through the Relay Depository contract that carries the order ID as an argument, so the solver tracks the deposit by that order ID rather than by exact calldata — trailing suffix bytes don’t affect it. Builder codes only work for quotes that route through the Relay Depository contract; same-chain swaps don’t use the deposit flow and can’t carry a builder code.
Example
import {
createWalletClient,
custom,
concatHex,
toHex,
stringToHex,
size,
type Hex,
} from "viem";
import { base } from "viem/chains";
// ERC-8021 suffix = 1-byte length + builder code (ASCII) + 1-byte version + 16-byte marker.
const ERC8021_MARKER = "0x80218021802180218021802180218021" as const; // 0x8021 repeated 8×
const ERC8021_VERSION = "0x00" as const;
function buildDataSuffix(builderCode: string): Hex {
const codeHex = stringToHex(builderCode);
const lenByte = toHex(size(codeHex), { size: 1 });
return concatHex([lenByte, codeHex, ERC8021_VERSION, ERC8021_MARKER]);
}
async function fetchQuote(req: Record<string, unknown>) {
const res = await fetch("https://api.relay.link/quote/v2", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(req),
});
if (!res.ok) throw new Error(`Relay quote failed: ${res.status} ${await res.text()}`);
return res.json();
}
// Append the suffix to the deposit step only — that is the depositErc20 call the
// Depository tracks by order ID, so trailing bytes don't break requestId matching.
function applyDataSuffix(quote: any, dataSuffix: Hex) {
return {
...quote,
steps: quote.steps.map((step: any) =>
step.id !== "deposit"
? step
: {
...step,
items: step.items.map((item: any) => ({
...item,
data: { ...item.data, data: concatHex([item.data.data, dataSuffix]) },
})),
}
),
};
}
const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum) });
const [account] = await walletClient.getAddresses();
// 1. Request an explicit-deposit quote (required for builder codes).
const quote = await fetchQuote({
user: account,
recipient: account,
originChainId: 8453, // Base
destinationChainId: 42161, // Arbitrum
originCurrency: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
destinationCurrency: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
amount: "1000000", // 1 USDC (6 decimals)
tradeType: "EXACT_INPUT",
protocolVersion: "v2",
explicitDeposit: true,
});
// 2. Append your builder code to the deposit calldata.
const dataSuffix = buildDataSuffix("YOUR_BUILDER_CODE");
const quoteWithSuffix = applyDataSuffix(quote, dataSuffix);
// 3. Execute each transaction step in order (approval first, then deposit).
for (const step of quoteWithSuffix.steps) {
if (step.kind !== "transaction") continue;
for (const { data: tx } of step.items) {
await walletClient.sendTransaction({
account,
to: tx.to,
data: tx.data,
value: BigInt(tx.value ?? 0),
chainId: tx.chainId,
});
}
}
Caveats
- Builder codes require an explicit deposit: set
explicitDeposittotrueandprotocolVersionto "v2" in your quote request. - Only quotes that route through the Relay Depository contract (
depositErc20) support builder codes. Same-chain swaps do not. - Append the suffix to the
depositstep only.