Skip to main content

Overview

The YieldDistributor pays out C0 yield in weekly periods. Each period the operator publishes a merkle root summarizing every holder’s share for the prior week; holders then claim their cut by submitting their leaf and proof against that root. Two function calls cover the holder side:
  • getPeriods — fetch every period the holder has a leaf in, with status, root, and merkle proof. Resolved entirely client-side from your wagmi Config + the public artifact endpoint — no API key needed.
  • claim — submit one period’s claim. The on-chain claim() transfers C0 to the leaf’s account regardless of who signs the transaction.
For the end-to-end walkthrough, see How to claim yield.

How a period works

Every period moves through three phases:
  1. Published — the operator commits a merkle root, exclusion list hash, and total amount on-chain. Claims revert with NotFinalized during this window.
  2. Finalized — after a ~24h replace window, claims open. The operator can no longer replace the root.
  3. Expired — after the claim window closes, unclaimed funds are sweepable to a recipient. Holders can no longer claim that period.
getPeriods applies the same phase rules client-side, so any entry it returns with status: "unclaimed" is safe to submit immediately.

Reads

import { createConfig, http } from "@wagmi/core";
import { mainnet } from "@wagmi/core/chains";
import {
  addresses,
  readYieldDistributorPeriods,
  readYieldDistributorPeriodCount,
  readYieldDistributorIsClaimed,
  readYieldDistributorIsFinalized,
  readYieldDistributorIsExpired,
} from "@camino-treasury/sdk";

const config = createConfig({
  chains: [mainnet],
  transports: { [mainnet.id]: http() },
});

const distributor = addresses[mainnet.id].yieldDistributor!;

// How many periods have ever been published.
const count = await readYieldDistributorPeriodCount(config, { address: distributor });

// Full state for a specific period.
const period = await readYieldDistributorPeriods(config, {
  address: distributor,
  args: [0n],
});
// period: [merkleRoot, exclusionListHash, totalAmount, claimed,
//          startBlock, endBlock, publishedAt, finalizedAt, expiresAt]

// Has a specific leaf been claimed?
const claimed = await readYieldDistributorIsClaimed(config, {
  address: distributor,
  args: [0n, 4n], // periodId, leaf index
});

// Phase checks
const finalized = await readYieldDistributorIsFinalized(config, {
  address: distributor,
  args: [0n],
});
const expired = await readYieldDistributorIsExpired(config, {
  address: distributor,
  args: [0n],
});

Claim

The SDK ships two high-level helpers that wrap proof resolution + the on-chain write:
import { claim, getPeriods } from "@camino-treasury/sdk";

const { data: periods } = await getPeriods({
  config,
  chainId: mainnet.id,
  holder: account.address,
});

for (const p of periods) {
  if (p.status !== "unclaimed") continue;
  const hash = await claim({ config, account, period: p });
  // p carries periodId, index, account, amount, proof — everything the
  // on-chain claim() needs.
}
getPeriods returns { data: Period[], count }. Each Period carries its own status: "claimed" | "unclaimed"; filter client-side for the entries you want to submit. If you want to call the contract directly (custom proof source, calldata-only path, etc.) the wagmi-generated entry point is also exported:
import { writeYieldDistributorClaim } from "@camino-treasury/sdk";

const hash = await writeYieldDistributorClaim(config, {
  account,
  address: distributor,
  args: [
    periodId,        // bigint
    BigInt(index),   // bigint
    holderAccount,   // address that receives the C0
    amount,          // bigint, raw 6-decimal C0
    proof,           // bytes32[]
  ],
});
msg.sender is unconstrained — anyone can submit the claim on a holder’s behalf; the funds always go to the account embedded in the leaf.

Watch events

import {
  watchYieldDistributorClaimedEvent,
  watchYieldDistributorPublishedEvent,
  addresses,
} from "@camino-treasury/sdk";

// Stream every claim that lands.
const unwatchClaims = watchYieldDistributorClaimedEvent(config, {
  address: addresses[mainnet.id].yieldDistributor!,
  onLogs: (logs) => {
    for (const log of logs) {
      const { periodId, index, account, amount } = log.args;
      console.log(`period ${periodId} index ${index}: ${amount}${account}`);
    }
  },
});

// Stream new period publications.
const unwatchPublish = watchYieldDistributorPublishedEvent(config, {
  address: addresses[mainnet.id].yieldDistributor!,
  onLogs: (logs) => console.log("new period:", logs),
});
Claimed, Published, Paused, Unpaused, Swept, and RootReplaced all have matching watchYieldDistributor*Event exports — IDE autocomplete on watchYieldDistributor will list them. Admin-only events (role changes, parameter updates) are filtered out of the SDK surface.

Revert reasons

ErrorMeaning
NotFinalizedPeriod is still in its 24h replace window. Wait for finalization.
ExpiredClaim window has closed. The leaf is no longer claimable.
AlreadyClaimedThis (periodId, index) pair has already been submitted.
InvalidProofProof doesn’t verify against the on-chain root. Check the leaf came from the matching period.
OverAllocatedPer-period cap reached. Indicates a published root with Σ leaves > totalAmount — operator bug.
ZeroAddressThe leaf’s account is the zero address.
EnforcedPauseThe contract is paused.

What’s next