# Integrate Merkl rewards \[Show your users the extra rewards their Conduit shares earn, and let them claim from Merkl in your app]

[Merkl](https://merkl.xyz) credits extra rewards to each wallet holding your Conduit shares, after any fee set in your [reward policy](/conduits/rewards). Here, each user sees their rewards in your app and claims them in one transaction. For other ways to claim, such as on a user's behalf, see [Merkl's claim documentation](https://developers.merkl.xyz/integrate-merkl/user-rewards).

## How it works

:::steps
### Fetch the user's rewards

Call the Merkl API with the holder's address and the chain your Conduit lives on.

```http [GET]
GET https://api.merkl.xyz/v4/users/{address}/rewards/summary?chainId=8453
```

The response is an array with one entry per chain, each with a `rewards` array. Per token you get:

| Field | Meaning |
|---|---|
| `token` | `address`, `symbol`, `decimals` of the token the claim pays |
| `amount` | Cumulative amount in the live Merkle root, claimed part included |
| `claimed` | Cumulative amount already claimed on-chain |
| `pending` | Earned but not yet in a Merkle root, so not claimable yet |
| `proofs` | Merkle proof to pass to the Distributor unchanged |

Claimable now is `amount - claimed`. Show `pending` separately as "on its way": Merkl publishes a new root every few hours, and `pending` then moves into `amount`. Never display `amount + pending` as claimable.

The figures cover every Merkl campaign the address earns from, Railnet or not. To show only what your Conduit earned, use `GET /v4/users/{address}/rewards/breakdowns`, which splits each token's amount per campaign.

### Claim from the Distributor

Merkl's Distributor is `0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae` on Ethereum, Base and most EVM chains (check [Merkl's contract list](https://developers.merkl.xyz/resources/chains-and-contracts) for others). One `claim` call takes any number of tokens.

```typescript [TypeScript]
import { parseAbi } from 'viem'

const DISTRIBUTOR = '0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae'
const distributorAbi = parseAbi([
  'function claim(address[] users, address[] tokens, uint256[] amounts, bytes32[][] proofs)',
])

const res = await fetch(
  `https://api.merkl.xyz/v4/users/${user}/rewards/summary?chainId=${chainId}`,
)
const [{ rewards }] = (await res.json()).filter((c) => c.chain.id === chainId)

// Keep tokens with something to claim and a proof
const claimable = rewards.filter(
  (r) => r.proofs.length > 0 && BigInt(r.amount) > BigInt(r.claimed),
)

const hash = await walletClient.writeContract({
  address: DISTRIBUTOR,
  abi: distributorAbi,
  functionName: 'claim',
  args: [
    claimable.map(() => user),
    claimable.map((r) => r.token.address),
    claimable.map((r) => BigInt(r.amount)), // cumulative, as returned
    claimable.map((r) => r.proofs),
  ],
})
```

Pass `amount` as returned. It is cumulative, and the Distributor pays the difference with what was already claimed.

### Handle the result

Wait for the receipt and check its status before you update the UI.

```typescript [TypeScript]
try {
  const receipt = await publicClient.waitForTransactionReceipt({ hash })

  if (receipt.status === 'success') {
    // Fresh balances, read on-chain instead of from Merkl's cache
    const refreshed = await fetch(
      `https://api.merkl.xyz/v4/users/${user}/rewards/summary?chainId=${chainId}&reloadChainId=${chainId}`,
    )
    // Show the claimed tokens and the new claimable amounts
  } else {
    // Transaction reverted: show an error and keep the claim button
  }
} catch (error) {
  // Rejected in the wallet or dropped: show an error
}
```

Merkl indexes claims within about five minutes, so a plain refetch still shows the old amounts. `reloadChainId` forces a fresh read of the claim's chain. It is slower, so send it only after a claim. Details: [Merkl cache bypass](https://developers.merkl.xyz/integrate-merkl/user-rewards#cache-bypass-reloadchainid).
:::

## Pitfalls

* **Wrong sender.** The user's own wallet must send the transaction. A claim sent from another address reverts `NotWhitelisted`.
* **Wrong chain.** Claims happen on the reward's distribution chain, which can differ from the Conduit's. Read `chain.id` from the response.
* **Empty proofs or nothing left.** Filter them out, or the whole transaction reverts `InvalidProof`.

## Next steps

<Cards>
  <Card title="Rewards & incentives" description="Choose who gets extra rewards on your Conduit." icon="gift" to="/conduits/rewards" />

  <Card title="Additional rewards" description="Interceptions: how rules route rewards." icon="split" to="/developers/contracts/interceptors" />

  <Card title="Integrate a Conduit" description="Deposits, withdrawals and positions." icon="plug" to="/developers/integrate-conduit" />
</Cards>
