Skip to main content
In this guide, you’ll read and claim the creator side of trading fees on Robinhood Chain. Each token has its own BagsFeeShare contract that accrues 1% of every trade (in WETH) and splits it among the claimers configured at launch.

Prerequisites

Before starting, make sure you have:
  • Completed the Environment Setup.
  • A token whose feeShare address you can resolve (from launch, factory.feeShareForToken(token), or BagsLens.getTokenState).
  • A wallet with a positive claimable balance — i.e. one of the token’s configured fee claimers or the optional partner. Check with claimableOf(token, user) (or feeShare.claimable(user)).

1. How Fee Sharing Works

Of the flat 2% trade fee, half (1%) goes to the platform vault and half (1%) is delivered to the token’s BagsFeeShare, where it accrues in WETH. That full creator side is split among the recipients configured at launch — there is no separate protocol cut taken here. When fees are notified, the contract splits the notified amount:
partnerShare = amount * PARTNER_BPS / 10000     -> claimable[partner]   (if a partner was set)
claimerPot   = amount - partnerShare
each claimer : claimerPot * bps[i] / 10000      (last claimer absorbs rounding dust)
This mirrors the FeeNotified(amount, partnerShare, claimerPot) event: partnerShare + claimerPot == amount. Each recipient’s balance accumulates in claimable[address] until they call claim. This happens automatically as trades occur in both phases — creators earn from bonding-curve trades and pool trades alike.
Post-migration fees first accrue inside the Bags v4 hook and are periodically swept into the fee-share contract. claim pokes that sweep for you, so you don’t need a separate sweep transaction.

2. Read Claimable Fees

Use BagsLens.claimableOf(token, user) for the amount claimable right now (already notified to the fee-share, in WETH wei):
read-claimable.ts
import { publicClient } from "./clients";
import { ROBINHOOD_LAUNCHPAD } from "./addresses";
import { bagsLensAbi } from "./abi";
import type { Address } from "viem";

export async function readClaimable(token: Address, user: Address): Promise<bigint> {
  return publicClient.readContract({
    address: ROBINHOOD_LAUNCHPAD.lens,
    abi: bagsLensAbi,
    functionName: "claimableOf",
    args: [token, user],
  });
}
You can also read directly from the fee-share contract:
CallReturns
feeShare.claimable(user)WETH wei claimable now for user
feeShare.getClaimers()(address[] claimers, uint16[] bps)
feeShare.claimerBps(user)A user’s share in bps
feeShare.PARTNER() / feeShare.PARTNER_BPS()Partner address and its bps
claimableOf counts only WETH already notified to the fee-share. Fees still sitting un-swept in the v4 hook are not included; they become claimable once claim (or another sweep) flushes them.

3. Claim Fees

Call claim(unwrap) on the token’s BagsFeeShare. Pass unwrap: true to receive native ETH; false leaves the payout as WETH.
claim-fees.ts
import "dotenv/config";
import { publicClient, getWalletClient } from "./clients";
import { bagsFeeShareAbi } from "./abi";
import { readClaimable } from "./read-claimable";
import type { Address } from "viem";

export async function claimFees(feeShare: Address, unwrap = true) {
  const walletClient = getWalletClient();
  const txHash = await (async () => {
    // Simulate first so custom errors (e.g. BagsFeeShare_NothingToClaim) decode cleanly.
    const { request } = await publicClient.simulateContract({
      account: walletClient.account,
      address: feeShare,
      abi: bagsFeeShareAbi,
      functionName: "claim",
      args: [unwrap],
    });
    return walletClient.writeContract(request);
  })();

  const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
  if (receipt.status !== "success") throw new Error("Claim reverted.");
  console.log(`Claimed. Tx: https://robinhoodchain.blockscout.com/tx/${txHash}`);
  return { txHash };
}

// Example: resolve feeShare from the token, then claim as native ETH.
async function main() {
  const token = "0xTOKEN_ADDRESS" as Address;
  const user = getWalletClient().account.address;

  const feeShare = await publicClient.readContract({
    address: (await import("./addresses")).ROBINHOOD_LAUNCHPAD.factory,
    abi: (await import("./abi")).bagsFactoryAbi,
    functionName: "feeShareForToken",
    args: [token],
  }) as Address;

  const claimable = await readClaimable(token, user);
  if (claimable <= 0n) {
    console.log("Nothing to claim yet.");
    return;
  }
  console.log(`Claimable: ${claimable} WETH wei`);
  await claimFees(feeShare, true);
}

main().catch(console.error);
Check claimableOf (or feeShare.claimable) is greater than zero before claiming. Calling claim with nothing to claim reverts with BagsFeeShare_NothingToClaim and wastes gas.

4. Estimate Pending (Un-Swept) Fees

For a fuller picture of what a creator will eventually receive, add the un-swept accrual sitting in the v4 hook for the token’s pool. The hook exposes per-pool pending fees; the creator side is half of it, split among the partner (if any) and the claimers by bps.
pending-estimate.ts
import { publicClient } from "./clients";
import { ROBINHOOD_LAUNCHPAD, ROBINHOOD_FEES } from "./addresses";
import { bagsLensAbi, bagsFeeShareAbi, bagsV4HookAbi } from "./abi";
import type { Address } from "viem";

/** Claimable-now plus an estimate of the user's cut of un-swept hook fees. */
export async function readClaimableBreakdown(token: Address, user: Address) {
  const state = await publicClient.readContract({
    address: ROBINHOOD_LAUNCHPAD.lens, abi: bagsLensAbi, functionName: "getTokenState", args: [token],
  });
  if (!state.exists) return null;

  const [claimable, claimers, partnerBps, pool] = await Promise.all([
    publicClient.readContract({ address: ROBINHOOD_LAUNCHPAD.lens, abi: bagsLensAbi, functionName: "claimableOf", args: [token, user] }),
    publicClient.readContract({ address: state.feeShare, abi: bagsFeeShareAbi, functionName: "getClaimers" }),
    publicClient.readContract({ address: state.feeShare, abi: bagsFeeShareAbi, functionName: "PARTNER_BPS" }),
    publicClient.readContract({ address: ROBINHOOD_LAUNCHPAD.hook, abi: bagsV4HookAbi, functionName: "pools", args: [state.poolId] }),
  ]);

  const [addresses, bps] = claimers;
  const i = addresses.findIndex((a) => a.toLowerCase() === user.toLowerCase());
  const userBps = i === -1 ? 0 : bps[i];
  const pendingFees = pool[2]; // un-swept WETH accrual for this pool (both fee legs)

  // On sweep the accrual splits 50/50: half to the platform vault, half is the
  // creator side delivered to the fee-share (there is no separate protocol cut on
  // that leg). A partner, if configured, takes PARTNER_BPS of the creator side
  // before claimers split the remainder by bps. Multiply before dividing to limit
  // truncation. This estimates a claimer's cut; a partner reads their own row.
  const bpsDenom = BigInt(ROBINHOOD_FEES.bpsDenominator);
  const creatorSide = pendingFees / 2n;
  const partnerShare = (creatorSide * BigInt(partnerBps)) / bpsDenom;
  const claimerPot = creatorSide - partnerShare;
  const pendingUserEstimate = (claimerPot * BigInt(userBps)) / bpsDenom;

  return { claimable, pendingFees, userBps, isClaimer: i !== -1, feeShare: state.feeShare, pendingUserEstimate };
}
The exact field layout of the hook’s pools(poolId) return tuple is defined in BagsV4Hook.json; pool[2] is the pending WETH accrual in the reference implementation. Confirm the index against the ABI you import. pendingUserEstimate is an estimate — the exact amount is settled when the sweep runs during a claim.

Troubleshooting

  • BagsFeeShare_NothingToClaim — your claimable balance is zero. Wait for trades to accrue fees (or for a sweep to flush hook fees), and check claimableOf before claiming.
  • BagsFeeShare_NotAuthorized — only the bonding curve and hook may notify fees; you can’t call notifyFee yourself. Just call claim.
  • Claim succeeds but you received WETH, not ETH — you passed unwrap: false. Pass true for native ETH, or unwrap the WETH yourself later.
  • Not a claimer — only the token’s configured claimers and its optional partner accrue fees. Verify eligibility with claimableOf(token, user) (or feeShare.claimable(user)) being greater than zero. Note feeShare.getClaimers() lists only the configured claimers — it does not include the partner.
For the full function, event, and error catalog, see the Contracts Reference.