Onchain goals on Robinhood Chain

Count a token buy, a contract call or a holding on Robinhood Chain as the conversion, credited to the variant and the source that brought the wallet.

1visitor lands on /launch from a post on X
the proxy assigns variant b and records source x
2they connect a wallet in your app
your Server Action calls linkWallet(address): ccat_vid now maps to 0xabc...
3they buy the token on its pool
a Transfer of the token to 0xabc... lands on Robinhood Chain (chain 4663)
4the indexer reads the Transfer logs
one eth_getLogs filtered on linked wallets, up to 1,000,000 blocks per pass
5the conversion is credited
variant b gets +1, and attribution shows x as the source of a buyer
Nothing runs in the browser: the link is made on your server, the credit comes from the chain.

Goal types

goalcredited when
onchain:transfer:<token>[:min]A Transfer of the token to a linked wallet, of at least min raw units. A buy on the token's pool is a Transfer to the buyer.
onchain:call:<contract>A transaction from a linked wallet in which the contract emits a log: a mint, a deposit, a swap through a router.
onchain:hold:<token>[:min]The linked wallet's balance is at least min (and above zero).

Robinhood Chain: chain id 4663, RPC https://rpc.mainnet.chain.robinhood.com, explorer robinhoodchain.blockscout.com.

The link is made on your server, so no script is added to the page. In the Server Action that runs after a wallet connects:

"use server";
import { linkWallet } from "@/lib/ccat";

export async function onWalletConnected(address: string) {
  await linkWallet(address); // reads the ccat_vid cookie
}

The indexer

Each pass reads, for every running onchain goal, the logs since the last block it saw: Transfer logs filtered on the linked wallets (up to 1,000,000 blocks per pass), or the contract's logs (up to 50,000 blocks) checked against the sender. Holdings are read with balanceOf. A pass runs on the daily schedule and whenever you run ccat index; each covers the 200 most recently linked wallets of a project.