stroid.fun

Construct Fee Claim TX

Build, sign, and send creator fee claim transactions against the FeeHook contracts.

The launchpad's fee hook tracks ethOwed[address] for every wallet that should receive a slice of swap fees — the launcher, every recipient passed in LaunchOpts.creatorRecipients, the partner wallet (when registered), and the protocol fee wallet. Anyone with a non-zero balance calls claim() from their own wallet to pull it.

Network and targets

  • Chain: Ethereum Mainnet (chainId = 1)
  • FeeHookV3 (current): 0x0d62529346ac2c61f5c0582210D01214687bc0CC
  • FeeHookV2 (legacy, kept for V2 tokens): 0x8A22e2A5768C72751F2Da3E3b904365203b100cC

Every token launched after the V3 upgrade routes its swap fees to FeeHookV3. Tokens deployed before then still accrue on FeeHookV2, so creators of older tokens claim there. Check both hooks if you launched in different eras — the function surface is identical so a single ABI works for both.

Function surface

function ethOwed(address who) external view returns (uint256);
function claim() external returns (uint256 amount);
  • ethOwed(addr) — pure read, returns the wei currently claimable by addr from this hook.
  • claim() — pulls the caller's full ethOwed balance into their wallet, sets the ledger entry to 0, and returns the amount transferred. Reverts with nothing to claim if ethOwed(msg.sender) == 0.

Recipients claim themselves

If a token was launched with a non-empty LaunchOpts.creatorRecipients:

  • Each recipient address has its own ethOwed[recipient] entry on the hook.
  • Each recipient signs their own claim() from their own wallet — there's no "send claim to recipients" call. The hook keeps a per-address ledger; nothing is shared.
  • Recipient splits only apply to swap fees accrued after the launch. Swaps from before any recipient change keep accruing under the old split.

The same applies to V3's partner field: if the protocol admin has registered a partner with setPartnerBps(partner, bps), that wallet just calls FeeHookV3.claim() from their own address.

Constructed claim tx shape

to     = FeeHook address (V3 or V2)
value  = 0
data   = selector(claim())
chainId = 1
EIP-1559 fees: maxPriorityFeePerGas, maxFeePerGas

You only pay gas — the call sends no ETH.

ABI fragment

[
  {
    "name": "ethOwed",
    "type": "function",
    "stateMutability": "view",
    "inputs": [{ "name": "who", "type": "address" }],
    "outputs": [{ "name": "", "type": "uint256" }]
  },
  {
    "name": "claim",
    "type": "function",
    "stateMutability": "nonpayable",
    "inputs": [],
    "outputs": [{ "name": "amount", "type": "uint256" }]
  }
]

Worked example (viem) — claim from both hooks

import {
  createPublicClient,
  createWalletClient,
  defineChain,
  http,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const mainnet = defineChain({
  id: 1,
  name: 'Ethereum',
  nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
  rpcUrls: { default: { http: ['https://eth.llamarpc.com'] } },
});

const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({ account, chain: mainnet, transport: http() });

const FEEHOOK_V3 = '0x0d62529346ac2c61f5c0582210D01214687bc0CC' as const;
const FEEHOOK_V2 = '0x8A22e2A5768C72751F2Da3E3b904365203b100cC' as const;

const ABI = [
  {
    name: 'ethOwed',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'who', type: 'address' }],
    outputs: [{ name: '', type: 'uint256' }],
  },
  {
    name: 'claim',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [],
    outputs: [{ name: 'amount', type: 'uint256' }],
  },
] as const;

// 1. Read both balances in parallel.
const [owedV3, owedV2] = await Promise.all([
  publicClient.readContract({ address: FEEHOOK_V3, abi: ABI, functionName: 'ethOwed', args: [account.address] }),
  publicClient.readContract({ address: FEEHOOK_V2, abi: ABI, functionName: 'ethOwed', args: [account.address] }),
]);

console.log('claimable on v3:', owedV3);
console.log('claimable on v2:', owedV2);

// 2. Send one claim tx per non-zero hook (sequential — each tx bumps the nonce).
const txs: `0x${string}`[] = [];

for (const [hook, owed] of [
  [FEEHOOK_V3, owedV3],
  [FEEHOOK_V2, owedV2],
] as const) {
  if (owed === 0n) continue;
  const hash = await walletClient.writeContract({
    address: hook,
    abi: ABI,
    functionName: 'claim',
  });
  await publicClient.waitForTransactionReceipt({ hash });
  txs.push(hash);
}

console.log('claim txs:', txs);

If you only launched after the V3 upgrade you can skip the V2 read + send entirely.

Common claim issues

ProblemCause
nothing to claim revertethOwed(msg.sender) == 0 on the hook you targeted
Tx succeeds but no ETH arrivedYou're checking the wrong hook — read both (V3 and V2)
Wallet on wrong chainSwitch to Ethereum Mainnet (chainId = 1)
Insufficient gas balanceclaim() sends value = 0, but the wallet still pays gas
Expected to claim for a recipient I addedRecipients claim from their own wallet — not the launcher's

On this page