Skip to main content

Your wallet, in every trade you bring

Every trade on Feast pays a 1% fee, and the contract credits a share of it to the trade’s referrer in the same transaction. A trading bot or terminal can be that referrer: put your payout wallet in each trade it sends, and ask each wallet once to bind to you for good.

0x000000000000000000000000951ab0bb6271d99e7997362fbb71fc3fd7464fa0

  • 12 zero bytes
  • the payout wallet, 20 bytes

The hookData our example’s buy carried on the sandbox: one 32-byte word, with the payout wallet in its last 20 bytes.

Every call on this page ran on 14 October 2026 (UTC): the trades on the sandbox, the reads on BNB Chain, and the binding against BNB Chain’s own registry code on a local chain.

What a referrer is credited

A trade pays the hook a 1% fee in the pool’s quote token: wrapped BNB, or the stock token the coin is paired with.

Each coin’s creator sets the referrer’s seat at launch, in basis points of that fee. Read it per pool as feeSplitBps.referrer in /api/v1/pools: on 14 October all 11 pools listed on BNB Chain had 1,000, a tenth of the fee.

The hook credits that share to the trade’s referrer in the same transaction, per pool, in the quote token, and referrerPending reads it. claimReferrer pays it out, and anyone may send it: the hook pays the referrer, not the sender.

A trade that names no referrer adds the referrer’s seat to the creator’s.

Separately, the platform coin’s weekly emissions are split into three pots: referrers, traders and creators. The referrers’ pot is 4,000 of every 10,000 in weeks 1 to 8 and 2,000 from week 9, as the emissions contract answers; inside it, each referrer’s part follows the fees of the trades they brought that week.

Checked on 14 October: the emissions contract had paid out no week yet, and its claims were not open.

Trade from a script

A Feast pool is a PancakeSwap Infinity pool with Feast’s hook, and trades go through PancakeSwap’s Universal Router. The sandbox is a public copy of BNB Chain with free test BNB, and the router, Permit2 and wrapped BNB sit at the same addresses there.

Addresses
Universal RouterBNB Chain (56)0xd9C500DfF816a1Da21A48A732d3498Bf09dc9AEBSandbox (56096)0xd9C500DfF816a1Da21A48A732d3498Bf09dc9AEB
Permit2BNB Chain (56)0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768Sandbox (56096)0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768
Wrapped BNBBNB Chain (56)0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095cSandbox (56096)0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
Referral registryBNB Chain (56)0x01bc8640af92f4E556cB38644bC3DA5f41f253a4Sandbox (56096)none: an older hook
Site and read endpointsBNB Chain (56)https://feast.memeSandbox (56096)https://sandbox.feast.meme

The script below is one file in seven parts. Save them in order as feast-example.mjs, then run npm install viem and node feast-example.mjs. It makes throwaway keys, asks the sandbox faucet for test BNB and sends nothing to BNB Chain. It ran with Node 24 and viem 2.56.

  1. Set up

    The sandbox’s chain and node, the router and Permit2, and two throwaway keys: the trader, and a stand-in for your payout wallet. Set PAYOUT to your own address to use yours.

    import { createPublicClient, createWalletClient, defineChain, encodeAbiParameters, erc20Abi, formatEther, http, maxUint256, parseAbi, parseAbiParameters, parseEther } from "viem";
    import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
    
    const SITE = "https://sandbox.feast.meme";
    const chain = defineChain({
      id: 56096,
      name: "Feast sandbox",
      nativeCurrency: { name: "tBNB", symbol: "tBNB", decimals: 18 },
      rpcUrls: { default: { http: [`${SITE}/api/sandbox/rpc`] } },
    });
    const ROUTER = "0xd9C500DfF816a1Da21A48A732d3498Bf09dc9AEB";
    const PERMIT2 = "0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768";
    const WBNB = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
    const env = process.env;
    const PAYOUT = env.PAYOUT ?? privateKeyToAccount(generatePrivateKey()).address;
    const trader = privateKeyToAccount(generatePrivateKey());
    const read = createPublicClient({ chain, transport: http() });
    const send = createWalletClient({ account: trader, chain, transport: http() });
    
    const routerAbi = parseAbi(["function execute(bytes commands, bytes[] inputs, uint256 deadline) payable"]);
    const permit2Abi = parseAbi(["function approve(address token, address spender, uint160 amount, uint48 expiration)"]);
    const hookAbi = parseAbi([
      "struct PoolKey { address currency0; address currency1; address hooks; address poolManager; uint24 fee; bytes32 parameters; }",
      "function referrerPending(bytes32 poolId, address referrer) view returns (uint256)",
      "function claimReferrer(PoolKey key, address referrer)",
    ]);
    const SWAP = parseAbiParameters(
      "((address currency0, address currency1, address hooks, address poolManager, uint24 fee, bytes32 parameters) poolKey, bool zeroForOne, uint128 amountIn, uint128 amountOutMinimum, bytes hookData)",
    );
    const MSG_SENDER = "0x0000000000000000000000000000000000000001";
    const ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
    const ALL = 1n << 255n;
    
    const enc = (types, ...values) => encodeAbiParameters(parseAbiParameters(types), values);
    const api = async (path, init) => {
      const res = await fetch(`${SITE}${path}`, init);
      if (!res.ok) throw new Error(`${path} answered ${res.status}: ${await res.text()}`);
      return res.json();
    };
    const deadline = async () => (await read.getBlock()).timestamp + 1800n;
    const sent = async (hash) => {
      const receipt = await read.waitForTransactionReceipt({ hash });
      if (receipt.status !== "success") throw new Error(`reverted: ${hash}`);
      return receipt;
    };
  2. Fund the trader

    The sandbox faucet sent 100 test BNB. On BNB Chain there is no faucet: use a funded wallet.

    await api("/api/sandbox/faucet", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ address: trader.address }),
    });
    console.log("trader", trader.address, "holds", formatEther(await read.getBalance({ address: trader.address })), "tBNB");
  3. Pick a pool, and name your wallet

    /api/v1/pools lists pools newest first, each with its poolKey and feeSplitBps. This asks for pools quoted in wrapped BNB and takes the first open one. hookData is your payout wallet as one 32-byte word: the word at the top of this page.

    const { pools } = await api(`/api/v1/pools?quote=${WBNB}&limit=20`);
    const pool = pools.find((p) => p.opening.open);
    const key = pool.poolKey;
    const coin = pool.coin.address;
    const wbnbIs0 = key.currency0.toLowerCase() === WBNB.toLowerCase();
    const hookData = enc("address", PAYOUT);
    const swap = (zeroForOne, amountIn, amountOutMinimum) =>
      encodeAbiParameters(SWAP, [{ poolKey: key, zeroForOne, amountIn, amountOutMinimum, hookData }]);
    console.log("pool", pool.poolId, "referrer seat", pool.feeSplitBps.referrer, "bps of the fee");
  4. Quote it

    /api/v1/quote asks PancakeSwap’s quoter what the Feast pool gives for this amount with your hookData. For a pool quoted in wrapped BNB that is the whole trade. Its feeSplit.referrer is your share of this trade’s fee: 9,900,990,099,009 wei in the run.

    const amountIn = parseEther("0.01");
    const quote = await api(`/api/v1/quote?poolId=${pool.poolId}&side=buy&amountIn=${amountIn}&referrer=${PAYOUT}`);
    const minOut = (BigInt(quote.amountOut) * 99n) / 100n;
    console.log("quote", quote.amountOut, "coin units; referrer share", quote.feeSplit.referrer, "wei of wrapped BNB");
  5. Buy

    One execute on the Universal Router. Commands 0x0b10: wrap the BNB sent with the call, then one Infinity swap. Its actions 0x0b060f: pay the pool from the router, swap exact-in through the Feast pool with your hookData, and take at least the minimum out.

    const bought = await sent(
      await send.writeContract({
        address: ROUTER,
        abi: routerAbi,
        functionName: "execute",
        value: amountIn,
        args: [
          "0x0b10",
          [
            enc("address, uint256", ADDRESS_THIS, amountIn),
            enc("bytes, bytes[]", "0x0b060f", [
              enc("address, uint256, bool", WBNB, ALL, false),
              swap(wbnbIs0, 0n, minOut),
              enc("address, uint256", coin, minOut),
            ]),
          ],
          await deadline(),
        ],
      }),
    );
    const held = await read.readContract({ address: coin, abi: erc20Abi, functionName: "balanceOf", args: [trader.address] });
    console.log("bought", held, "coin units in", bought.transactionHash);
  6. Read the credit, then pay it out

    Right after the buy, referrerPending read 9,900,990,099,009 wei for the payout wallet, the figure the quote gave. claimReferrer, sent here by the trader, paid exactly that to the payout wallet in wrapped BNB.

    const credited = await read.readContract({ address: key.hooks, abi: hookAbi, functionName: "referrerPending", args: [pool.poolId, PAYOUT] });
    console.log("credited to", PAYOUT, formatEther(credited), "wrapped BNB");
    await sent(await send.writeContract({ address: key.hooks, abi: hookAbi, functionName: "claimReferrer", args: [key, PAYOUT] }));
    const paid = await read.readContract({ address: WBNB, abi: erc20Abi, functionName: "balanceOf", args: [PAYOUT] });
    console.log("paid to", PAYOUT, formatEther(paid), "wrapped BNB");
  7. Sell

    A sale pulls the coin through Permit2: approve Permit2 on the coin, then the router on Permit2, once per coin. Commands 0x100c: one Infinity swap, then unwrap to BNB. Its actions 0x060c0e: swap exact-in, pay the coin from the wallet, and take the wrapped BNB to the router. The sale names your hookData too, and the payout wallet was credited again.

    await sent(await send.writeContract({ address: coin, abi: erc20Abi, functionName: "approve", args: [PERMIT2, maxUint256] }));
    const until = (await deadline()) + 30n * 86400n;
    await sent(await send.writeContract({ address: PERMIT2, abi: permit2Abi, functionName: "approve", args: [coin, ROUTER, held, until] }));
    const sale = await api(`/api/v1/quote?poolId=${pool.poolId}&side=sell&amountIn=${held}&referrer=${PAYOUT}`);
    const saleMin = (BigInt(sale.amountOut) * 99n) / 100n;
    const sold = await sent(
      await send.writeContract({
        address: ROUTER,
        abi: routerAbi,
        functionName: "execute",
        args: [
          "0x100c",
          [
            enc("bytes, bytes[]", "0x060c0e", [
              swap(!wbnbIs0, held, saleMin),
              enc("address, uint256", coin, held),
              enc("address, address, uint256", WBNB, ADDRESS_THIS, 0n),
            ]),
            enc("address, uint256", MSG_SENDER, saleMin),
          ],
          await deadline(),
        ],
      }),
    );
    const after = await read.readContract({ address: key.hooks, abi: hookAbi, functionName: "referrerPending", args: [pool.poolId, PAYOUT] });
    console.log("sold in", sold.transactionHash, "; credited to", PAYOUT, "since the claim", formatEther(after), "wrapped BNB");

What it printed

The addresses are the run’s throwaway keys, and the hashes are sandbox transactions.

trader 0x9b602F62Ac2933dcCc529366eC8e5fd63b5560fE holds 100 tBNB
pool 0x318da75125c8e9f85252f7f3c796b9932b764d91cc9fd095e03fe7a832503985 referrer seat 1000 bps of the fee
quote 1084150761376251322410665 coin units; referrer share 9900990099009 wei of wrapped BNB
bought 1084150761376251322410665n coin units in 0xd59838163008d036ed593190d7864d047bd51fc80d7dc33709547bef6ed686c1
credited to 0x951ab0Bb6271D99e7997362fBb71Fc3Fd7464FA0 0.000009900990099009 wrapped BNB
paid to 0x951ab0Bb6271D99e7997362fBb71Fc3Fd7464FA0 0.000009900990099009 wrapped BNB
sold in 0x24777ec226efc418a6588608fcb5860b7938e90b03568d0e99891e8b2f747e5c ; credited to 0x951ab0Bb6271D99e7997362fBb71Fc3Fd7464FA0 since the claim 0.000009898185739486 wrapped BNB

This example trades a pool quoted in wrapped BNB. A pool quoted in a stock token also needs PancakeSwap v3 hops between wrapped BNB and that token, before the Feast pool on a buy and after it on a sale; the example does not show them.

On BNB Chain the same commands go to the same router: the site’s own trade box sends them, and its tests rebuild real BNB Chain trades from them byte for byte. This page sent no trade to BNB Chain. It checked the referral rules on a local copy of that chain instead, with throwaway keys, as the list of what a bot cannot do says.

Bind a wallet to you

A wallet bound to a referrer credits that referrer on every trade it sends, on every Feast coin, whatever its hookData says. The binding lives in FeastReferrals on BNB Chain, and only the wallet can make it: by its own transaction, bind, or by signing a Bind message that anyone may relay with bindWithSig.

The first binding stands for good. The registry has three functions, bind, bindWithSig and referrerOf, and none of them changes a binding once it is made: a second one is refused as AlreadyBound.

A signature carries a deadline and is refused once it has passed (SignatureExpired). The site asks for seven days.

  1. The registry and the signer

    The registry, its two calls, and the wallet that signs. On your service that wallet is the user’s; USER_KEY stands in for it here.

    import { createPublicClient, createWalletClient, http, parseAbi, zeroAddress } from "viem";
    import { bsc } from "viem/chains";
    import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
    
    const env = process.env;
    const SITE = env.FEAST_SITE ?? "https://feast.meme";
    const RPC = env.BSC_RPC ?? "https://bsc-rpc.publicnode.com";
    const REGISTRY = "0x01bc8640af92f4E556cB38644bC3DA5f41f253a4";
    const PAYOUT = env.PAYOUT;
    const user = privateKeyToAccount(env.USER_KEY ?? generatePrivateKey());
    const registryAbi = parseAbi([
      "function referrerOf(address wallet) view returns (address)",
      "function bindWithSig(address wallet, address referrer, uint256 deadline, bytes signature)",
    ]);
    const read = createPublicClient({ chain: bsc, transport: http(RPC) });
  2. Check first

    referrerOf answers the zero address for a wallet that has never bound. A wallet that is already bound keeps its referrer.

    const before = await read.readContract({ address: REGISTRY, abi: registryAbi, functionName: "referrerOf", args: [user.address] });
    console.log("referrerOf", user.address, "=", before);
    if (before !== zeroAddress) process.exit(0);
  3. Ask the wallet to sign

    The typed data the site’s own trade button asks for. The domain is FeastReferrals, version 1, chain 56 and the registry’s address; the message is the wallet, the referrer and the deadline. It is a signature, not a transaction, and costs the wallet nothing.

    const deadline = BigInt(Math.floor(Date.now() / 1000) + 7 * 86400);
    const signature = await user.signTypedData({
      domain: { name: "FeastReferrals", version: "1", chainId: 56, verifyingContract: REGISTRY },
      types: {
        Bind: [
          { name: "wallet", type: "address" },
          { name: "referrer", type: "address" },
          { name: "deadline", type: "uint256" },
        ],
      },
      primaryType: "Bind",
      message: { wallet: user.address, referrer: PAYOUT, deadline },
    });
  4. Then queue it with the site

    Post it to /api/v1/referral/bind. The route checks the signature the way the registry will, then queues it. It answered 200 with status pending, the same again for the same body, and 409 AlreadyBound once the wallet was bound. It allows 120 requests a minute from one address, then three a minute for one wallet, and sends no cross-origin headers, so call it from your server.

    const queued = await fetch(`${SITE}/api/v1/referral/bind`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ wallet: user.address, referrer: PAYOUT, deadline: deadline.toString(), signature, chainId: 56 }),
    });
    console.log("queued", queued.status, await queued.json());

    The same request on the wire, and the two answers the route gave:

    POST https://feast.meme/api/v1/referral/bind
    content-type: application/json
    
    { "wallet": "0x…", "referrer": "0x…", "deadline": "1792570226", "signature": "0x…", "chainId": 56 }
    200 { "chainId": 56, "status": "pending" }
    409 { "error": { "code": "refused", "message": "…", "refusal": "AlreadyBound" } }
  5. Or relay it yourself

    Any account may send bindWithSig with the wallet’s signature; it used about 55,000 gas when we ran it. After it, referrerOf names your payout wallet.

    const relayer = createWalletClient({ account: privateKeyToAccount(env.RELAYER_KEY), chain: bsc, transport: http(RPC) });
    const hash = await relayer.writeContract({
      address: REGISTRY,
      abi: registryAbi,
      functionName: "bindWithSig",
      args: [user.address, PAYOUT, deadline, signature],
    });
    const receipt = await read.waitForTransactionReceipt({ hash });
    const after = await read.readContract({ address: REGISTRY, abi: registryAbi, functionName: "referrerOf", args: [user.address] });
    console.log("relayed", receipt.status, "gas", receipt.gasUsed, "; referrerOf", user.address, "=", after);

This code ran against BNB Chain’s own registry code, copied byte for byte onto a local chain with chain id 56, and against this site’s bind route, served from the same build. It queued, then relayed, and printed:

referrerOf 0xC3b643869b589BBfF5fAAca03345cc9331884206 = 0x0000000000000000000000000000000000000000
queued 200 { chainId: 56, status: 'pending' }
relayed success gas 54894n ; referrerOf 0xC3b643869b589BBfF5fAAca03345cc9331884206 = 0xe6f97ef87d4cBFc3fa778CB08C78cB0DFAdf2Da9
How the route and the registry answered each case
The wallet’s own signature, for the payout walletRoute 200 pendingRegistry accepted
The same body againRoute 200 pendingRegistry
Signed for another chainRoute 400 wrong-chainRegistry
The wallet as its own referrerRoute 400 SelfReferralRegistry SelfReferral
The zero address as referrerRoute 400 ZeroReferrerRegistry ZeroReferrer
The burn address as referrerRoute 400 BurnReferrerRegistry BurnReferrer
A deadline that has passedRoute 400 SignatureExpiredRegistry SignatureExpired
Signed for one referrer, sent naming anotherRoute 400 BadSignatureRegistry BadSignature
Signed by another key for this walletRoute 400 BadSignatureRegistry BadSignature
A new Bind from a wallet already boundRoute 409 AlreadyBoundRegistry AlreadyBound
A fourth post for one wallet inside a minuteRoute 429 rate-limitedRegistry

What happens to a queued signature

The site’s keeper relays queued signatures with bindWithSig and pays the gas. It relays one only after the wallet, or its referrer, has traded on Feast, and by default at most 50 a day for one referrer. Those are the keeper’s settings, not the registry’s rules, and its code promises no time: to have a binding on chain at once, relay it yourself.

What a bot cannot do

  • Bind a wallet without that wallet’s signature or its own transaction. The registry checks the signature against the wallet; one made by any other key is refused.
  • Change or undo a binding, its own or anyone else’s.
  • Take over a wallet that is already bound: its trades credit its bound referrer, whatever hookData names.
  • Be credited for a trade it sends from its own key. The hook looks up the binding of the account that sends the transaction, and pays no referrer who is that account; send each trade from the user’s wallet.
  • Bind a wallet to itself, to the zero address or to the burn address.

We ran each of these on 14 October: the signature rules against the registry’s own code, and the trade rules on a local copy of BNB Chain with throwaway keys. There, a bound wallet’s buy that named another referrer credited only its bound one, and a buy sent from a bot’s own key, naming the bot, credited nobody.

If your service holds a user’s key, a Bind you sign with it is that wallet’s signature to the registry, and it is permanent. Ask the person first.

Your referral code

Your referrer is an address: your payout wallet. A referral code is a short name for it, for links.

Make one on the referral page with the payout wallet connected, and sign once: a signature, not a transaction. The code names that wallet for good. Open the referral page

A link to /r/ followed by your code brings a reader to the site with you as their referrer; on the site, a wallet that is not bound yet is asked to bind to you before its first trade.

From your server, /api/v1/referral/resolve?code= answers the wallet a code names, and 404 for a code nobody holds.

Read endpoints

Every read below needs no key and no account. They share one limit: 60 requests a minute for one client across /api/v1, counted in the x-ratelimit-limit and x-ratelimit-remaining headers. Any site may call them from a browser.

/api/v1/pools
Every pool, newest first: its coin, its quote token, poolKey, fee split and opening state.
/api/v1/pools/{poolId}
One pool, with its live state and its fee totals.
/api/v1/quote?poolId=&side=&amountIn=&referrer=
What a trade would give, simulated on the chain, with the fee and its split for the referrer you name.
/api/v1/events
Launches, trades and fees from the index, with a cursor to page on.
/api/v1/ohlcv?poolId=
Candles for one pool.
/api/v1/weekly
This week’s boards: creators, traders and referrers.
/api/v1/payouts
What has been paid out: claims, buy-back burns and the platform’s releases.
/api/v1/screener/latest-block/api/v1/screener/asset?id=/api/v1/screener/pair?id=/api/v1/screener/events?fromBlock=&toBlock=
A swap feed by block range, in the shape screener adapters read: the latest final block, one token, one pair, and the swaps between two blocks.
/api/v1/me/holdings?wallet=/api/v1/me/fees?wallet=/api/v1/me/launches?wallet=/api/v1/me/referrals?wallet=/api/v1/me/campaign?wallet=
One wallet, by address: what it holds, its fees this week by role, what it launched, who it brought (its code, how many wallets are bound to it, their fees this week) and its launch-week campaign standing.
/api/v1/referral/resolve?code=
The wallet a referral code names. Not open to other sites’ browsers, and outside the 60-a-minute limit.
/api/v1
The full reference, with every parameter.

Each of these answered on feast.meme on 14 October 2026.

The feast-agent client

feast-agent is an open-source TypeScript client over viem, with a command line and a Model Context Protocol server. github.com/feastmeme/feast-agent

We cloned it on 14 October and ran its own commands: on the sandbox its faucet, buy, sell and split worked, and on BNB Chain it read the list of pairable quote tokens.

git clone https://github.com/feastmeme/feast-agent.git
cd feast-agent
npm ci
cp .env.example .env
npm run faucet
npm run agent -- buy <coin address> 0.01

As of its commit 33df435 it trades through the sandbox’s own router, not the Universal Router. Its referrerOf reads the hook, which on BNB Chain no longer answers that call; to read a binding, call referrerOf on FeastReferrals, as the bind code above does.

What the code leaves open

  • The sandbox runs an older hook with no FeastReferrals. A named referrer is credited per trade there as on BNB Chain, but a wallet cannot be bound the way this page describes, and the sandbox’s bind route answers 503.
  • When the keeper relays a queued signature: its code sets conditions and budgets, not a time.
  • Each coin’s referrer seat is set at its launch. Read feeSplitBps for each pool rather than assume a tenth.