Indexing

Discover launches and trades from events, and compute price.

Recipe

  1. Discover tokens

    Subscribe to DirectPoolOpened on each network's DirectLaunchFactory. One event per token, with the creator, pool id, name, symbol, and metadata URI.
  2. Read trades

    Subscribe to the Uniswap v4 PoolManager Swap event filtered by the pool ids you stored. This covers trades from Arklis and from any other router.
  3. Attribute fees

    Join FeeSplit from the DirectFeeSplitter in the same transaction for the fee, the attributed trader, and each share.
  4. Price and market cap

    Derive price from sqrtPriceX96 in the latest Swap (or the opening value in DirectPoolOpened). Market cap is price times the fixed supply.

Factory events

DirectLaunchFactorysolidity

event TokenDeployed(address indexed token, address indexed creator);

event DirectPoolOpened(
  address indexed token,
  address indexed creator,
  bytes32 indexed poolId,
  int24 tickLower,
  int24 tickUpper,
  uint160 sqrtPriceX96,
  uint256 supply,
  uint256 lpTokenId,
  string name,
  string symbol,
  string uri
);

event InitialBuy(address indexed token, address indexed buyer, uint256 usdcIn, uint256 tokensOut);
event LpNftToDead(address indexed token, uint256 indexed tokenId);

DirectPoolOpened fields

tokenindexedaddressToken address. Use as the primary key with the chain id.
creatorindexedaddressWallet that launched the token.
poolIdindexedbytes32Uniswap v4 PoolId. Matches the id topic of PoolManager Swap.
tickLowerint24Lowest usable tick for spacing 200.
tickUpperint24Launch config upper tick; also the opening tick.
sqrtPriceX96uint160Opening price.
supplyuint2561,000,000,000 × 10⁶ raw units.
lpTokenIduint256PositionManager NFT id, owned by 0x…dEaD.
name, symbolstringOn-chain values. Immutable.
uristringMetadata JSON URL (the token's tokenURI()).

Trades: PoolManager Swap

IPoolManagersolidity

// Uniswap v4 PoolManager (one contract for every pool)
event Swap(
  bytes32 indexed id,       // poolId from DirectPoolOpened
  address indexed sender,   // router or contract that called swap
  int128 amount0,           // native leg (USDC on Arc, ETH on Robinhood Chain)
  int128 amount1,           // token leg, 6 decimals
  uint160 sqrtPriceX96,     // price after the swap
  uint128 liquidity,
  int24 tick,
  uint24 fee                // 0 — the Arklis fee is taken by the hook
);
  • amount0 < 0 is a buy (native paid in); amount0 > 0 is a sell.
  • Amounts are the pool's side of the swap and exclude the hook fee. With fee as the sum of the matching FeeSplit, a buyer paid |amount0| + fee and a seller received amount0 − fee.
  • sender is the router, not the trader. Use the transaction sender, or FeeSplit.trader (attributed by the router's hookData).
  • The launch's first buy also appears as a Swap with the factory as sender. Count it once: from Swap, or from InitialBuy, not both.

Fees: FeeSplit

DirectFeeSplittersolidity

event FeeSplit(
  address indexed token,
  address indexed creator,
  address indexed trader,
  uint256 creatorFee,
  uint256 referrerFee,
  uint256 cashbackFee,
  uint256 platformFee,
  uint256 calloutFee,
  uint256 nftFee
);
tokenindexedaddressToken traded.
creatorindexedaddressCredited creator.
traderindexedaddressAttributed trader (hookData, else router sender, else tx origin).
creatorFeeuint256Credited to the creator.
referrerFeeuint256Credited to the trader's referrer; 0 if none.
cashbackFeeuint256Credited to the trader.
platformFeeuint256Kept as protocol fees after callout and NFT shares.
calloutFeeuint256Credited to the callout author; 0 if none.
nftFeeuint256Forwarded to the NFT fee vault.
The sum of the six amounts is the full fee for that swap, in native units (18 decimals).

Example

viemts

import { createPublicClient, http, parseAbiItem } from "viem";

const client = createPublicClient({ transport: http("https://rpc.drpc.mainnet.arc.io") });

const FACTORY = "0x0c2179EE85D090fa66148fb6764A71F569cAB0F8";
const POOL_MANAGER = "0x8366a39CC670B4001A1121B8F6A443A643e40951";

const opened = parseAbiItem(
  "event DirectPoolOpened(address indexed token, address indexed creator, bytes32 indexed poolId, int24 tickLower, int24 tickUpper, uint160 sqrtPriceX96, uint256 supply, uint256 lpTokenId, string name, string symbol, string uri)",
);
const swap = parseAbiItem(
  "event Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee)",
);

// 1. Discover launches from the factory.
const launches = await client.getLogs({ address: FACTORY, event: opened, fromBlock, toBlock });
const pools = new Map(launches.map((l) => [l.args.poolId!, l.args.token!]));

// 2. Read trades for those pools from the PoolManager, filtered by poolId topic.
const swaps = await client.getLogs({
  address: POOL_MANAGER,
  event: swap,
  args: { id: [...pools.keys()] },
  fromBlock,
  toBlock,
});

for (const s of swaps) {
  const token = pools.get(s.args.id!);
  const isBuy = s.args.amount0! < 0n; // native paid into the pool
  // price and market cap: see "Price from sqrtPriceX96" below
}

Price from sqrtPriceX96

price.tsts

// currency0 = native (18 decimals), currency1 = token (6 decimals).
// sqrtPriceX96 encodes token1 per token0 in raw units.
const Q96 = 2n ** 96n;

function nativePerToken(sqrtPriceX96: bigint): number {
  const p = Number(sqrtPriceX96) / Number(Q96);   // sqrt(raw token / raw native)
  const rawTokenPerRawNative = p * p;
  const tokenPerNative = rawTokenPerRawNative * 10 ** (18 - 6);
  return 1 / tokenPerNative;                     // native per 1 whole token
}

const marketCap = nativePerToken(sqrtPriceX96) * 1_000_000_000; // supply is fixed

Coverage

For complete Arc history, also index the Genesis 1 factory and fee ledger listed in the Changelog, with the same recipe. Confirm its event ABI on the explorer before decoding.