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,
});
}
}