Motyw
Build on X Layer

Integrate Builder Codes#

Add ERC-8021 attribution to your app on X Layer using Viem or Wagmi to attribute onchain activity on X Layer.

Prerequisites#

Requires viem 2.45.0 or higher. Define the X Layer chain(s) you need and reuse them across your config:

Ts
// chain.ts
import { defineChain } from "viem";

// Mainnet
export const xlayer = defineChain({
  id: 196,
  name: "X Layer",
  nativeCurrency: { name: "OKB", symbol: "OKB", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://rpc.xlayer.tech"] },
  },
  blockExplorers: {
    default: { name: "OKLink", url: "https://www.oklink.com/xlayer" },
  },
});

// Testnet
export const xlayerTestnet = defineChain({
  id: 1952,
  name: "X Layer Testnet",
  nativeCurrency: { name: "OKB", symbol: "OKB", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://testrpc.xlayer.tech"] },
  },
  blockExplorers: {
    default: { name: "OKLink", url: "https://www.oklink.com/x-layer-testnet" },
  },
});

Get your Builder Code

Client-Level Setup#

Configure dataSuffix once on the client that actually sends the transaction. For browser wallet integrations, Viem is the recommended default because the configured wallet client is also the client used to send the transaction.

Viem (Recommended)#

  1. Install dependencies:
Bash
npm i ox viem
  1. Create and reuse one wallet client with dataSuffix:
Ts
// client.ts
import { createWalletClient, custom } from "viem";
import { Attribution } from "ox/erc8021";
import { xlayer } from "./chain";

const DATA_SUFFIX = Attribution.toDataSuffix({
  codes: ["YOUR-BUILDER-CODE"],
});

if (!window.ethereum) {
  throw new Error("No injected wallet found");
}

export const walletClient = createWalletClient({
  chain: xlayer,
  transport: custom(window.ethereum),
  dataSuffix: DATA_SUFFIX,
});
  1. Send transactions with the same client:
Ts
import { walletClient } from "./client";

const [account] = await walletClient.requestAddresses();

const hash = await walletClient.sendTransaction({
  account,
  to: "0x8d7c41aa990234b2d7e064df150a4228ed984648",
});

Every transaction sent through walletClient includes your Builder Code automatically.

Wagmi#

For connector wallets, use a custom connector client so dataSuffix is applied to the wallet-backed client used by useSendTransaction.

  1. Install dependencies:
Bash
npm i ox wagmi viem
  1. Add a custom injected connector that returns a client with dataSuffix:
Ts
// config.ts
import { Attribution } from "ox/erc8021";
import { createClient, custom } from "viem";
import { createConfig, createConnector, http } from "wagmi";
import { injected } from "wagmi/connectors";
import { xlayer } from "./chain";

const DATA_SUFFIX = Attribution.toDataSuffix({
  codes: ["YOUR-BUILDER-CODE"],
});

function injectedWithDataSuffix() {
  const baseInjected = injected();

  return createConnector((config) => {
    const connector = baseInjected(config);

    return {
      ...connector,
      async getClient({ chainId } = {}) {
        const id = chainId ?? (await connector.getChainId());
        const chain = config.chains.find((item) => item.id === id);
        const provider = await connector.getProvider({ chainId: id });
        const [account] = await connector.getAccounts();

        if (!chain) throw new Error(`Chain ${id} is not configured.`);
        if (!provider) throw new Error("Injected provider not found.");
        if (!account) throw new Error("No connected account found.");

        return createClient({
          account,
          chain,
          dataSuffix: DATA_SUFFIX,
          transport: (options) =>
            custom(provider)({
              ...options,
              retryCount: 0,
            }),
        });
      },
    };
  });
}

export const config = createConfig({
  chains: [xlayer],
  connectors: [injectedWithDataSuffix()],
  transports: {
    [xlayer.id]: http(),
  },
});
  1. Transactions via useSendTransaction inherit your Builder Code from the custom connector client:
Tsx
// App.tsx
import { useSendTransaction } from "wagmi";

function SendButton() {
  const { sendTransaction } = useSendTransaction();

  return (
    <button
      onClick={() =>
        sendTransaction({
          to: "0x8d7c41aa990234b2d7e064df150a4228ed984648",
        })
      }
    >
      Send OKB
    </button>
  );
}

Per-Transaction Configuration#

If you need granular control, pass dataSuffix directly on individual transactions instead of at the client level.

useSendTransaction#

Tsx
import { useSendTransaction } from "wagmi";
import { Attribution } from "ox/erc8021";

const DATA_SUFFIX = Attribution.toDataSuffix({
  codes: ["YOUR-BUILDER-CODE"],
});

function App() {
  const { sendTransaction } = useSendTransaction();

  return (
    <button
      onClick={() =>
        sendTransaction({
          to: "0x8d7c41aa990234b2d7e064df150a4228ed984648",
          dataSuffix: DATA_SUFFIX,
        })
      }
    >
      Send OKB
    </button>
  );
}

Verify Attribution#

See Verify Attribution for steps to confirm your transaction was properly attributed.

Builder Code Analytics#

Track your Builder Code performance in the OKX Developer Portal:

  • Onchain activity — monitor attributed transactions and call volume
  • User acquisition — measure new users driven by your app
  • Conversion metrics — analyze funnel performance from impression to onchain action

Use these insights to optimize your integration and grow your audience on X Layer.