# What is Fuul?

Fuul is the incentives infrastructure for digital asset companies and fintech platforms. It powers rewards, affiliate, and loyalty programs across blockchains, DeFi applications, exchanges, and fintech products.

With Fuul, projects define incentives for specific onchain or offchain actions — liquidity provisioning, trading, lending, staking, referrals, and more. Fuul handles all the attribution, reward calculation, and distribution automatically.

{% hint style="info" %}
Fuul was accelerated by **a16z Crypto (CSX)** and used by leading companies including **Coinbase**, **Kraken**, **Morpho**, and **dYdX**.
{% endhint %}

## Who is Fuul for?

Fuul serves two types of users:

| User type                         | What they do                                                                                          |
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Projects** (B2B clients)        | Growth and product teams that create, manage, and optimize incentive programs through the Fuul webapp |
| **End Users** (reward recipients) | Users of your product who earn and claim rewards through your incentives hub                          |

### Industries we serve

| Vertical                  | Common use cases                                                       |
| ------------------------- | ---------------------------------------------------------------------- |
| **L1/L2 Ecosystems**      | Bridging incentives, liquidity programs, points-to-token distributions |
| **DeFi Protocols**        | TVL growth, lending/borrowing rewards, referral incentives             |
| **Trading Platforms**     | Affiliate programs, trading competitions, rebate systems               |
| **Centralized Exchanges** | Referral programs, loyalty systems, trading rewards                    |
| **Fintech & Neobanks**    | Cashback, loyalty programs, partner-driven acquisition                 |

## How it works — in a nutshell

1. **Define conversion events** — Choose what actions earn rewards (LP deposits, trades, referrals, custom onchain or offchain events)
2. **Set up incentive rules** — Configure payouts as fixed amounts, variable percentages, or pool distributions in tokens or points
3. **Deploy a budget** (token rewards only) — Fund your program through a non-custodial smart contract you fully control
4. **Launch your hub** — Go live with a no-code landing page or build a white-label experience with the SDK
5. **Track and optimize** — Monitor performance with analytics, detect fraud with built-in sybil resistance, and iterate

## What makes Fuul different?

| Capability                   | Description                                                                                                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **End-to-end lifecycle**     | Points programs, token launches, LP incentives, referrals, and affiliates — all in one platform. No need to stitch multiple tools together.                  |
| **Enterprise flexibility**   | Granular targeting, custom reward logic, multiple concurrent programs, and dynamic changes — not cookie-cutter templates                                     |
| **30+ trigger integrations** | Pre-built integrations with leading DeFi protocols (concentrated and constant AMMs, lending, staking, token holders) plus custom onchain and offchain events |
| **Affiliate infrastructure** | Multi-level referrals, sub-affiliate onboarding, revenue sharing, and custom commission logic                                                                |
| **Fraud prevention**         | Wallet clustering, wash trading detection, self-referral prevention, and behavioral filtering to protect your budget                                         |
| **Non-custodial**            | Token reward programs run on their own smart contract. Your project is the sole administrator — Fuul never has access to your funds                          |
| **No-code and white-label**  | Launch in minutes with no-code templates, or build a fully branded experience with the SDK and APIs                                                          |

{% hint style="success" %}
Fuul's core thesis: **incentives are not campaigns — they are infrastructure.** As programs grow, they require constant iteration, measurement, and operational reliability. Fuul provides this as a platform so your engineering team can focus on building your core product.
{% endhint %}


# Why Use Fuul?

## The problem

Incentive programs are essential growth infrastructure — they drive acquisition, engagement, liquidity, and retention. Most companies start by building these systems in-house because they seem simple at first.

As programs grow, they become increasingly complex. New behaviors need to be tracked, reward structures evolve, abuse must be mitigated, and programs require constant iteration. What starts as a growth feature becomes an engineering burden.

This creates two structural problems:

| Problem                                                                 | Impact                                                                |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **Go-to-market teams depend on engineering** for every incentive change | Slows experimentation, reduces momentum, and delays time-to-market    |
| **Engineering teams get pulled away** from building the core product    | Maintaining incentive systems that constantly evolve drains resources |

Without dedicated infrastructure, incentive programs end up being manually operated, spreadsheet-driven, difficult to measure, hard to scale, and vulnerable to abuse.

{% hint style="warning" %}
Building in-house is the most common alternative — and the most expensive. Internal incentive infrastructure typically costs **$50k–200k+** to build and maintain, and requires ongoing engineering investment as programs evolve.
{% endhint %}

## How Fuul solves this

### Cross-chain and fully composable

Fuul enables projects to deploy incentive programs that operate across multiple EVM-compatible chains as well as SVM-based networks like Solana and Fogo. Listen to conversion events on one chain and pay out rewards on another.

Fuul comes with 30+ pre-built integrations with leading DeFi protocols — concentrated liquidity AMMs, constant product pools, lending protocols, staking, token holders, and more. You can also define custom onchain or offchain events.

Learn more about [Trigger Integrations](/core-concepts/trigger-integrations).

### White-label and no-code options

| Option          | Best for                        | What you get                                                               |
| --------------- | ------------------------------- | -------------------------------------------------------------------------- |
| **No-code**     | Teams that want to go live fast | Templates and drag-and-drop editor — launch in minutes, no dev work needed |
| **White-label** | Teams that want full control    | APIs and SDK to build a fully branded incentives hub inside your own app   |

Learn more about the [Incentives Hub](/incentives-manager/incentives-hub).

### Sybil-resistant

Fuul's fraud prevention tools ensure that only legitimate users receive rewards, protecting your budget and maintaining program integrity.

| Protection                      | How it works                                                                                                         |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Behavioral cluster detector** | Proprietary ML model that analyzes 30+ onchain behavior signals to identify wallets likely operated by the same user |
| **Self-referral detector**      | Detects self-referrals across multiple wallets by the same user — flagged and excluded from distributions            |
| **Wallet screening**            | Checks if wallets are allowed to receive payouts at the time of movement creation                                    |
| **Blacklisting**                | Exclude specific addresses from earning rewards in your program                                                      |
| **Payout caps**                 | Limit excessive rewards and safeguard your program from abuse                                                        |

Learn more about [Fraud Prevention](/core-concepts/fraud-prevention).

### Non-custodial

For token reward programs, each program is powered by its own smart contract, deployed directly through the Fuul webapp — no coding required.

{% hint style="success" %}
Your project is the **sole administrator** of its smart contract, with full control over its incentives budget. Fuul never has access to your funds. Points-only programs are fully offchain and don't require a smart contract.
{% endhint %}

### Speed without tradeoffs

Because the infrastructure layer already exists, programs can be deployed in days instead of weeks — while maintaining full flexibility for future iteration. No need to choose between speed and customizability.


# Quickstart

This guide gets you from zero to a running incentive program. Follow the common setup, then jump to the guide for your specific use case.

## 1. Create your account

* Go to [app.fuul.xyz](https://app.fuul.xyz) and sign up with **email or Google**
* Enter the basic details about your project — name, website, and other relevant information
* Complete your project's profile (category, image, and description)

{% hint style="warning" %}
Dashboard signup and login accept email or Google only. There is no wallet-connect option, and an account created with a wallet and no email address cannot sign in.

Your wallet is still used for every onchain action — budget deposits on EVM and Solana, protocol fee deposits, contract deploys, and claiming. This applies to the dashboard login only.
{% endhint %}

{% hint style="info" %}
You can create a test project first to explore the platform before setting up your real program.
{% endhint %}

## 2. Select how you're going to reward participants

* Choose between **Points** or **Tokens** — you can create different incentive programs later
* If you select tokens, choose the network you want to use from the dropdown

## 3. Set up your trigger

* Select the type of trigger (onchain, offchain, subgraph, etc.)
* For custom onchain triggers, select from any EVM chain or Solana (you can also use a testnet like Base Sepolia)
* Enter the smart contract address — Fuul will automatically look up all available events and functions
* Select which event or function to use as the trigger for your rewards
* Define how Fuul should interpret the transaction volume, revenue calculation, and currency
* Click **Save**

## 4. Set up your rewards

* Choose the conversion you've just created
* Select who to reward: **Referrers**, **End Users**, or **both**
* Configure the reward structure:
  * Payment type: Fixed amount or Variable (percentage of conversion volume)
  * Set the amount
* Hit **Create**

## 5. Finalize and launch

* Click **Publish** to save your configuration
* For token reward programs, go to **Initialize program** to deploy the program onchain

{% hint style="success" %}
For token rewards, deploying the program creates a smart contract that **you fully control**. Fuul never has access to the funds you deposit into your program's contract. Points-only programs don't require this step.
{% endhint %}

{% embed url="<https://drive.google.com/file/d/17OhT3GA70yKpaiQDenWvqjqQ4T-5jcuh/view?usp=sharing>" %}

## 6. Add a budget (token rewards only)

Fund your program with the tokens you'll distribute as rewards. See [How to Add a Budget](/getting-started/how-to-add-a-budget-in-fuul) for the step-by-step guide.

{% hint style="info" %}
Points-only programs skip this step — no smart contract or budget needed.
{% endhint %}

## 7. Get your API keys

Go to **Settings > API keys** in the dashboard and click **New API Key**. You'll need different keys depending on your integration:

| Key type              | Use for                                           |
| --------------------- | ------------------------------------------------- |
| `send:tracking_event` | Frontend — tracking referrals and displaying data |
| `send:trigger_event`  | Backend — sending custom events                   |
| `read-only`           | Frontend — display-only (leaderboards, rewards)   |

See [API Key Management](/developer-guide/api-key-management) for details.

## 8. Choose your path

Pick the guide that matches what you're building:

| I want to...                             | Start here                                                                                          |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Set up a referral/affiliate program**  | [Tutorial: Referral Program with Token Rewards](/tutorials/referral-program-tutorial)               |
| **Run a points-based loyalty program**   | [Tutorial: Points Program with Leaderboard](/tutorials/points-program-tutorial)                     |
| **Incentivize DeFi liquidity providers** | [Tutorial: Incentivize DeFi LPs](/tutorials/defi-incentives-tutorial)                               |
| **Launch quickly with no code**          | [No-Code Setup](https://github.com/fuul-automation/gitbook-docs/tree/dev/incentives-hub/no-code.md) |
| **Build a custom-branded hub**           | [Build Your Incentives Hub](/developer-guide/build-your-incentives-hub)                             |

## 9. Install the SDK (if building custom)

If you're building a custom integration (not using the no-code hub):

```bash
npm install @fuul/sdk
```

```typescript
import { Fuul } from '@fuul/sdk';

Fuul.init({ apiKey: 'your-api-key' });
```

{% hint style="warning" %}
**Next.js App Router:** Initialize in a client component (`'use client'`).
{% endhint %}

The tutorials above walk you through the full SDK integration for each use case.


# Use Cases

Fuul supports a wide range of incentive programs tailored to different project needs. Here are the most common applications:

## DeFi Incentives

### Liquidity Incentives

Reward liquidity providers across concentrated liquidity AMMs (e.g., Uniswap V3) and constant product pools (e.g., Uniswap V2). Set daily, weekly, or monthly reward pools distributed proportionally based on liquidity provisioned.

{% hint style="success" %}
**Case study:** [Resolv boosted user acquisition with points and referrals](https://www.fuul.xyz/case-studies/resolv-boosting-user-acquisition-with-points-and-referrals)
{% endhint %}

### Lending & Borrowing Incentives

Incentivize lending and borrowing activity on any protocol. Reward participants based on overall volume or net positions.

{% hint style="success" %}
**Case study:** [HypurrFi migrated its rewards infrastructure in 7 days](https://www.fuul.xyz/case-studies/how-hypurrfi-migrated-its-rewards-infrastructure-in-7-days)
{% endhint %}

### Trading Competitions

Boost trading volumes for specific tokens across specific DEXes. Distribute incentives based on volume or P\&L leaderboard positions.

{% hint style="success" %}
**Case study:** [Hibachi scaled perps trading volume with points, competitions, and multi-level referrals](https://www.fuul.xyz/case-studies/hibachi-scaling-perps-trading-volume-with-points-competitions-and-multi-level-referrals)
{% endhint %}

### Token Holder Rewards

Reward users periodically based on how long they hold your token. Fuul integrates with DeFi protocols to ensure composability — users holding tokens in vaults, pools, or staking contracts are still tracked.

### Staking Incentives

Deploy staking rewards distributed periodically through multiple snapshots based on staked amounts and time.

{% hint style="success" %}
**Case study:** [SatLayer drove BTC deposits through strategic incentives](https://www.fuul.xyz/case-studies/satlayer-driving-btc-deposits-through-strategic-incentives)
{% endhint %}

### Minting

Incentivize users with extra yield when minting your stablecoin. Fuul integrates with DeFi protocols to ensure composability without compromising incentives.

{% hint style="success" %}
**Case study:** [HaloFi achieved 13x user growth by incentivizing stablecoin minting](https://www.fuul.xyz/case-studies/halofi-13x-increase-in-the-number-of-users-depositing-with-a-groundbreaking-points-program)
{% endhint %}

## Growth & Acquisition

### Affiliate & Referral Programs

Align incentives with affiliates and key opinion leaders (KOLs). Set up customizable affiliate tiers and payout structures — fixed rewards per sign-up, revenue sharing, or multi-level commission structures.

{% hint style="success" %}
**Case study:** [dYdX powers VIP affiliate growth with Fuul](https://www.fuul.xyz/case-studies/dydx-powering-vip-affiliate-growth-with-fuul)
{% endhint %}

### Points Programs

Run points-based incentive programs to reward user engagement before a token launch. Points can be distributed for any tracked action and used to build leaderboards, tier systems, and airdrop eligibility.

### Quests & Social Engagement

Incentivize social actions like following on X, posting content, completing Galxe or Zealy quests, and GitHub contributions. Combine social triggers with onchain actions for comprehensive engagement programs.

{% hint style="success" %}
**Case study:** [Recall scaled participation to 900K by unifying Galxe, Zealy, and campaigns into a single points program](https://www.fuul.xyz/case-studies/recall-scaling-participation-to-900k-by-unifying-galxe-zealy-and-campaigns-into-an-incentives-points-program)
{% endhint %}

## Any Other Use Case

Fuul's flexible trigger system means you can incentivize virtually any action:

* **Custom onchain events** — Set up conversion events using any smart contract with emitted events or functions
* **Custom offchain events** — Integrate any event via the Fuul API (sign-ups, deposits, KYC completion, etc.)
* **Dune queries** — Use Dune analytics data as a trigger source for protocols without APIs or subgraphs

{% hint style="info" %}
Fuul supports **35+ networks** including Ethereum, Arbitrum, Base, Optimism, Polygon, Solana, HyperEVM, Berachain, Stellar, Stable, and many more. A few are tracking-only and cannot pay out onchain — see [Trigger Integrations](/core-concepts/trigger-integrations) for the full list and the caveats.
{% endhint %}


# How Integration Works

{% hint style="info" %}
**TLDR:** Most projects can launch an incentive program on Fuul with zero code. Developer integration is only needed for advanced use cases like referral tracking or white-label hubs.
{% endhint %}

## What do I need to integrate?

It depends on what you want to do. Here's a quick guide:

| What you want to do                                                  | Integration needed                      | Details                                                                     |
| -------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------- |
| Incentivize onchain actions (LP, trading, staking, lending, holding) | **None** — configure through the webapp | Fuul listens to onchain events automatically via 30+ pre-built integrations |
| Track custom offchain events (sign-ups, purchases, etc.)             | **API** — send events to Fuul           | Your backend sends events via the Fuul API                                  |
| Track referrals in your app                                          | **SDK** — add the Fuul Web SDK          | A few lines of code to track pageviews and wallet connections               |
| Build a white-label incentives hub                                   | **SDK + API** — build with Fuul's tools | Full control over the user experience using the SDK and API                 |
| Launch a no-code incentives page                                     | **None** — use Fuul's hosted solution   | Built-in templates and drag-and-drop editor                                 |

## No-code setup

All incentive rules are configured using the [Fuul Incentives Manager](https://app.fuul.xyz). Through this platform, you can:

1. **Define trigger events** — Choose from 30+ pre-built integrations or create custom triggers
2. **Set up incentive distribution rules** — Fixed, variable, or pool-based payouts in tokens or points
3. **Manage budgets** (token rewards only) — Deploy and fund your program's smart contract
4. **Access reporting** — Understand ROI and identify key users

No developer resources required.

## Multi-chain support

Fuul supports conversion events across all EVM and SVM compatible chains, including Layer 1 and Layer 2 solutions. You can listen for events on one chain and distribute rewards on another — giving you full flexibility for multi-chain ecosystems.

## Reward options

| Reward type       | Description                                                                    |
| ----------------- | ------------------------------------------------------------------------------ |
| **Points**        | Offchain points for engagement programs, leaderboards, and airdrop eligibility |
| **Tokens**        | Any ERC-20 token on supported chains                                           |
| **Native tokens** | ETH, SOL, and other native currencies                                          |

{% hint style="info" %}
To use your token for onchain payouts, Fuul requires a token whitelisting process. Contact <ecosystem@fuul.xyz> for details.
{% endhint %}

## Next steps

Ready to launch? Follow the [Quickstart](/getting-started/quickstart) to create your first program.

Want to integrate the SDK? Head to the [Developer Guide](/developer-guide/getting-started-with-fuul-web-sdk).


# How to Add a Budget in Fuul

Adding a budget is a key step for incentive programs that distribute token rewards. Fuul uses non-custodial smart contracts to hold your program's funds, so you always retain full control.

{% hint style="info" %}
Budgets are only required for **token rewards**. Points-only programs don't need a smart contract or budget.
{% endhint %}

### 1. Access the Fuul dashboard

Log in to your Fuul account at [app.fuul.xyz](https://app.fuul.xyz) and navigate to your project's dashboard.

### 2. Go to the budget section

From the dashboard, locate the budget management area on the left sidebar under the **Budgets** tab.

### 3. Set up a new budget

Click **Add Budget** and choose the currency for the budget (USDC or other supported tokens).

### 4. Specify the budget amount

Enter the amount you want to deposit into the program.

### 5. Deposit the protocol fee

You'll see the Fuul protocol fee. Click **Deposit Protocol Fee** and confirm the transaction in your wallet.

### 6. Deposit funds to the contract

Click **Deposit Budget** and confirm the transaction. Your funds are sent directly to the program's smart contract and are immediately available for rewards.

{% hint style="info" %}
You can also fund the contract by sending tokens directly from any wallet or multisig — no additional approval steps needed.
{% endhint %}

{% hint style="warning" %}
If the smart contract does not have sufficient balance, reward claims will fail. You can add more funds or withdraw at any time.
{% endhint %}

### 7. Monitor your budget

After setup, you can monitor the budget's performance and remaining balance directly from the dashboard.

{% hint style="info" %}
You may need to refresh the page for the deposited funds to appear in the contract balance.
{% endhint %}

{% embed url="<https://drive.google.com/file/d/1Pc8yB9U64QkMCecVUXfCjAEPjBBR2DZz/view?usp=sharing>" %}


# Types of Rewards

Fuul supports two reward types, each with different distribution mechanics:

|                  | Tokens                                                                                                                    | Points                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Distribution** | Onchain via voucher system                                                                                                | Offchain, automatic                  |
| **Claiming**     | User claims with an onchain transaction. Projects can also claim on behalf of users or sponsor gas for claim transactions | No claim needed — credited instantly |
| **Networks**     | Any EVM or SVM chain                                                                                                      | Offchain (no chain required)         |
| **Gas costs**    | User pays gas to claim (unless the project sponsors gas or claims on behalf)                                              | None                                 |
| **Data access**  | SDK + Subgraph                                                                                                            | SDK only                             |

## Tokens (Onchain Rewards)

Token rewards are distributed using a voucher-based claiming system powered by Fuul's V2 protocol contracts.

### How it works

1. **User performs a qualifying action** (swap, deposit, referral, etc.)
2. **Fuul calculates the reward** based on your program's incentive rules
3. **A signed claim check (voucher) is generated** — containing the recipient address, token amount, and cryptographic signature
4. **User claims onchain** by submitting a transaction that verifies the signature and releases the tokens from your program's smart contract

{% hint style="success" %}
Projects can also **claim on behalf of users**, sending rewards directly to user wallets without requiring any action from the end user. This can be done from the dashboard or via the contract.
{% endhint %}

### Benefits of the voucher model

| Benefit                 | Description                                                                |
| ----------------------- | -------------------------------------------------------------------------- |
| **No wasted budget**    | Unclaimed rewards stay in your contract — no tokens are sent until claimed |
| **Budget recovery**     | Reclaim unused funds by setting expiration periods on claim checks         |
| **Budget reallocation** | Recovered funds can be redirected to other campaigns                       |
| **Gas efficiency**      | No gas spent on rewards that are never claimed                             |

### Approval mode

Administrators can enable **Pending Approval** mode to review rewards before they become claimable:

1. Reward is calculated and set to **Pending** status
2. Admin reviews the reward in the dashboard
3. Admin approves or rejects
4. If approved, the voucher is generated and the user can claim

{% hint style="info" %}
Approval mode is useful for high-value rewards, new programs, or when you want manual oversight before distributing funds.
{% endhint %}

## Points (Offchain Rewards)

Points are an offchain rewards system that doesn't require blockchain transactions. They're ideal for engagement programs, pre-token-launch campaigns, and building leaderboard-based competitions.

### How it works

1. **User performs a qualifying action**
2. **Points are calculated and credited automatically** — no claim transaction needed
3. **User's balance is updated instantly** — visible in leaderboards and the SDK

### Key characteristics

| Feature                    | Detail                                                                          |
| -------------------------- | ------------------------------------------------------------------------------- |
| **Automatic distribution** | Points are credited immediately when a qualifying action is detected            |
| **No gas costs**           | Users don't pay any transaction fees                                            |
| **Integer values only**    | Points are always whole numbers — decimals are not supported                    |
| **Rounding**               | Values below 1 round to 0 (no points awarded); values 1 or above are rounded up |

### Points airdrops

Points can also be distributed directly via CSV upload, bypassing the normal event pipeline. This is useful for:

* Retroactive rewards for past activity
* Migrating from other rewards systems
* Manual bonus distributions
* One-time promotional airdrops

{% hint style="info" %}
Both reward types can be queried through the [Fuul SDK](/developer-guide/getting-started-with-fuul-web-sdk). Token rewards can additionally be queried via [subgraphs](/protocol-reference/subgraphs) for advanced filtering and historical data.
{% endhint %}


# NFTs

Fuul supports NFTs (ERC-721 and ERC-1155) as on-chain rewards. Like ERC-20 token rewards, NFTs are distributed through the voucher-based claiming system.

## How it works

The NFT reward flow mirrors the standard token flow:

1. User performs a qualifying action
2. Fuul issues a signed claim check for the specific token ID
3. User submits a claim transaction to the Fuul contract
4. The NFT is transferred from the project's contract to the user's wallet

The claim check includes a `token_id` field specifying which NFT the user is entitled to claim.

## Token type reference

| Token type | Value | Description                             |
| ---------- | ----- | --------------------------------------- |
| `ERC20`    | `1`   | Fungible tokens                         |
| `ERC721`   | `2`   | Non-fungible tokens (unique items)      |
| `ERC1155`  | `3`   | Multi-token standard (editions, badges) |

## Use cases

* **Exclusive membership NFTs** — reward early adopters or top users with a unique collectible
* **Achievement badges** — mint on-chain badges for reaching milestones (e.g., first trade, 100 referrals)
* **Access passes** — NFTs that unlock features or content in your protocol
* **Event participation** — distribute NFTs to users who participated in a specific campaign

{% hint style="info" %}
Claiming NFT rewards works identically to claiming ERC-20 tokens. Users submit a transaction with their signed claim check. See [Claiming Onchain Rewards](/developer-guide/claiming-onchain-rewards/evm) for the full implementation guide.
{% endhint %}


# Email-Based Payouts

Fuul supports sending rewards to users identified by email address — no wallet required at the time of conversion. This enables incentive programs for audiences that are not crypto-native.

## How it differs from other reward types

|                                    | Tokens (onchain) | Points (offchain)       | Email-based                 |
| ---------------------------------- | ---------------- | ----------------------- | --------------------------- |
| **User identifier**                | Wallet address   | Wallet address or email | Email address               |
| **Wallet required at conversion?** | Yes              | No (if using email)     | No                          |
| **Wallet required to claim?**      | Yes              | No                      | Yes (mapped later)          |
| **Distribution**                   | Onchain voucher  | Automatic               | Held until wallet is mapped |

## How it works

1. **User performs a qualifying action** — identified by their email address
2. **Fuul calculates the reward** — a pending payout is created with the email as the recipient
3. **Payout waits for wallet mapping** — it stays pending until a wallet address is mapped to the email
4. **Project creates an email-to-wallet mapping** — via the API or dashboard
5. **Rewards are resolved** — pending payouts are transferred to the mapped wallet address
6. **User claims** — the user can now claim their onchain rewards normally

## Setting up email-based payouts

Email-based payouts work with any trigger type. When sending custom events, use `email` as the `identifier_type`:

```json
{
  "name": "purchase_completed",
  "user": {
    "identifier": "user@example.com",
    "identifier_type": "email"
  },
  "dedup_id": "order-12345"
}
```

## Resolving email recipients

When rewards are created for email identifiers, they remain pending until you create a mapping. Use the recipient resolution endpoints to manage this:

Check which emails have pending payouts ([API reference](https://fuul.readme.io/reference/get_v1-payouts-pending-recipient-resolution)):

```typescript
// GET /v1/payouts/pending-recipient-resolution
// Returns: [{ email_identifier, pending_movements_count }]
```

Create mappings to resolve pending payouts to wallet addresses ([API reference](https://fuul.readme.io/reference/post_v1-payouts-recipient-mappings)):

```typescript
// POST /v1/payouts/recipient-mappings
// Body: {
//   mappings: [{
//     source_identifier: "user@example.com",
//     target_identifier: "0x1234...",
//     target_identifier_type: "evm_address"
//   }]
// }
```

{% hint style="info" %}
You can send up to 100 mappings per request. Once a mapping is created, pending payouts for that email are resolved to the target address.
{% endhint %}

## Use cases

| Use case                  | Description                                                                             |
| ------------------------- | --------------------------------------------------------------------------------------- |
| **Fintech & exchanges**   | Reward users who sign up or trade using their email, map wallets when they complete KYC |
| **CEX referral programs** | Track referrals by email, distribute token rewards after users create wallets           |
| **Pre-launch campaigns**  | Collect emails during waitlist phase, distribute rewards when users onboard             |
| **Non-crypto audiences**  | Run incentive programs for users who don't yet have wallets                             |

{% hint style="warning" %}
Email identifiers are normalized (lowercased) before processing. Ensure consistency when creating mappings.
{% endhint %}


# Automated Claims

Token rewards normally sit as claim checks until someone submits a transaction. Automated Claims closes that loop for you: on a schedule you set, Fuul submits the claim transactions for your users and sponsors the gas, so recipients receive tokens without doing anything.

{% hint style="success" %}
Tokens settle **directly to the recipient** in each claim check. Fuul never takes custody of your funds at any point in the process.
{% endhint %}

## Three ways tokens reach a wallet

Automated Claims is one of three options. They are not exclusive, and most programs use more than one:

| Option               | Who submits the transaction | Who pays gas             | Best for                                                             |
| -------------------- | --------------------------- | ------------------------ | -------------------------------------------------------------------- |
| **User claims**      | The end user                | The end user             | Default. Users who are already onchain-native                        |
| **Claim on behalf**  | Your project, ad hoc        | Your project             | One-off pushes, a campaign wrap-up, VIP users                        |
| **Automated Claims** | Fuul, on a schedule         | Fuul, billed back to you | Ongoing programs where you want claiming to disappear as a user step |

{% hint style="warning" %}
Claim checks do **not** distribute automatically unless one of these is in place. A common misconception is that payouts are pushed to wallets by default. They are not: a reward becomes a claim check, and a claim check needs a transaction.
{% endhint %}

## How it works

1. Your project opts in and Fuul approves it
2. You set a cadence, plus a day of the week for weekly runs
3. On each scheduled run, Fuul collects the project's available claim checks, batches them into claim transactions, and submits them onchain
4. Each run records which users were claimed for, the per-currency amounts distributed, and what the run cost

Automated Claims sits on top of the existing claim pipeline. It does not change how payouts or claim checks are created, only how they get redeemed. See [Types of Rewards](/core-concepts/types-of-rewards) for how claim checks are generated in the first place.

## Configuration

Projects configure this from the **Automated Claims Hub**, at **Activity → Reward Payouts → Automate claims**.

| Setting            | Description                                                                          |
| ------------------ | ------------------------------------------------------------------------------------ |
| **Master toggle**  | Turns automated claiming on or off for the project                                   |
| **Cadence**        | How often runs happen. Weekly runs also take a day of the week                       |
| **Native fee cap** | Upper bound on native token spend per run, so a single run cannot exceed your budget |

The Hub also shows metric cards and a paginated run history, with the claim batches for each run.

{% hint style="info" %}
Enabling the toggle is not sufficient on its own. A run executes only once the project is both enabled and approved by Fuul. Contact <ecosystem@fuul.xyz> to get set up.
{% endhint %}

## Costs

Fuul sponsors two things per transaction: the network gas, and the protocol's `nativeUserClaimFee`. Both are metered per run, converted to USD, and accumulated into a monthly per-project total for billing and reconciliation.

Your `native_fee_cap` bounds native spend per run. If a run would exceed it, it stops at the cap rather than continuing.

## Availability and limitations

|                       | Status                                                                                                          |
| --------------------- | --------------------------------------------------------------------------------------------------------------- |
| **Networks**          | EVM only                                                                                                        |
| **Per-currency caps** | Not available. The cap applies to native spend per run, not to how much of any given reward currency goes out   |
| **Cost reporting**    | Run cost is shown as a single combined figure. Gas and the protocol fee are not broken out separately in the UI |

{% hint style="warning" %}
If your program pays on a chain where no project has run the relayer before, treat it as a first-time enablement rather than a toggle. Confirm timing with the Fuul team before committing a launch date to your users.
{% endhint %}

## Related

* How claim checks are created and what the voucher model buys you → [Types of Rewards](/core-concepts/types-of-rewards)
* Keeping the pool funded so runs don't fail → [Budgets & Smart Contracts](/core-concepts/budgets-and-smart-contracts) and [Rewards Pool Budget Alerts](/core-concepts/budgets-and-smart-contracts/budget-alerts)
* Building your own claim button instead → [Claim Flow Integration](/developer-guide/claim-frontend)


# Trigger Integrations

Triggers are the events that qualify users for rewards. Fuul supports 30+ pre-built integrations with leading DeFi protocols, plus the ability to create custom triggers for any onchain or offchain action.

## Integration types

Triggers work differently depending on the data source and timing:

| Type                    | How it works                                      | Timing                    | Best for                                                    |
| ----------------------- | ------------------------------------------------- | ------------------------- | ----------------------------------------------------------- |
| **Native integrations** | Pre-built connectors for popular protocols        | Scheduled (usually daily) | Supported DeFi protocols (AMMs, lending, staking)           |
| **Custom onchain**      | Monitor any smart contract event or function call | Real-time                 | Swaps, mints, deposits, or any onchain action               |
| **Custom offchain**     | Receive events via the Fuul API or CSV upload     | Real-time                 | Sign-ups, purchases, social actions, or any off-chain event |
| **Token holders**       | Track balances of any token (ERC-20, NFT, SPL)    | Scheduled (usually daily) | Holding rewards, governance token programs                  |

## Which integration should I use?

| Your use case                                              | Recommended integration                     |
| ---------------------------------------------------------- | ------------------------------------------- |
| Action is atomic and irreversible (swaps, mints)           | **Custom onchain**                          |
| Value is time-dependent (deposits, LP positions, holdings) | **Native integration** or **Token holders** |
| Supported protocol (Uniswap, Morpho, etc.)                 | **Native integration** (pre-built, best UX) |
| You track actions in your own backend                      | **Custom offchain** (API events)            |
| You need daily aggregated data from a partner              | **Custom offchain** (scheduled API)         |

## Pre-built integrations

Fuul natively supports the following categories of protocols. Each is described in detail in the pages below.

{% content-ref url="/pages/4SiyjDFfN2ZtUnPA4riL" %}
[1️⃣ CLAMM LPs (e.g. Uniswap V3)](/core-concepts/trigger-integrations/clamm-lps-e.g.-uniswap-v3)
{% endcontent-ref %}

{% content-ref url="/pages/OtiPDRFL36ZiaR2Ncs1A" %}
[2️⃣ Constant LPs (e.g., Uniswap V2)](/core-concepts/trigger-integrations/constant-lps-e.g.-uniswap-v2)
{% endcontent-ref %}

{% content-ref url="/pages/iS1e73vJVJt6GnlnxDpV" %}
[3️⃣ Lending & Borrowing](/core-concepts/trigger-integrations/lending-and-borrowing)
{% endcontent-ref %}

{% content-ref url="/pages/TUIpF6k8fbZtJFBvF6rc" %}
[4️⃣ Staking](/core-concepts/trigger-integrations/staking)
{% endcontent-ref %}

{% content-ref url="/pages/L7CyL3InLnwm4pYsOmK2" %}
[5️⃣ Token Holders](/core-concepts/trigger-integrations/token-holders)
{% endcontent-ref %}

{% content-ref url="/pages/0ME5kEmzRkZshWNXiiOX" %}
[6️⃣ Custom Onchain Events](/core-concepts/trigger-integrations/custom-onchain-events)
{% endcontent-ref %}

{% content-ref url="/pages/c7WYSy9QupFKfXp4ZHL0" %}
[7️⃣ Custom Offchain Events](/core-concepts/trigger-integrations/custom-offchain-events)
{% endcontent-ref %}

{% content-ref url="/pages/0QGOhllAPHCR3yPfUiQ2" %}
[8️⃣ Trading](/core-concepts/trigger-integrations/trading)
{% endcontent-ref %}

{% content-ref url="/pages/O0jnapR0LWkukqUDemO9" %}
[9️⃣ Yield (Pendle)](/core-concepts/trigger-integrations/yield-pendle)
{% endcontent-ref %}

{% content-ref url="/pages/atLxdVVu34BGZnDAOjlF" %}
[Quests & Social](/core-concepts/trigger-integrations/quests-and-social)
{% endcontent-ref %}

{% content-ref url="/pages/oQbqRzS7BSeM5fHZzjgF" %}
[1️⃣1️⃣ Prediction Markets (Polymarket)](/core-concepts/trigger-integrations/prediction-markets-polymarket)
{% endcontent-ref %}

## Supported networks

Fuul supports triggers across **35+ networks** including Ethereum, Arbitrum, Base, Optimism, Polygon, Solana, HyperEVM, Berachain, Monad, Stellar, Stable, and many more.

{% hint style="warning" %}
**Tracking a network is not the same as paying out on it.** Two networks are currently tracking-only:

* **Stellar** — a `stellar_address` can accept referral codes, be attributed conversions, rank on leaderboards, and earn points, but cannot receive an onchain token payout. See [Stellar signatures](/developer-guide/tracking-referrals-in-your-app#stellar-signatures).
* **Stable** (chain 988) — selectable for token holder triggers and in the audiences token balance segment builder. No V2 contracts are deployed there, so wallet connection, budget deposits, and claims are unavailable.

For programs paying tokens on these networks, reward in points or collect a payout address on a supported chain. The networks listed in [EVM Claiming](/developer-guide/claiming-onchain-rewards/evm) are the payout-enabled set.
{% endhint %}

{% hint style="info" %}
We're continually expanding support for new triggers and protocols. If you're interested in a specific use case that isn't listed, reach out at <ecosystem@fuul.xyz>.
{% endhint %}


# 1️⃣ CLAMM LPs (e.g. Uniswap V3)

Everything you need to know about concentrated liquidity campaigns on Fuul

Fuul enables projects to reward Liquidity Providers (LPs) on concentrated liquidity AMMs such as Uniswap V3, Quickswap, Camelot, and others.

In concentrated liquidity pools, LPs allocate capital to specific price ranges instead of the full price curve. Fuul's engine fetches all positions from a pool's subgraph and calculates each position's contribution to determine reward shares.

### How Rewards Are Distributed

The recommended approach is **Active Liquidity**: each in-range position earns rewards proportional to the **USD value of its liquidity** relative to the pool's total in-range liquidity.

| Step                       | What happens                                                                                    |
| -------------------------- | ----------------------------------------------------------------------------------------------- |
| **1. Fetch positions**     | Fuul pulls all positions from the pool's subgraph                                               |
| **2. Filter in-range**     | Only positions whose price range includes the current price are considered                      |
| **3. Calculate USD value** | Each position's liquidity is valued in USD based on the current token prices                    |
| **4. Distribute rewards**  | Rewards are split proportionally — your share of in-range USD liquidity = your share of rewards |

Out-of-range positions receive zero rewards, ensuring incentives go to liquidity that is actively contributing to the pool.

### Alternative: Distribution Formula

For projects that want more granular control, Fuul also supports a **weighted distribution formula** that breaks rewards into three components:

* **Liquidity Share** — the position's share of the pool's total in-range liquidity
* **Token A Share** — the position's Token A amount divided by the pool's total Token A TVL
* **Token B Share** — the position's Token B amount divided by the pool's total Token B TVL

The incentive provider assigns a weight to each component. The formula is:

<figure><img src="/files/XCMoNP190AoX29APfBt8" alt=""><figcaption></figcaption></figure>

**Example:** With weights of `liquidity = 40%, Token A = 30%, Token B = 30%`:

* A position holding 50% of active liquidity earns **20%** (40% x 50%) of total rewards
* A position holding 30% of Token A earns **9%** (30% x 30%) of total rewards
* A position holding 20% of Token B earns **6%** (30% x 20%) of total rewards

This approach also allows rewarding **out-of-range positions** — their token amounts are calculated using the V3 math formula so they still receive a share based on their potential contribution.

{% hint style="info" %}
We recommend **Active Liquidity** for most campaigns. The Distribution Formula is best suited for projects with specific requirements around token composition or out-of-range incentives.
{% endhint %}

### Key Differences from Constant Product (V2)

|                           | Concentrated Liquidity (V3)                           | Constant Product (V2)                  |
| ------------------------- | ----------------------------------------------------- | -------------------------------------- |
| **Liquidity range**       | Custom price bands                                    | Full price range (0 to ∞)              |
| **Reward calculation**    | USD value of in-range liquidity (or weighted formula) | Simple share of LP token supply        |
| **Out-of-range handling** | Configurable — exclude or include                     | N/A — all liquidity is always in range |


# 2️⃣ Constant LPs (e.g., Uniswap V2)

Fuul enables projects to incentivize Liquidity Providers (LPs) in constant product AMMs such as Uniswap V2, Sushiswap, and similar DEXs.

### How It Works

* Fuul tracks LP token balances for a specific pool — either directly on-chain or via subgraphs
* Each provider's share is calculated as their LP token balance divided by the total LP token supply
* Rewards are distributed proportionally based on each provider's share of the pool

### Key Differences from Concentrated Liquidity (V3)

|                           | Constant Product (V2)                  | Concentrated Liquidity (V3)                                   |
| ------------------------- | -------------------------------------- | ------------------------------------------------------------- |
| **Liquidity range**       | Full price range (0 to ∞)              | Custom price bands                                            |
| **Position tracking**     | Single LP token balance                | Per-position tick ranges                                      |
| **Reward calculation**    | Simple share of total supply           | Weighted by liquidity share, Token A share, and Token B share |
| **Out-of-range handling** | N/A — all liquidity is always in range | Configurable (Active Liquidity vs Distribution Formula)       |

### Why Use Fuul for V2 LPs?

* **Simple and transparent** — reward calculation is straightforward: your share of the pool = your share of rewards
* **Accurate tracking** — LP token balances are fetched directly from the blockchain
* **Flexible payouts** — combine with referral rewards, set fixed or variable amounts, and target specific pools


# 3️⃣ Lending & Borrowing

Fuul allows projects to incentivize both lenders and borrowers, giving you the flexibility to reward the specific behavior that matters most for your protocol.

### What You Can Incentivize

| Side                 | What's tracked                                  | Example use case                         |
| -------------------- | ----------------------------------------------- | ---------------------------------------- |
| **Lending (supply)** | Amount supplied relative to total pool deposits | Grow TVL by rewarding depositors         |
| **Borrowing**        | Amount borrowed relative to total pool borrows  | Drive utilization by rewarding borrowers |

### How Fuul Tracks Positions

Fuul supports two approaches for fetching lending and borrowing data:

* **Receipt / debt tokens** — track the balance of protocol-issued tokens (e.g., aTokens, cTokens) that represent a user's supply or borrow position
* **Subgraph queries** — fetch positions directly from a protocol's subgraph for real-time on-chain balance tracking

Both methods calculate each user's share of the total pool, ensuring proportional and accurate reward distribution.

### Supported protocols

| Protocol             | Type                          | How positions are tracked                                              |
| -------------------- | ----------------------------- | ---------------------------------------------------------------------- |
| **Compound V3**      | Lending                       | Receipt/debt token balances                                            |
| **Morpho**           | Lending                       | Receipt/debt token balances                                            |
| **Morpho Vaults**    | Curated vaults                | Subgraph query (supply positions in curated vault structures)          |
| **Euler Vaults**     | Curated vaults                | Subgraph query (supply positions in curated vault structures)          |
| **Euler V2 Looping** | Looping (lending + borrowing) | Subgraph query (looped supply/borrow positions across Euler V2 vaults) |

{% hint style="info" %}
Morpho Vaults and Euler Vaults are curated vault structures, distinct from direct lending pools. Fuul tracks user supply positions via subgraph queries rather than receipt tokens. Euler V2 Looping extends this by tracking looped supply and borrow positions within Euler V2 vaults.
{% endhint %}

### Why Use Fuul for Lending & Borrowing?

* **Target supply or demand** — choose whether to incentivize lenders, borrowers, or both
* **Accurate on-chain data** — positions are fetched directly from the blockchain or subgraphs
* **Works with major protocols** — native integrations for Compound V3, Morpho, and others


# 4️⃣ Staking

Fuul makes it easy to reward users for staking their assets, encouraging long-term commitment and engagement.

### How It Works

* Fuul tracks staking positions directly on-chain via subgraphs or receipt tokens
* Each user's staked balance is fetched periodically and compared to the total staked in the pool
* Rewards are distributed proportionally based on each user's share of the total stake

### What You Can Incentivize

| Factor            | Description                                                                    |
| ----------------- | ------------------------------------------------------------------------------ |
| **Amount staked** | Reward users proportionally to how much they have staked                       |
| **Duration**      | Encourage long-term staking by weighting rewards toward longer lock-ups        |
| **Milestones**    | Set specific thresholds (e.g., stake ≥ 1,000 tokens) that unlock bonus rewards |

### Why Use Fuul for Staking?

* **On-chain tracking** — balances are fetched directly from the blockchain, ensuring accuracy
* **Flexible reward design** — combine fixed and variable payouts, reward referrers, end users, or both
* **Ecosystem alignment** — incentivize the behavior that strengthens your protocol's security and stability


# 5️⃣ Token Holders

Fuul allows projects to reward token holders based on their balances at specific points in time. By capturing a **snapshot** of token ownership at a defined moment, Fuul simplifies the process of distributing rewards to your community.

### What You Can Incentivize

| Trigger                     | What's tracked                                   | Use case                                                   |
| --------------------------- | ------------------------------------------------ | ---------------------------------------------------------- |
| **Token balance snapshot**  | User's token balance at a specific point in time | Airdrops, loyalty rewards, governance incentives           |
| **Subgraph-based snapshot** | Custom data derived from a subgraph query        | Advanced balance calculations or protocol-specific metrics |
| **Dune Query snapshot**     | Custom data derived from a Dune Analytics query  | Complex onchain data that requires SQL-based analysis      |

### How It Works

| Step                       | What happens                                                                                        |
| -------------------------- | --------------------------------------------------------------------------------------------------- |
| **1. Define the snapshot** | Configure which token (or subgraph/Dune query) to track, on which chain, and at what point in time  |
| **2. Capture balances**    | Fuul takes a snapshot of all holder balances at the defined moment                                  |
| **3. Filter holders**      | Optional minimum balance thresholds or other criteria are applied to determine qualifying holders   |
| **4. Distribute rewards**  | Rewards are distributed proportionally based on each holder's share of the total qualifying balance |

### Use Cases

| Use case                  | Description                                                                                   |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| **Airdrops**              | Reward holders with tokens or points based on their snapshot balance                          |
| **Loyalty rewards**       | Conduct periodic snapshots and reward consistent balances to encourage long-term holding      |
| **Governance incentives** | Distribute rewards to token holders during key governance events to incentivize participation |

### Data Sources

| Source            | When to use                               | Setup                                   |
| ----------------- | ----------------------------------------- | --------------------------------------- |
| **Token balance** | Standard ERC-20 or native token holdings  | Provide the token contract address      |
| **Subgraph**      | Custom or protocol-specific balance logic | Provide the subgraph endpoint and query |
| **Dune Query**    | Complex onchain analysis requiring SQL    | Provide the Dune query ID               |

{% hint style="info" %}
Token holder snapshots can be taken on any EVM-compatible chain. For advanced configurations using subgraphs or Dune queries, reach out at <ecosystem@fuul.xyz>.
{% endhint %}


# 6️⃣ Custom Onchain Events

Fuul allows projects to set up any smart contract function or event as a conversion event through the Fuul Incentives Manager. You can filter transactions by specific parameters to target exactly the onchain actions you want to reward.

### What You Can Incentivize

| Trigger type                 | Description                                                                           | Reward basis                              |
| ---------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Deposit & Hold**           | Incentivize users to deposit liquidity and reward them periodically for holding       | Proportional to deposited value over time |
| **Swap**                     | Reward users for exchanging one token for another on your platform                    | Per swap or proportional to volume        |
| **Mint**                     | Encourage users to mint in-game assets, NFTs, or any other type of token              | Per mint event                            |
| **Stake**                    | Incentivize users to stake tokens and reward them periodically for their staked value | Proportional to staked value over time    |
| **Any contract interaction** | Define any smart contract function or event as a trigger                              | Configurable per event                    |

### How It Works

| Step                          | What happens                                                                                            |
| ----------------------------- | ------------------------------------------------------------------------------------------------------- |
| **1. Define the trigger**     | Select a smart contract address and specify the function or event you want to track                     |
| **2. Set filters (optional)** | Add parameter-level filters to narrow which transactions qualify (e.g., minimum amount, specific token) |
| **3. Monitor transactions**   | Fuul monitors the blockchain for matching transactions in real time                                     |
| **4. Record events**          | Qualifying transactions are recorded and attributed to the user's wallet address                        |
| **5. Distribute rewards**     | Rewards are distributed based on the configured reward basis — either per event or proportionally       |

### Hold-Based vs Event-Based Triggers

|                                 | Event-based (Swap, Mint)            | Hold-based (Deposit & Hold, Stake)   |
| ------------------------------- | ----------------------------------- | ------------------------------------ |
| **When rewards are calculated** | At the time of the transaction      | Periodically (e.g., daily snapshots) |
| **Reward basis**                | Per event or proportional to volume | Proportional to value held over time |
| **Use case**                    | One-time actions                    | Ongoing participation incentives     |

{% hint style="info" %}
Custom onchain triggers support any EVM-compatible chain. If you need help defining the right contract events and filters for your protocol, reach out at <ecosystem@fuul.xyz>.
{% endhint %}


# 7️⃣ Custom Offchain Events

Fuul allows projects to reward users for actions that happen outside the blockchain. Use the Fuul API, CSV uploads, or Zapier to feed offchain events into Fuul and make them part of your incentive program.

{% hint style="info" %}
This trigger is labelled **API** in the dashboard. "Custom Offchain" is the platform's internal name for the same thing — the label changed, the behavior did not.
{% endhint %}

### Available Methods

| Method               | Description                                                          | Best for                                                |
| -------------------- | -------------------------------------------------------------------- | ------------------------------------------------------- |
| **Custom (API Key)** | Integrate your own backend or third-party software with Fuul via API | Full control over event reporting from your own systems |
| **CSV File**         | Upload event data from a CSV file                                    | Bulk imports or one-time reward distributions           |
| **Zapier**           | Connect other apps and conversion events with Fuul through Zapier    | No-code integrations with 5,000+ apps                   |

### How It Works

| Step                          | What happens                                                                                  |
| ----------------------------- | --------------------------------------------------------------------------------------------- |
| **1. Send events**            | Events reach Fuul via API calls, CSV upload, or Zapier triggers                               |
| **2. Validate & deduplicate** | Fuul validates incoming events and ensures users are only rewarded once per qualifying action |
| **3. Attribute to users**     | Each event is attributed to the user's wallet address or identifier                           |
| **4. Distribute rewards**     | Rewards are distributed based on the configured payout — fixed per event or proportional      |

### API vs CSV vs Zapier

|                  | API                          | CSV                                | Zapier                               |
| ---------------- | ---------------------------- | ---------------------------------- | ------------------------------------ |
| **Automation**   | Fully automated, real-time   | Manual upload                      | Automated via triggers               |
| **Setup effort** | Requires backend integration | None — upload a file               | No-code configuration                |
| **Best for**     | Continuous event streams     | One-off distributions or backfills | Connecting third-party tools         |
| **Flexibility**  | Highest — any event shape    | Fixed CSV format                   | Depends on available Zapier triggers |

{% hint style="info" %}
For social and quest-based integrations that don't require custom development, see [Quests & Social](/core-concepts/trigger-integrations/quests-and-social) which provides pre-built integrations with platforms like X, Discord, Zealy, and Galxe.
{% endhint %}


# 8️⃣ Trading

Fuul allows projects to reward users for trading activity across spot DEXs and perpetual exchanges. Trading triggers capture volume, fees, and PnL to distribute rewards based on real usage.

### What You Can Incentivize

| Factor             | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| **Trade volume**   | Reward users proportionally to the USD value of their trades                 |
| **Fees paid**      | Target active traders by rewarding based on fees generated                   |
| **PnL**            | Incentivize profitable trading or reward participation regardless of outcome |
| **Maker vs taker** | Distinguish between maker and taker volume to fine-tune incentives           |

### Supported Protocols

| Protocol        | Type             | Description                                               |
| --------------- | ---------------- | --------------------------------------------------------- |
| **Sushiswap**   | Spot DEX         | Swap trades on Sushiswap                                  |
| **Nado**        | DEX              | Trade activity on Nado                                    |
| **Ambient**     | DEX              | 24-hour trading leaderboard with volume and PnL           |
| **Orderly**     | Perpetuals       | Builder trades with maker/taker volume breakdown and fees |
| **Hyperliquid** | Perpetuals       | Builder trades on Hyperliquid                             |
| **Odyssey**     | Perpetuals       | Builder trades on Odyssey                                 |
| **Valiant**     | Perps & Spot DEX | Perps and spot trades with volume and fees, on Fogo (SVM) |

### How It Works

| Step                         | What happens                                                                                                                                         |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **1. Fetch trades**          | Fuul pulls trade data from each protocol's API or subgraph on a scheduled basis                                                                      |
| **2. Filter by time window** | Only trades within the measurement period are included. The time window can be **daily** or **hourly** depending on the endpoint and the data source |
| **3. Calculate volume**      | Each trade's USD volume is computed and attributed to the trader's address                                                                           |
| **4. Distribute rewards**    | Rewards are split proportionally based on each trader's share of total volume (or other selected metric)                                             |

### Perpetual vs Spot Trades

|                          | Spot trades                  | Perpetual trades                               |
| ------------------------ | ---------------------------- | ---------------------------------------------- |
| **Data captured**        | Volume, fees                 | Volume, leverage, PnL, fees, maker/taker split |
| **Position lifecycle**   | Instant (single transaction) | Open → Close (position must close to count)    |
| **Typical reward basis** | Volume or fees               | Volume, fees, or PnL                           |

{% hint style="info" %}
For projects looking to incentivize trading, Fuul can configure rewards based on volume, fees, or any combination — reach out at <ecosystem@fuul.xyz> to discuss the best setup for your protocol.
{% endhint %}


# 9️⃣ Yield (Pendle)

Fuul supports native integrations with Pendle, allowing projects to reward users who hold Yield Tokens (YT) or provide liquidity through LP tokens.

### Pendle Basics

Pendle splits yield-bearing assets into two components:

| Token                       | What it represents                                                                 |
| --------------------------- | ---------------------------------------------------------------------------------- |
| **YT (Yield Token)**        | Ownership of the **future yield** from an underlying asset until maturity          |
| **PT (Principal Token)**    | Ownership of the **principal** — redeemable for the underlying asset at maturity   |
| **LP Token**                | Liquidity provided to Pendle's AMM pools (PT/SY pairs)                             |
| **SY (Standardized Yield)** | A wrapper that standardizes different yield-bearing assets into a common interface |

### What You Can Incentivize

| Trigger       | What's tracked                                        | Use case                                                                      |
| ------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------- |
| **Pendle YT** | User's SY balance derived from holding YT             | Reward users who are betting on yield — incentivize demand for the yield side |
| **Pendle LP** | User's LP token holdings (with liquid locker support) | Reward liquidity providers in Pendle AMM pools                                |

### How YT Rewards Work

| Step                      | What happens                                                                                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **1. Fetch balances**     | Fuul queries pre-computed data to get each user's SY (Standardized Yield) balance for the specific Pendle contract |
| **2. Map to users**       | Each balance is attributed to the user's wallet address                                                            |
| **3. Distribute rewards** | Rewards are split proportionally based on each user's SY balance relative to the total                             |

### How LP Rewards Work

LP rewards track users who provide liquidity to Pendle's AMM pools. Fuul fetches LP token balances — including positions held through liquid lockers — and distributes rewards proportionally.

### Setup Requirements

Pendle integrations require **3 contract addresses**:

| Address        | Purpose                                                           |
| -------------- | ----------------------------------------------------------------- |
| **YT address** | The Yield Token contract                                          |
| **LP address** | The LP token contract                                             |
| **SY address** | The Standardized Yield contract (needed for balance calculations) |

{% hint style="info" %}
Pendle integrations are available on Ethereum, HyperEVM, and Base. If you need support on additional chains, reach out at <ecosystem@fuul.xyz>.
{% endhint %}

### Spectra support

Fuul also supports Spectra, which follows the same yield-splitting model as Pendle (YT/PT/LP). Projects can reward users who hold Spectra YT or provide liquidity through Spectra LP tokens.

| Trigger        | What's tracked                                    | Use case                                         |
| -------------- | ------------------------------------------------- | ------------------------------------------------ |
| **Spectra YT** | User's underlying balance derived from holding YT | Reward users betting on yield in Spectra markets |
| **Spectra LP** | User's LP token holdings in Spectra pools         | Reward liquidity providers in Spectra AMM pools  |

Setup follows the same pattern as Pendle, with contract addresses for the YT, LP, and underlying components. If you need a Spectra integration on a chain that is not currently supported, reach out at <ecosystem@fuul.xyz>.


# Quests & Social

Fuul provides native integrations with quest platforms and social networks, allowing projects to reward users for community engagement and task completion without any custom development.

| Platform        | What's tracked                                    |
| --------------- | ------------------------------------------------- |
| **X (Twitter)** | Follows, posts, likes, reposts                    |
| **Discord**     | Server membership, channel joins, role assignment |
| **Zealy**       | Quest completion                                  |
| **Galxe**       | Campaign participation                            |

{% content-ref url="/pages/aSqRzcpLaX9OtWHX88Dn" %}
[𝕏 (Twitter)](/core-concepts/trigger-integrations/quests-and-social/x-twitter)
{% endcontent-ref %}

{% content-ref url="/pages/c3aVE22TyT0atbj1hwLm" %}
[Discord](/core-concepts/trigger-integrations/quests-and-social/discord)
{% endcontent-ref %}

{% content-ref url="/pages/7DZt9TZWEhGsJZIkhywC" %}
[Galxe & Zealy](/core-concepts/trigger-integrations/quests-and-social/galxe-zealy)
{% endcontent-ref %}


# 𝕏 (Twitter)

Reward users for engaging with your project on X — follows, posts, likes, and reposts are all supported with automatic verification.

## Supported integrations

| Integration | What's verified                                     |
| ----------- | --------------------------------------------------- |
| **Follow**  | User follows your specified account on X            |
| **Post**    | User publishes a post meeting your content criteria |
| **Like**    | User likes a specific post on X                     |
| **Repost**  | User reposts a specific post on X                   |

## Follow

Due to X API limitations, follow verification requires an OAuth flow — Fuul does not store or transmit the user's auth token. Users must complete verification through the dedicated page:

```
https://app.fuul.xyz/verify-social/{project-slug}/x
```

Append a `redirectUrl` parameter to send users back after verification completes:

```
https://app.fuul.xyz/verify-social/{project-slug}/x?redirectUrl={redirectUrl}
```

The verification modal adopts the **background** and **primary** colors from your Page Customization settings in the Fuul webapp.

## Post

The user connects their X account and Fuul checks their posts against the criteria you configured on the trigger before crediting the reward.

**What you can require:**

* Post contains a specific hashtag (e.g., `#YourProtocol`)
* Post mentions a specific account (e.g., `@yourprotocol`)
* Post includes a specific keyword or URL

{% hint style="info" %}
Combine X post verification with onchain triggers to create multi-step campaigns — users who both post and deposit can unlock a higher reward tier.
{% endhint %}

## Like & Repost

Verification is automatic — Fuul checks the X API to confirm the user liked or reposted the specified post before crediting the reward.

## How verification is targeted

X verification is **trigger-bound**. The verify endpoints (`follows`, `verify_post`, `verify_retweet`, `verify_like`) take the trigger's `trigger_ref` and nothing else. Fuul resolves both the verification target (which account to follow, which post to like or repost) and the emitted event name server-side from the trigger's stored configuration.

This means a client cannot supply its own `post_url` or `username` to steer what gets verified. If you are building against these endpoints directly rather than using the hosted page:

| Case                                                       | Response |
| ---------------------------------------------------------- | -------- |
| Unknown `trigger_ref`, or one belonging to another project | `404`    |
| Wrong trigger type, or malformed trigger context           | `422`    |

No paid verification call is made against the X API when resolution fails.

{% hint style="warning" %}
This replaced an earlier flow where the target came from client-supplied parameters. If you integrated against that shape, switch to passing the `trigger_ref`.
{% endhint %}

## Configuration

| Setting           | Value                                      |
| ----------------- | ------------------------------------------ |
| **Deduplication** | Automatic — one reward per user per action |


# Discord

Reward users for joining your Discord server, specific channels, or being assigned a role.

## Supported integrations

| Integration       | What's verified                                                     |
| ----------------- | ------------------------------------------------------------------- |
| **Join Server**   | User has joined the specified Discord server                        |
| **Join Channel**  | User has joined a specific channel within your server               |
| **Role Assigned** | User has been assigned a specific role in the linked Discord server |

Verification is automatic — Discord membership and role events are delivered to Fuul through the bot and matched against the user's linked Discord account before crediting the reward.

## Setup

Discord triggers require a one-time setup by the project, plus a per-user connection step.

**Project setup**

1. Go to **Settings → Discord** and click **Install bot** to add the Fuul bot to your Discord workspace (Discord OAuth).
2. Connect the bot to the specific **server (guild)** you want to reward activity in.
3. (Optional) Add a Discord **invite URL** to show a **Join** call-to-action on your hosted incentives page.

{% hint style="info" %}
The bot only needs the permissions required to read membership and role assignments for the server you connect. You can install it on one server per project.
{% endhint %}

## How users connect

Before a user can earn Discord rewards, they link their wallet to their Discord account:

1. From the hosted incentives page, the user clicks **Connect Discord** and authorizes via Discord OAuth.
2. Once linked, the reward card shows a **joined** state and Fuul can attribute their Discord activity to their wallet.

A user only needs to connect once per project. After that, joining the server, joining a channel, or receiving a role is verified automatically.

## Configuration

| Setting           | Value                                      |
| ----------------- | ------------------------------------------ |
| **Deduplication** | Automatic — one reward per user per action |


# Galxe & Zealy

Reward users for completing quests in your Galxe campaigns or Zealy communities.

## How it works

Quest integrations work on a **completion basis** — each user who completes the specified quest or campaign earns a reward.

| Step                   | What happens                                                                          |
| ---------------------- | ------------------------------------------------------------------------------------- |
| **Fetch participants** | Fuul pulls the list of users who completed the quest/campaign from the platform's API |
| **Deduplicate**        | New participants are identified by comparing against previously recorded completions  |
| **Record completion**  | Each new participant is recorded with a binary completion status                      |
| **Distribute rewards** | Rewards are distributed to all users who completed the quest                          |

{% hint style="info" %}
Quest rewards are usually configured as **fixed payouts** (e.g., 100 points per quest completed) rather than proportional distribution.
{% endhint %}


# 1️⃣1️⃣ Prediction Markets (Polymarket)

Fuul supports a native integration with Polymarket, enabling projects to reward users for trading activity on specific prediction markets.

### How It Works

Unlike traditional trading integrations that track activity per pool or pair, Polymarket tracks trade volume per **market** (also called a "condition"). Each market represents a yes/no question (e.g., "Will X happen by Y date?"), and traders buy or sell outcome shares.

| Step                      | What happens                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------ |
| **1. Specify the market** | Configure the trigger with a specific Polymarket condition (market) ID                           |
| **2. Fetch trades**       | Fuul pulls all trades for that market from the previous day                                      |
| **3. Calculate volume**   | Each trade's volume is computed as `size x price` and attributed to the trader's proxy wallet    |
| **4. Distribute rewards** | Rewards are split proportionally based on each trader's share of the market's total daily volume |

### What's Captured Per Trade

| Field            | Description                                          |
| ---------------- | ---------------------------------------------------- |
| **Volume**       | Trade size multiplied by price (USD value)           |
| **Side**         | Buy or sell                                          |
| **Outcome**      | Which outcome the trade is for (e.g., "Yes" or "No") |
| **Proxy wallet** | The user's Polymarket proxy wallet address           |

### Use Cases

* **Market creation incentives** — reward early traders to bootstrap liquidity in new markets
* **Volume campaigns** — incentivize trading activity on specific markets relevant to your project
* **Community engagement** — combine with referral rewards to grow your prediction market community

{% hint style="info" %}
Each Polymarket trigger targets a single market (condition). To incentivize multiple markets, create separate triggers for each one.
{% endhint %}


# 1️⃣2️⃣ HyperLiquid Builder Codes

HyperLiquid Builder Codes allow protocols and interfaces to incentivize perpetual trading volume on HyperLiquid. Fuul integrates as a registered builder to track and reward trades routed through your program — no custom event instrumentation required.

## What are Builder Codes?

HyperLiquid's builder code system lets ecosystem protocols tag trades they route through their interface. Fuul uses this tagging mechanism to track all trading activity routed through your builder — volume, fees, PnL, and order types are captured automatically for every trader.

## What you can incentivize

| Metric             | Description                                                   |
| ------------------ | ------------------------------------------------------------- |
| **Trading volume** | USD value of perpetual trades made during the period          |
| **Fees generated** | Trading fees attributed to trades routed through your builder |
| **PnL**            | Profit/loss per trader over the period                        |
| **Maker vs taker** | Distinguish volume by order type                              |

## How it works

Trade data is fetched daily using the builder code linked to your project. Activity from each day is processed and rewarded the following day.

{% hint style="info" %}
No contract address is required when setting up a HyperLiquid trigger. Fuul handles all data fetching automatically using the builder code associated with your project.
{% endhint %}

## Setup

Select **HyperLiquid Trade** as the trigger type when creating a conversion. Combine it with any payout type:

* **Variable** — reward traders as a % of their volume (e.g., 0.05% in POINTS)
* **Leaderboard** — rank traders by volume or PnL for a time-limited competition
* **Proportional pool** — distribute a fixed token pool proportionally to each trader's share of total volume

## Use cases

* Run affiliate and referral programs that reward partners for bringing in active traders
* Incentivize traders to route orders through your interface
* Run monthly trading competitions ranked by PnL or volume
* Distribute token rewards proportionally to the most active traders

{% hint style="warning" %}
Reach out at <ecosystem@fuul.xyz> to register your HyperLiquid builder code and connect it to your Fuul project before setting up the trigger.
{% endhint %}


# 1️⃣3️⃣ Orderly Network

Orderly Network is a decentralized orderbook and liquidity layer for perpetual futures. Fuul integrates with Orderly's builder trade system to track and reward trading activity across protocols built on top of Orderly.

## What you can incentivize

| Metric                   | Description                                                         |
| ------------------------ | ------------------------------------------------------------------- |
| **Builder trade volume** | USD volume of trades routed through your registered builder account |
| **Trading fees**         | Fee revenue generated by trades routed through your builder account |

## How it works

Orderly operates on its own network (Chain ID: 291). Fuul fetches daily trade data using the builder identifier linked to your project, tracking volume and fees for every trader.

Trade data is fetched on a **daily schedule** — activity from each day is processed and rewarded the following day.

{% hint style="info" %}
No contract address or webhook setup is required. Fuul handles all data fetching automatically using your builder account credentials.
{% endhint %}

## Setup

Select **Orderly Builder Trades** as the trigger type when creating a conversion. Choose a payout type:

* **Variable** — reward as % of trading volume in your token
* **Leaderboard** — rank traders by volume for a competition
* **Proportional pool** — distribute a fixed pool among traders based on their volume share

**Example configuration:**

```
Trigger:    Orderly Builder Trades
Reward:     Variable — 0.1% of volume in YOUR_TOKEN
Recipients: End users
```

{% hint style="warning" %}
Reach out at <ecosystem@fuul.xyz> to register your Orderly builder account and connect it to your Fuul project before setting up the trigger.
{% endhint %}


# 1️⃣4️⃣ Pacifica Builder Trades

Pacifica is a decentralized trading platform with a builder code system. Fuul integrates with Pacifica's builder trades to track and reward trading activity routed through your builder account.

## What you can incentivize

| Metric                   | Description                                                      |
| ------------------------ | ---------------------------------------------------------------- |
| **Builder trade volume** | USD volume of trades routed through your registered builder code |
| **Trading fees**         | Fee revenue generated by trades routed through your builder code |

## How it works

Fuul fetches daily trade data using the builder code linked to your project, tracking volume and fees for every trader.

Trade data is fetched on a **daily schedule** — activity from each day is processed and rewarded the following day.

{% hint style="info" %}
No contract address or webhook setup is required. Fuul handles all data fetching automatically using your builder code credentials.
{% endhint %}

## Setup

Select **Pacifica Builder Trades** as the trigger type when creating a conversion. Choose a payout type:

* **Variable** — reward as % of trading volume in your token
* **Leaderboard** — rank traders by volume for a competition
* **Proportional pool** — distribute a fixed pool among traders based on their volume share

**Example configuration:**

```
Trigger:    Pacifica Builder Trades
Reward:     Variable — 0.1% of volume in YOUR_TOKEN
Recipients: End users
```

## Use cases

| Use case                 | How it works                                                                |
| ------------------------ | --------------------------------------------------------------------------- |
| **Affiliate programs**   | Reward affiliates based on the trading volume their referred users generate |
| **Trading competitions** | Rank traders by volume over a time period and distribute prizes from a pool |
| **Fee rebates**          | Return a percentage of trading fees to active traders as an incentive       |

{% hint style="warning" %}
Reach out at <ecosystem@fuul.xyz> to register your Pacifica builder code and connect it to your Fuul project before setting up the trigger.
{% endhint %}


# Incentive Payouts

Once a trigger fires and a conversion is attributed, Fuul calculates and distributes the reward. There are four payout structures available, each suited for different program goals.

| Payout type                                                                     | How it works                                                      | Best for                               |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------- |
| [**Fixed Rewards**](/core-concepts/incentive-payouts/fixed-rewards)             | Set amount per action (e.g., $10 per referral)                    | Simple, predictable programs           |
| [**Variable Rewards**](/core-concepts/incentive-payouts/variable-rewards)       | Percentage of volume or revenue (e.g., 0.1% of trading volume)    | Rewarding higher-value actions         |
| [**Pool Distribution**](/core-concepts/incentive-payouts/pool-distribution)     | Fixed budget divided among users based on their share of activity | Liquidity programs, capped budgets     |
| [**Leaderboard Rewards**](/core-concepts/incentive-payouts/leaderboard-rewards) | Payouts based on ranking position on the leaderboard              | Competitions, rewarding top performers |

All four payout types support **tiered rewards** — users progress through levels and earn better rates as their participation increases.

{% hint style="info" %}
**Onchain rewards** (tokens) are distributed as claim checks that users claim via a smart contract transaction. **Points** are distributed automatically to users' accounts — no claiming needed.
{% endhint %}

### Who gets rewarded?

For each incentive rule, you choose who receives the payout:

| Recipient     | Description                                    |
| ------------- | ---------------------------------------------- |
| **Referrers** | The affiliate or referrer who brought the user |
| **End users** | The user who performed the action              |
| **Both**      | Split the reward between referrer and end user |

{% content-ref url="/pages/9csKZvnPUH6RFHUBbmz3" %}
[Fixed Rewards](/core-concepts/incentive-payouts/fixed-rewards)
{% endcontent-ref %}

{% content-ref url="/pages/ogpmahMd9PZrYLv0uKtB" %}
[Variable Rewards](/core-concepts/incentive-payouts/variable-rewards)
{% endcontent-ref %}

{% content-ref url="/pages/YpIZBRKzldzUxR5CvWtD" %}
[Pool Distribution](/core-concepts/incentive-payouts/pool-distribution)
{% endcontent-ref %}

{% content-ref url="/pages/7MlcNmD00JKWenbzXlsy" %}
[Leaderboard Rewards](/core-concepts/incentive-payouts/leaderboard-rewards)
{% endcontent-ref %}


# Fixed Rewards

Fixed rewards provide a consistent incentive amount for each completed action. This option is ideal when you want to reward users equally for every instance of a specific action, creating straightforward and predictable incentives. With fixed rewards, participants clearly understand what they will earn each time they complete the action, fostering transparency and simplicity in your program.

Example:

* A user earns $10 per referral, regardless of how many referrals they generate.
* A user receives 2 tokens per swap, with no limit.

✅ Best for: Simple, predictable incentive programs with clear and consistent rewards.

{% hint style="info" %}
Fixed rewards can be combined with tiers to give different user groups different rates or multipliers. See [Tiers & Multipliers](/core-concepts/tiers-and-multipliers).
{% endhint %}


# Variable Rewards

Variable rewards scale with the magnitude of the action — the more a user does, the more they earn.

There are two types of variable rewards:

#### **1️⃣ Based on Volume:**

Rewards increase based on the total amount of an action performed.

Example: A trading program where users receive 0.1% of their trading volume as a reward. The more they trade, the more they earn.

#### **2️⃣ Based on Revenue:**

Rewards are calculated based on the revenue a user generates. There are two payout modes:

* **Per unit** — a fixed rate of points or tokens per USD of revenue (e.g., 10 points per $1 in referred fees)
* **Percentage** — a percentage of revenue paid in points or tokens (e.g., 5% of referred fees paid in USDC)

Example: In an exchange affiliate program, a referrer earns rewards proportional to the fees generated by their referred users.

✅ Best for: Programs that want to reward higher-value actions rather than just participation.

{% hint style="info" %}
Variable rewards can be combined with tiers to give different user groups different rates or multipliers. See [Tiers & Multipliers](/core-concepts/tiers-and-multipliers).
{% endhint %}


# Pool Distribution

You define a fixed total budget, and at the end of the period each participant receives a share proportional to their contribution relative to all others. Choose whether the pool goes to end users or referrers.

The pool is divided based on one of three metrics:

### Volume-Based Distribution

Rewards are allocated based on the volume of activity each participant contributes. This is ideal for incentivizing high engagement.

Example:

* In a referral program, users who generate more referred transactions will receive a higher share of the reward pool.
* In a trading incentive campaign, users who execute larger trade volumes will get a larger portion of the rewards.

### Revenue-Based Distribution

Rewards are distributed based on the revenue generated by each participant. This method ensures that the highest contributors receive the largest share of the pool.

Example:

* In a DeFi protocol, liquidity providers (LPs) who generate more trading fees through their liquidity contributions receive a greater portion of the rewards.
* In an affiliate program, referrals that result in higher-value transactions receive a larger percentage of the incentive pool.

### Attribution Count

Rewards are allocated based on the **number of times each participant performed a qualifying action**. Each conversion a user triggers adds to their count, and their share of the pool is proportional to how many conversions they contributed relative to the total.

Example:

* In a referral program with a $5,000 pool, if Alice refers 6 users and Bob refers 4 users (10 total conversions), Alice receives 60% ($3,000) and Bob receives 40% ($2,000).
* In a quest campaign where users earn points for completing on-chain actions (e.g., swapping, bridging, staking), a user who completes 5 qualifying actions gets a larger share than one who completes 2 — even if both crossed the eligibility threshold.

{% hint style="info" %}
Pool distribution can be combined with tiers to give different user groups different rates or multipliers. See [Tiers & Multipliers](/core-concepts/tiers-and-multipliers).
{% endhint %}


# Leaderboard Rewards

Leaderboard incentives rank users by their activity over a defined date range and assign rewards based on their final position. Instead of paying per action or splitting a pool proportionally, each position (or range of positions) receives a predetermined reward.

Example:

* In a trading competition running for one month, the top trader earns 1,000 POINTS, 2nd place earns 500, and positions 3–10 each earn 100.
* In a referral campaign, the top 5 referrers by referred volume each receive a fixed token reward.

✅ Best for: Competitions, trading campaigns, and time-bound programs where you want to reward top performers by rank.

{% hint style="warning" %}
Tiers and multipliers cannot be applied to leaderboard incentives.
{% endhint %}

### How it works

1. **Define a date range** — Set the start and end dates for the competition period
2. **Choose a ranking metric** — Users are ranked by **Volume**, **Revenue**, or **number of conversions** over the period
3. **Configure rewards per position** — Assign a fixed reward to specific ranks or ranges of positions (e.g., 1st place, 2nd place, 3rd–10th place)
4. **Users compete during the period** — Activity is tracked and rankings are updated on the leaderboard
5. **At the end of the period, rewards are distributed** — Each participant receives the payout corresponding to their final ranking position

### Example Configuration

| Position | Reward          |
| -------- | --------------- |
| 1st      | 1,000 POINTS    |
| 2nd      | 500 POINTS      |
| 3rd      | 250 POINTS      |
| 4th–10th | 100 POINTS each |

### When to use Leaderboard vs Pool Distribution

|                      | Pool Distribution                              | Leaderboard                           |
| -------------------- | ---------------------------------------------- | ------------------------------------- |
| **Rewards based on** | Proportional share of activity                 | Final ranking position                |
| **Budget**           | Fixed total pool, split among all participants | Fixed rewards per position            |
| **Everyone earns?**  | Yes — any participant gets a share             | Only ranked positions earn            |
| **Best for**         | Broad participation, liquidity programs        | Competitions, top-performer campaigns |


# Tiers & Multipliers

By default, all users earn the same payout rate. Tiers let you change that for specific groups — giving ambassadors, NFT holders, or top traders a higher rate or a multiplier on top of the default.

A user can qualify for multiple tiers at once, but only the highest-ranked tier applies — tiers never stack.

## 1. Create an audience

Audiences define who belongs to a tier. They can be dynamic or static:

| Type        | How it works                                             | Examples                                 |
| ----------- | -------------------------------------------------------- | ---------------------------------------- |
| **Dynamic** | Users are added automatically when they meet a condition | Token holdings, NFT holdings, Dune query |
| **Static**  | A manually curated list of addresses                     | Ambassadors, partners, hand-picked users |

## 2. Create a tier

Once you have an audience, go to **Audiences → Tiers** and create a tier linked to it.

| Field           | Description                                                                             |
| --------------- | --------------------------------------------------------------------------------------- |
| **Name**        | Display name for the tier (e.g., "Ambassadors", "Diamond")                              |
| **Description** | Optional context for what the tier represents                                           |
| **Rank**        | Priority order — if a user qualifies for multiple tiers, the highest-ranked one applies |
| **Audience**    | The audience whose members automatically qualify for this tier                          |
| **Slug**        | Derived from the name. Not editable directly                                            |

{% hint style="info" %}
When manual approval is enabled on a tier, users who qualify do not advance automatically — an admin must approve the transition first. When disabled, tier resolution is automatic.
{% endhint %}

### Slugs change when you rename a tier

Renaming a tier regenerates its slug from the new name. A collision with an existing slug gets a numeric suffix (`platinum-1`), and slugs longer than 50 characters are truncated. Saving a tier without changing its name leaves the slug alone.

{% hint style="warning" %}
**Do not use `tier_slug` as a stable key.** It is returned by `getIncentivesByTier`, by the project-affiliate read endpoints, and by the public conversions API, so anything you store or match on it breaks the moment someone renames the tier in the dashboard. Key on `tier_id`, which never changes.
{% endhint %}

## 3. Add the tier to your incentive

With the tier created, open any incentive's payout settings and click **Add tiers**. For each tier you add, choose how users in it are rewarded:

| Option         | Example                                              |
| -------------- | ---------------------------------------------------- |
| **Amount**     | Users in this tier earn $0.05 per USD of volume      |
| **Multiplier** | 2× gives users in this tier twice the default payout |

## API endpoints

| Endpoint                                | Method | Description                           |
| --------------------------------------- | ------ | ------------------------------------- |
| `/v1/projects/:projectId/tiers`         | POST   | Create a new tier                     |
| `/v1/projects/:projectId/tiers`         | GET    | List all tiers                        |
| `/v1/projects/:projectId/tiers/:tierId` | PATCH  | Update a tier                         |
| `/v1/projects/:projectId/tiers/:tierId` | DELETE | Delete a tier                         |
| `/v1/projects/:projectId/tiers/reorder` | POST   | Bulk reorder tiers                    |
| `/v1/payouts/terms-by-tier`             | GET    | Retrieve payout terms grouped by tier |

## Displaying payout rates per tier

To show potential rates on your own frontend, use `getIncentivesByTier`. It reads from the latest published project metadata, the same source as `getIncentives`, and returns display-ready `amount` / `currency` / `unit` strings:

```typescript
import { Fuul } from '@fuul/sdk';

// All tiers, plus the default (no-tier) bucket
const { tiers } = await Fuul.getIncentivesByTier();

// Specific tiers. Pass the literal string 'null' to include the default bucket
const { tiers } = await Fuul.getIncentivesByTier({ tier_ids: ['3fa8...', 'null'] });
```

Each entry in `tiers` has `tier_id`, `tier_name`, `tier_description`, `tier_slug`, `tier_rank`, and a `payout_terms` array. For the default bucket, every `tier_*` field is `null`.

{% hint style="info" %}
Display the current tier to users or fetch payout rates per tier → [Managing Audiences](/developer-guide/managing-audiences)
{% endhint %}


# Trading Competitions

A trading competition is a [Leaderboard](/core-concepts/incentive-payouts/leaderboard-rewards) incentive applied to a trading trigger. Users are ranked over a defined time window and rewarded based on their final position.

Competitions are built around custom formulas — you choose the ranking metric that fits your program: PnL, trading volume, referred fees, or any combination submitted via the [Custom Offchain Events API](/developer-guide/sending-custom-events-through-the-api).

## Setting up a competition

1. Create a trigger for the trading activity you want to track (HyperLiquid, Orderly, or a custom event)
2. Add a **Leaderboard** incentive
3. Configure the date range and rewards per position

{% hint style="info" %}
Display live rankings in your app → [Volume Leaderboard](/developer-guide/getting-leaderboard-data/volume) · [Points Leaderboard](/developer-guide/getting-leaderboard-data/points)
{% endhint %}


# Referrals

Fuul lets you set up incentives to encourage affiliates, KOLs, and users to bring new participants to your project through referrals. Referrals are the core growth mechanism — they connect attribution, reward structures, and affiliate management into a single system.

## How referrals work

1. An affiliate generates a tracking link or code
2. A user clicks the link and connects their wallet → [Attribution](/core-concepts/referrals/referrals-and-attribution) determines which referrer gets credit
3. The user converts (trades, deposits, etc.) → rewards are calculated based on the reward structure
4. Payouts are distributed to the referrer (and optionally to the referred user)

## Referral reward structures

Once attribution is set up, you can configure how rewards flow through the referral chain:

| Structure                               | Description                                                                                    | Guide                                                                  |
| --------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| **Multi-level referrals**               | Reward up to 4 levels of the referral chain (direct referrer + up to 3 levels above)           | [Multi-Level Referrals](/core-concepts/referrals/multilevel-referrals) |
| **Sharing rewards with referred users** | Affiliates share a portion of their commission with the users they refer (double-side rewards) | [Sharing Rewards](/core-concepts/referrals/double-side-rewards)        |
| **Tiered rewards**                      | Different reward rates based on affiliate performance, volume, or audience segment             | [Tiers & Multipliers](/core-concepts/tiers-and-multipliers)            |

## Referral codes vs affiliate codes

Fuul offers two types of codes for referral tracking:

|                | Affiliate codes                                     | Referral codes                                           |
| -------------- | --------------------------------------------------- | -------------------------------------------------------- |
| **Created by** | The affiliate (with a wallet signature)             | The project (on behalf of users)                         |
| **Format**     | Custom (e.g., `my-brand`)                           | Auto-generated (7-char alphanumeric)                     |
| **Used in**    | Tracking links (`?af=code`) — automatic attribution | Accepted explicitly by the user — permanent relationship |
| **Scope**      | Cross-project                                       | Single project                                           |

See [Affiliate Codes vs Referral Codes](/core-concepts/affiliates/referral-codes-vs-invite-codes) for the full comparison.

## Dynamic Referral Cap

Fuul supports a **dynamic referral cap** that limits how much a referrer can earn from their referral chain. Instead of unlimited accumulation, the referrer's reward is capped based on a volume multiplier applied to their own trading volume.

### How it works

The cap is calculated as:

```
Referral Cap = Referrer's Own Volume × Volume Multiplier
```

When the referrer's accumulated referral rewards reach this cap, further referral rewards are reduced or stopped until the cap resets or the referrer's own volume increases.

**Key behaviors:**

* Works for both **Points** and **Tokens** payout types
* Setting the multiplier to `null` disables the cap entirely (unlimited referral rewards)
* The cap applies per referral cycle — it does not permanently block rewards
* Higher multipliers allow referrers to earn more relative to their own activity
* Caps are **currency-scoped**: only rewards paid in the cap's own currency count toward its accumulated total. A cap defined in one currency (e.g. USDT) is never reduced by rewards paid in a different currency (e.g. a points reward).

### Example

| Referrer | Own Volume | Multiplier | Referral Cap |
| -------- | ---------- | ---------- | ------------ |
| Alice    | $10,000    | 3×         | $30,000      |
| Bob      | $50,000    | 2×         | $100,000     |

Alice can earn up to $30,000 from her referrals before the cap kicks in. Bob can earn up to $100,000.

This mechanism prevents affiliates with minimal personal activity from earning disproportionate rewards purely through referral chains, while still rewarding active referrers who also contribute volume.

## Related pages

| Topic                             | Guide                                                                         |
| --------------------------------- | ----------------------------------------------------------------------------- |
| How attribution assigns credit    | [Attribution](/core-concepts/referrals/referrals-and-attribution)             |
| Track referrals in your app (SDK) | [Tracking Referrals](/developer-guide/tracking-referrals-in-your-app)         |
| Create affiliate links and codes  | [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes) |
| Manage referral codes             | [Referral Codes](/developer-guide/referral-codes)                             |
| Build an affiliate dashboard      | [Affiliate Dashboard](/developer-guide/affiliate-dashboard)                   |
| Manage affiliate applications     | [Affiliate Applications](/core-concepts/affiliates/affiliate-applications)    |


# Attribution

Attribution is the process of linking a user's conversion (swap, deposit, mint, etc.) back to the referrer who brought them in. When a referred user converts, Fuul's attribution system determines which referrer gets credit — and rewards.

## How attribution works

1. **User clicks a referral link** — A pageview event is recorded with the referrer's information
2. **User connects their wallet** — The tracking session is linked to the user's identity
3. **User performs a qualifying action** — The trigger fires and the system looks up who referred this user
4. **Attribution is made** — The referrer is credited and a payout is calculated

{% hint style="info" %}
Users can also accept a referral code directly (without clicking a link), which immediately establishes the referrer relationship for all future conversions.
{% endhint %}

## Attribution settings

Attribution behavior is configured per project at **Settings > Payouts & Attribution**. Two settings control how credit is assigned:

### Attribution window

The number of days during which a referrer can receive credit for a conversion.

| Setting                  | Description                                                                             |
| ------------------------ | --------------------------------------------------------------------------------------- |
| **Default: 10,000 days** | Effectively lifetime — the referrer gets credit as long as the user eventually converts |
| **Custom window**        | Set a shorter window (e.g., 30 days) to only credit recent referrals                    |

### Attribution type

Determines which referrer gets credit when a user has been referred by multiple people.

| Type                      | Who gets credit                            | Example                                                                  |
| ------------------------- | ------------------------------------------ | ------------------------------------------------------------------------ |
| **First Click** (default) | The very first referrer within the window  | Day 1: Referrer A, Day 3: Referrer B, Day 5: conversion → **Referrer A** |
| **Last Click**            | The most recent referrer before conversion | Same scenario → **Referrer B**                                           |

### Lifetime attribution

When enabled (recommended for most programs), all future conversions from a referred user are attributed to their original referrer — the one credited with the initial conversion.

| Setting               | Behavior                                                                                            |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| **Enabled** (default) | Once attributed, the relationship is permanent. All future conversions go to the original referrer. |
| **Disabled**          | Each conversion is evaluated independently using the attribution window and type.                   |

{% hint style="success" %}
Lifetime attribution is enabled in the vast majority of programs. It ensures referrers get ongoing credit for the users they bring in, which is the strongest incentive for long-term referral partnerships.
{% endhint %}

## Referral tracking

To track referrals, projects need to integrate the [Fuul Web SDK](/developer-guide/getting-started-with-fuul-web-sdk) to capture pageview and wallet connection events. Learn more in the [Tracking Referrals](/developer-guide/tracking-referrals-in-your-app) developer guide.

## Self-referral prevention

Fuul prevents affiliates from referring themselves. If a user's identifier matches their referrer's identifier, the attribution is rejected.


# Multi-Level Referrals

Fuul supports up to **4 levels** of referral attribution, allowing you to reward not just the direct referrer, but also the affiliates who brought them into the program.

## How it works

| Level       | Who                             | Example                                                                     |
| ----------- | ------------------------------- | --------------------------------------------------------------------------- |
| **Level 1** | Direct referrer (the affiliate) | Alice refers Bob                                                            |
| **Level 2** | Referrer's referrer             | Carol referred Alice, who referred Bob                                      |
| **Level 3** | Third level up the chain        | Dave referred Carol, who referred Alice, who referred Bob                   |
| **Level 4** | Fourth level up the chain       | Eve referred Dave, who referred Carol, who referred Alice, who referred Bob |

When Bob converts, all four levels can receive a payout — each with its own amount or percentage, configured in the incentive rules.

## Per-tier multi-level amounts

When using audiences (tiers), each tier can define its own multi-level payout amounts independently. For example:

* **Standard tier**: Level 1 = 5%, Level 2 = 2%
* **VIP tier**: Level 1 = 10%, Level 2 = 5%, Level 3 = 2%, Level 4 = 1%

This lets you reward higher-performing affiliates with better rates at every level of their referral chain, not just at the top level.

## Viewing multi-level data

The [Affiliate Dashboard](/developer-guide/affiliate-dashboard) provides multi-level stats that distinguish between referral levels:

| Level                                                | Description                                            |
| ---------------------------------------------------- | ------------------------------------------------------ |
| **Level 1 (referred\_volume)**                       | Volume from users directly referred by the affiliate   |
| **Level 2 + Level 3 + Level 4 (multilevel\_volume)** | Volume from second, third, and fourth-degree referrals |
| **Total (total\_volume)**                            | Combined Level 1 + Level 2 + Level 3 + Level 4 volume  |

Affiliates can also view their full referral tree to understand how their network generates value.


# Sharing Rewards with Referred Users

Affiliates can automatically share a portion of their rewards with the users they refer. This creates a mutual incentive: affiliates earn more by bringing in users, and referred users get rewarded for completing actions.

## How it works

When an affiliate creates a referral code with a **rebate rate**, that percentage of their payout is automatically transferred to the referred user at payout time.

|                        | Rate    |
| ---------------------- | ------- |
| Affiliate commission   | 20%     |
| Rebate rate            | 5%      |
| **Affiliate receives** | **15%** |
| **Referred user gets** | **5%**  |

The rebate rate is set on the referral code and **locked at the moment a user accepts it**. If the rate changes later, existing referrer relationships keep their original locked rate — only new relationships use the updated rate.

{% hint style="info" %}
The rebate is taken from the affiliate's share, not added on top. The total reward budget for the project remains the same.
{% endhint %}

## Configuration

Set the rebate rate when generating a referral code. The maximum allowed rebate rate is **20%** — values above this are automatically capped.

{% hint style="info" %}
See [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes) for the full affiliate code API, including the `rebateRate` parameter and the Updating Rebate Rates section.
{% endhint %}

## Supported payout types

Double side rewards apply to **variable payout terms only** — programs where rewards are calculated as a percentage of volume or revenue. Fixed rewards and pool distributions do not support rebates.

## Use cases

* **VIP affiliate programs** — top affiliates attract high-value users by passing on part of their commission
* **Two-sided marketplaces** — both the referrer and the new user need an incentive to participate
* **Referral competitions** — affiliates compete by offering the best rebate to maximize their referred volume


# Affiliates

Affiliates are the people and entities that promote your project in exchange for rewards. Fuul gives you full control over how affiliates join, how their activity is tracked, and how they get paid.

## Key topics

| Topic                                                                                         | Description                                                                                                                           |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| [Affiliate Codes vs Referral Codes](/core-concepts/affiliates/referral-codes-vs-invite-codes) | Understand the difference between affiliate codes (created by affiliates) and referral codes (created by projects on behalf of users) |
| [Affiliate Applications](/core-concepts/affiliates/affiliate-applications)                    | Set up an application flow so affiliates can request to join your program                                                             |
| [Affiliate Management Dashboard](/core-concepts/affiliates/affiliate-management-dashboard)    | Review, approve, and manage affiliates from the dashboard                                                                             |
| [Tax Information](/core-concepts/affiliates/tax-information)                                  | Collect tax forms and handle compliance requirements for affiliate payouts                                                            |


# Affiliate Codes vs Referral Codes

Fuul has two distinct code systems: **affiliate codes** for tracking link attribution, and **referral codes** for establishing referrer-user relationships directly. An affiliate code can also be activated as a referral code within a specific project.

## Affiliate codes

An affiliate code is a **personal, branded identifier** that an affiliate uses in tracking links. Instead of sharing a raw wallet address, the affiliate creates a custom code for cleaner URLs.

|                            | URL                                                                     |
| -------------------------- | ----------------------------------------------------------------------- |
| **Without affiliate code** | `https://yourwebsite.com?af=0x1f9090aae28b8a3dceadf281b0f12828e676c326` |
| **With affiliate code**    | `https://yourwebsite.com?af=my-affiliate-code`                          |

When a user clicks the link, attribution happens automatically through the pageview → wallet connection flow. The affiliate doesn't need to share a separate code — the link does all the work.

**Example:** A KOL creates the affiliate code `crypto-guy` and shares tracking links on Twitter. Followers click the link, connect their wallet on the dapp, and all their future conversions are attributed to the KOL — without the user ever typing a code.

|                  | Affiliate codes                                                                       |
| ---------------- | ------------------------------------------------------------------------------------- |
| **Created by**   | Affiliate (requires wallet signature)                                                 |
| **Format**       | User-defined (e.g., `crypto-guy`)                                                     |
| **Scope**        | Global — one code per affiliate across all Fuul projects                              |
| **How it works** | Embedded in tracking links (`?af=code`), attribution is automatic via pageview events |
| **Best for**     | KOLs, influencers, affiliates sharing links on social media or content                |

{% hint style="info" %}
Create and manage affiliate codes → [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes)
{% endhint %}

## Referral codes

A referral code is a **code that a user explicitly accepts** to establish a referrer-user relationship. The referred user enters the code in your UI and calls the SDK to accept it — no link click needed.

Referral codes are **generated by the project** on behalf of users via Fuul's API/SDK. They are auto-generated (random 7-character alphanumeric, e.g., `A1B2C3D`) and scoped to a single project.

**Example:** A DeFi protocol generates referral codes for its users. Alice gets code `A1B2C3D` and shares it in a Discord group. Bob opens the dapp, enters Alice's code, and signs a message to accept it. From that point on, Bob's conversions are attributed to Alice.

|                  | Referral codes                                                                                |
| ---------------- | --------------------------------------------------------------------------------------------- |
| **Created by**   | Project generates on behalf of users (via API/SDK)                                            |
| **Format**       | Auto-generated 7-char alphanumeric (e.g., `A1B2C3D`)                                          |
| **Scope**        | Project-specific                                                                              |
| **How it works** | User explicitly accepts the code via SDK/API, creating a permanent referrer-user relationship |
| **Best for**     | In-app referral programs, access gating, community-driven growth                              |

### Use case: access gating (invite codes)

A common pattern is using referral codes as **invite codes** to gate access to a product. Your frontend calls `getReferralStatus` to check whether a user was referred, and only lets them proceed if they were. Fuul doesn't enforce the gate — your application logic does.

**Example:** A perpetuals protocol in closed beta issues referral codes to early users. When someone visits the dapp, the frontend checks their referral status and grants access only if a valid code was used. The restriction lives in the project's own code, not in Fuul.

{% hint style="info" %}
"Invite code" is not a separate code type — it's a referral code used for access gating. The underlying mechanism is identical.
{% endhint %}

## Using an affiliate code as a referral code

An affiliate code is global and lives at the affiliate level. But a project can **activate** an affiliate code as a referral code within its own program — assigning it project-specific properties like maximum uses and rebate rates.

This means a single custom code (e.g., `crypto-guy`) can work as both:

* An **affiliate code** globally — for tracking link attribution across any Fuul project
* A **referral code** within a specific project — with uses, rebate rate, and other per-project configuration

**Example:** A KOL creates the affiliate code `crypto-guy`. A DeFi protocol activates that code as a referral code in their program with 100 max uses and a 5% rebate rate. Now the KOL's followers can either click a tracking link *or* enter `crypto-guy` directly in the dapp — both paths establish the referrer relationship.

{% hint style="info" %}
Think of it as two layers: the **affiliate code** is the identity layer (global, custom, owned by the affiliate) and the **referral code** is the project layer (uses, rebate, scoped per project). When a project activates an affiliate code, it adds the project layer on top.
{% endhint %}

## Side-by-side comparison

|                           | Affiliate code                                      | Referral code                                                                                 |
| ------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **Purpose**               | Track link-based attribution                        | Establish referrer-user relationship via code entry                                           |
| **Created by**            | Affiliate (wallet signature required)               | Project (via API/SDK, on behalf of users)                                                     |
| **Scope**                 | Global (1 per affiliate)                            | Project-specific                                                                              |
| **Format**                | User-defined (e.g., `crypto-guy`)                   | Auto-generated 7-char (e.g., `A1B2C3D`)                                                       |
| **How users interact**    | Click a link — no code entry needed                 | Enter and accept a code explicitly                                                            |
| **Rebate support**        | Only when activated as a referral code in a project | Yes — via [Sharing Rewards with Referred Users](/core-concepts/referrals/double-side-rewards) |
| **Can become the other?** | Yes — projects can activate it as a referral code   | No — referral codes are always project-generated                                              |

{% hint style="info" %}
Both mechanisms create referrer-user relationships and can trigger the same payout rules. The difference is **how the relationship is established**: link click (affiliate code) vs explicit code acceptance (referral code).
{% endhint %}

## Developer guides

* Generate and manage affiliate codes → [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes)
* Generate, accept, and manage referral codes → [Referral Codes](/developer-guide/referral-codes)
* Track referral links via SDK → [Tracking Referrals in Your App](/developer-guide/tracking-referrals-in-your-app)


# Affiliate Applications

Projects can require potential affiliates to apply before joining their referral program. This gives you control over who promotes your project and lets you vet applicants before granting access.

## How it works

1. **Build an application form** — Use the visual Form Builder to create custom fields (text, email, number, select, and more)
2. **Affiliates apply** — Potential affiliates submit the form with their information
3. **Review applications** — Your team reviews applications in the dashboard and approves, rejects, or puts them on hold
4. **Approved affiliates join an audience** — Approved applicants are added to a specific audience, which can have its own reward tiers and rules

## Form Builder

The Form Builder lets you create and edit your application form visually — no code required. Access it at **Affiliates > Affiliate Application**, then open the Form Builder.

### Supported field types

Text, Email, Textarea, Number, Select, Radio, Checkbox, URL

### What you can configure per field

* Label, placeholder, and description
* Required vs optional
* Options list (for Select and Radio fields)
* Validation rules: minimum/maximum length, min/max value, regex pattern

### Other capabilities

* **Drag & drop reordering** — rearrange fields by dragging them
* **Live preview** — each field shows how it will render for applicants
* **Enable/Disable toggle** — pause the form without deleting it. Disabling shows a confirmation and automatically rejects new submissions while disabled
* **Unsaved changes protection** — warns before navigating away with unsaved edits

{% hint style="info" %}
When no form is configured yet, the Affiliate Applications view shows an empty state with a **Create Form** button to get started.
{% endhint %}

## Application statuses

| Status       | Description                                                                             |
| ------------ | --------------------------------------------------------------------------------------- |
| **Pending**  | Application received, awaiting review                                                   |
| **Approved** | Affiliate accepted — added to a designated audience with access to the referral program |
| **Rejected** | Application declined — a rejection reason can be provided                               |
| **On Hold**  | Application saved for later review                                                      |

{% hint style="info" %}
All status decisions can include **admin notes** for internal record-keeping. Batch operations are available for processing multiple applications at once.
{% endhint %}

## Managing applications

Applications are managed in the dashboard at **Affiliates > Affiliate Application**. The management view includes:

* **Summary cards** — Quick overview of total, pending, approved, rejected, and on-hold applications
* **Applications table** — Dynamic columns based on your form fields, plus address, date, status, and admin notes
* **Search and sort** — Find applications by user identifier, sort by date or status
* **Bulk actions** — Select multiple applications and approve, reject, or hold in batch

## Audience assignment

When approving an affiliate, you select a **static audience** to add them to. This audience determines what reward structure the affiliate receives (tier, multipliers, payout rules).

{% hint style="success" %}
Combine affiliate applications with [Audiences](/developer-guide/managing-audiences) to create tiered affiliate programs — different tiers with different commission rates based on applicant quality or performance.
{% endhint %}

## Tier-based Approval

Affiliates are assigned a tier based on performance metrics. Each tier is configured by the project with its own name, rank, and rebate rate. The tier determines the affiliate's rebate rate — it does not control the multilevel referral tree depth.

### Tier Protection

When an affiliate advances tiers, they enter a **protection period** (configurable 1 to 365 days, default: 30). During this window, their tier cannot be downgraded even if metrics drop below the threshold. This prevents affiliates from being penalized for short-term volume fluctuations.

The dashboard exposes this through a `TierProtectionCard` component that shows:

* Current protection status (active/expired)
* Remaining days until protection expires
* The tier they're protected at

### CSV Export

Project admins can export the complete list of affiliates and their applications as a CSV file. The export includes the current tier, protection status, protection days remaining, and current metrics. Use the "Export" button on the Affiliate Management Dashboard.

## API integration

Projects can also accept affiliate applications programmatically through the API — for example, to embed the application form in a custom frontend.

Retrieve the form configuration to render it on your site ([API reference](https://fuul.readme.io/reference/get_v1-projects-projectid-affiliate-application-form-config)):

```typescript
// GET /v1/projects/{projectId}/affiliate-application-form-config
// Returns: form fields with labels, types, validation rules, and ordering
```

Submit an application on behalf of a user ([API reference](https://fuul.readme.io/reference/post_v1-projects-projectid-affiliate-applications)):

```typescript
// POST /v1/projects/{projectId}/affiliate-applications
// Body: { user_identifier, identifier_type, form_responses: { ... } }
```

Retrieve commission rates per tier for display in the application flow ([API reference](https://fuul.readme.io/reference/get_public-api-v1-affiliate-portal-commission-rates)):

```typescript
// GET /public-api/v1/affiliate-portal/commission-rates
// Returns: commission rates grouped by tier, useful for showing potential affiliates what they'd earn
```

{% hint style="info" %}
Both form endpoints are public — no API key required. The submission endpoint is rate-limited to 10 requests per 60 seconds per user identifier.
{% endhint %}


# Affiliate Management Dashboard

The affiliate management dashboard gives program admins full visibility and control over their affiliate base — from reviewing incoming applications to managing tiers, badges, and individual performance.

Access it at **Affiliates → Dashboard** in the Fuul app.

## Affiliate list

View all active affiliates in your program with their performance metrics:

| Metric              | Description                                       |
| ------------------- | ------------------------------------------------- |
| **Volume**          | Total action volume attributed to this affiliate  |
| **Revenue**         | Fees or revenue generated by their referred users |
| **End users**       | Total unique users brought in                     |
| **Earnings**        | Total payouts per currency                        |
| **Tier / Audience** | Which reward tier they currently qualify for      |

### Tier details

Each affiliate's profile shows:

* **Active tier** — The effective tier after accounting for any protection period
* **Current tier** — The tier based on current metrics, before protection is applied
* **Tier protection** — Days remaining in the protection period (1–365)

Filter by status (active / suspended), region, or audience. Multiselect filtering by specific conversions and audience IDs is also supported.

## Applications

When your program requires approval before affiliates can join, review and process incoming applications here.

| Action      | Description                                       |
| ----------- | ------------------------------------------------- |
| **Approve** | Add the affiliate to a static audience (required) |
| **Reject**  | Record a rejection reason                         |
| **Hold**    | Keep for later review                             |

{% hint style="info" %}
Click any affiliate to view their referral tree, audience memberships, badges, status, and activity history.
{% endhint %}

## Master Affiliates

A master affiliate manages a pool of sub-affiliates and earns a spread on each conversion they generate. Admins designate any existing affiliate as a master affiliate — a marketing agency is a common example.

Master affiliate list and detail responses include aggregated metrics:

| Field                  | Description                                                                     |
| ---------------------- | ------------------------------------------------------------------------------- |
| `sub_affiliates_count` | Number of sub-affiliates bound to this master affiliate                         |
| `referred_volume_usd`  | Combined L1 volume of the master's sub-affiliates (not the master's own volume) |

**Admin API endpoints** (require Admin role):

| Endpoint                                                            | Description                                                                             |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `GET /api/v1/projects/:projectId/agencies`                          | List all master affiliates with aggregated metrics                                      |
| `GET /api/v1/projects/:projectId/agencies/:agencyId`                | Master affiliate detail with per-currency earnings                                      |
| `GET /api/v1/projects/:projectId/agencies/:agencyId/sub-affiliates` | Sub-affiliates with individual volume and earnings. Supports `sort_by=referred_volume`. |

Admins can remove master status from an affiliate — this downgrades it back to a regular sub-affiliate while preserving all historical attribution and payout data.

{% hint style="info" %}
Master affiliate payouts (`agency_payout`) are excluded from leaderboard rankings. Only `affiliate_payout` and `end_user_payout` movements count toward leaderboard position.
{% endhint %}

## Audiences, tiers & badges

The dashboard shows each affiliate's audience memberships and badges. These features are not exclusive to affiliates — they apply to any user in the program.

* **Audiences & Tiers** — segment users and assign different reward rates → [Tiers & Multipliers](/core-concepts/tiers-and-multipliers)
* **Badges** — visual recognition for users based on audience membership → [Badges](/incentives-manager/badges)

{% hint style="info" %}
Approving an affiliate application automatically adds them to the audience you select during approval. This is the recommended way to onboard affiliates into a specific tier.
{% endhint %}


# Tax Information

Projects can require affiliates to submit tax information (W-9, W-8BEN, or W-8BEN-E forms) before they can claim rewards. This is separate from the end-user claiming flow — only affiliate payouts are affected.

## How It Works

When `require_tax_info` is enabled on a project:

* Affiliates with at least one affiliate payout must have their tax info in `approved` status to claim
* Affiliates without any affiliate payouts (only end-user payouts) are not blocked
* Claim flows return a **restricted payload** instead of a 403 error when the gate is active but not passed

## Affiliate Self-Service

Affiliates manage their tax information through the affiliate portal:

| Action              | Endpoint                                                      |
| ------------------- | ------------------------------------------------------------- |
| Submit tax info     | `POST /api/v1/projects/:projectId/affiliate-portal/tax-info`  |
| View submitted info | `GET /api/v1/projects/:projectId/affiliate-portal/tax-info`   |
| Check status        | `GET /api/v1/projects/:projectId/affiliate-portal/tax-status` |

## Tax Status Values

| Status          | Meaning                                                     |
| --------------- | ----------------------------------------------------------- |
| `not_required`  | Project does not require tax info                           |
| `not_submitted` | Required but affiliate hasn't submitted                     |
| `submitted`     | Form submitted, pending review                              |
| `approved`      | Approved — affiliate can claim                              |
| `revoked`       | Revoked by admin                                            |
| `expired`       | W-8 form expired (W-8 forms typically expire after 3 years) |

{% hint style="warning" %}
W-8 forms (W-8BEN, W-8BEN-E) expire after 3 years. When expired, the affiliate must resubmit before claiming.
{% endhint %}

## Claim Blocking Behavior

When `require_tax_info` is `true` and the affiliate has at least one affiliate payout but their tax info is not `approved`, the claim endpoints return a **restricted payload** instead of the full data.

| Endpoint                          | Behavior                                                                                                                             |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /api/v1/claim-checks`        | Returns HTTP 200 with the standard list shape. Restricted items include `restriction_message` and omit `deadline_seconds`.           |
| `POST /api/v1/claim-checks/claim` | Returns HTTP 200 with restricted rows. Restricted rows include `restriction_message` and omit `deadline`, `proof`, and `signatures`. |
| `POST /api/v1/claim-checks/close` | Returns the full `ClaimResponse` on success.                                                                                         |

{% hint style="info" %}
End-user payout claims are never blocked — only affiliate claims are subject to the tax gate.
{% endhint %}

## Admin Endpoints

| Action                                  | Endpoint                                                                |
| --------------------------------------- | ----------------------------------------------------------------------- |
| View full tax info (unmasked)           | `GET /api/v1/projects/:projectId/affiliates/:userIdentifier/tax-status` |
| List affiliates (includes `tax_status`) | `GET /api/v1/projects/:projectId/affiliates`                            |
| Update tax settings                     | `PATCH /api/v1/projects/:projectId/tax-settings`                        |

Tax settings include `require_tax_info`, company legal name, address, tax ID, and contact email — all used for tax reporting purposes.

## Tax info fields

### Affiliate tax info

| Field            | Description                                   |
| ---------------- | --------------------------------------------- |
| `form_type`      | `w9`, `w8ben`, or `w8bene`                    |
| `status`         | `submitted`, `approved`, `revoked`, `expired` |
| `legal_name`     | Legal name of the individual or entity        |
| `country`        | Country of residence or incorporation         |
| `tax_id_number`  | Encrypted — SSN/EIN (W-9) or foreign TIN      |
| `signature_date` | Date the form was signed                      |
| `expires_at`     | Expiration date (W-8 forms)                   |
| `revoked_reason` | Reason for revocation (if revoked)            |

### Project tax settings

| Field              | Description                                             |
| ------------------ | ------------------------------------------------------- |
| `require_tax_info` | Whether affiliates must submit tax info before claiming |
| `company_*`        | Company legal name, address, tax ID, contact email      |


# Budgets & Smart Contracts

To run an incentive program that distributes onchain token rewards, projects deploy a smart contract that holds funds and manages payouts. This contract is **fully controlled by the project** — Fuul has no access to or control over it.

{% hint style="info" %}
Budgets are only required for **token rewards**. Points-only programs don't need a smart contract or budget.
{% endhint %}

## How it works

| Step           | What happens                                                                                                   |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| **Deploy**     | Initialize your program through the Fuul webapp — this deploys a smart contract you fully own                  |
| **Fund**       | Deposit tokens into the contract via the webapp, or send funds directly from any wallet or multisig            |
| **Distribute** | When users earn rewards, Fuul generates signed claim checks (vouchers) that users redeem against your contract |
| **Recover**    | Set expiration periods on claim checks to recover unclaimed rewards and reallocate them                        |

## Key properties

| Property                      | Description                                                                                              |
| ----------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Non-custodial**             | Your project is the sole administrator of the smart contract. Fuul never has access to your funds.       |
| **One budget per program**    | All active payout rules draw from the same budget                                                        |
| **Multi-chain**               | Deploy on any supported EVM chain (Arbitrum, Base, Optimism, HyperEVM, and more)                         |
| **Direct transfers**          | Funds can be sent directly to the contract address from any source — no additional approval steps needed |
| **Withdraw anytime**          | Funds can be withdrawn at any time through the webapp                                                    |
| **Unclaimed reward recovery** | Define expiration periods for claim checks. Expired, unclaimed rewards can be recovered and reused.      |
| **Claim on behalf**           | Projects can claim rewards on behalf of users — no action required from end users, improving UX          |

{% hint style="warning" %}
If the smart contract does not have sufficient balance, reward claims will fail. Monitor your budget and top up as needed.
{% endhint %}

## Supported tokens

You can distribute rewards in any whitelisted ERC-20 token or native tokens (ETH, etc.) on supported chains.

{% hint style="info" %}
To use your token for onchain payouts, Fuul requires a token whitelisting process. Contact <ecosystem@fuul.xyz> for details.
{% endhint %}

For contract addresses and technical details, see [Smart Contracts](/protocol-reference/smart-contracts).


# Rewards Pool Budget Alerts

Budget alerts notify you in Slack before a rewards pool runs low, so onchain payouts never stall. Instead of checking your program balance manually, you set thresholds once and Fuul watches the pool for you.

{% hint style="info" %}
Budget alerts apply to programs that pay **onchain token rewards** from a smart contract budget. Points-only programs don't need them.
{% endhint %}

## How it works

Fuul reads your program's onchain balance once a day and compares it against the thresholds you configured.

| State                 | What happens                                                                                          |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| **Healthy**           | No message. Alerts stay silent while the pool is in good shape.                                       |
| **Threshold crossed** | Fuul posts an alert to your Slack channel with the current balance, the alert type, and the severity. |
| **Recovered**         | Fuul posts an all-clear in the same channel once the pool is healthy again.                           |

{% hint style="info" %}
No spam: the same alert is not repeated day after day. It fires again only if the situation gets worse and crosses a higher severity.
{% endhint %}

## Alert types

You can enable one or both alert types. They are evaluated independently, so one can fire without the other.

| Type         | Question it answers                                                                                   | Fires when                                                                     |
| ------------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Coverage** | Does the pool have enough to cover what users and affiliates have already earned but not yet claimed? | The margin between the balance and the earned rewards shrinks past a threshold |
| **Balance**  | Has the balance dropped a lot since the last deposit?                                                 | The balance falls below a set fraction of your most recent top-up              |

## Severity levels

Each alert type has four severity levels, from the earliest warning to the most urgent:

* **Warning**
* **Breach**
* **Critical**
* **Super Critical**

Enable the severities you want for each alert type. Every severity carries an optional custom trigger ratio: set your own value, or leave it blank to use Fuul's default for that alert type.

## Configuring alerts

Project admins configure budget alerts from **Settings → Notifications** in the dashboard.

1. Enable the alert types you want to monitor (Coverage, Balance, or both).
2. Turn on the severity levels you care about, and optionally set a custom trigger ratio for each.
3. Add the Slack webhook URL where alerts should be posted.

Changes take effect on the next daily check.

{% hint style="warning" %}
The Slack destination must be an incoming webhook (`https://hooks.slack.com/...`). For security, Fuul stores the webhook write-only: it is never displayed back after you save it.
{% endhint %}

## Use cases

* Keep programs running without checking the budget manually every day.
* Get warned early, before a low balance causes reward claims to fail.
* Route alerts to a dedicated ops channel so the right people act the moment a pool needs a top-up.

For how program budgets and smart contracts work, see [Budgets & Smart Contracts](/core-concepts/budgets-and-smart-contracts).


# Fraud Prevention

Fuul includes multiple layers of protection to ensure that only legitimate users receive rewards, safeguarding your budget from sybil attacks, self-referrals, and other abuse.

## Sybil detection

A sybil attack occurs when someone creates multiple fake identities (wallets) to exploit your rewards system — claiming airdrops, manipulating staking rewards, or inflating referral counts with fake accounts.

Fuul uses machine learning and behavioral analysis to detect and prevent these attacks:

| Protection                       | How it works                                                                                                                                                                                 |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Behavioral cluster detection** | Detects wallets likely operated by the same user based on onchain behavior patterns                                                                                                          |
| **Self-referral detection**      | Detects when a referrer and end user share the same browser session (tracking ID), indicating the user referred themselves                                                                   |
| **Bot activity detection**       | Analyzes frontend and onchain data to identify automated software                                                                                                                            |
| **Payout caps**                  | Limit the maximum rewards a single account can earn within a time window (e.g., monthly caps). Caps are currency-scoped — only rewards paid in the cap's own currency count toward its limit |
| **Continuous monitoring**        | Real-time adaptation based on evolving patterns, with detailed reporting on flagged accounts                                                                                                 |

### What happens when fraud is detected?

When fraud is detected:

* The **referrer payout is blocked** — the fraudulent referrer does not receive rewards
* The **end user still receives their payout** — legitimate user activity is not penalized
* The attribution is flagged and remains visible in the dashboard and attribution search results — it is not deleted

{% hint style="info" %}
Self-referral detection is **always enabled** for all projects. Other fraud detection features can be configured per project.
{% endhint %}

## Blacklist

Projects can maintain a blacklist of addresses that should be excluded from earning any rewards. When a blacklisted address triggers an event, the execution is automatically rejected — no attribution or payout is created.

| Feature                | Description                                                                                                      |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Project-scoped**     | Each project manages its own blacklist. An address blacklisted by one project can still earn rewards in another. |
| **Immediate effect**   | Once an address is added, all subsequent trigger executions for that address are rejected                        |
| **Managed via webapp** | Add or remove addresses with optional labels for identification                                                  |

{% hint style="warning" %}
Removing an address from the blacklist does **not** retroactively approve previously rejected executions. Only future activity is affected.
{% endhint %}

## Wallet screening

Wallet screening checks whether a wallet address is allowed to receive payouts at the time rewards are distributed. This is a system-level compliance check separate from the project blacklist.

Wallet screening is **opt-in per project** and runs against a compliance screening provider (e.g., Chainalysis). To enable it for your project, reach out to the Fuul team. Screening applies to EVM and Solana addresses.

| Feature                          | Description                                                                                                                          |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Opt-in per project**           | Configured per project against a compliance provider (e.g., Chainalysis)                                                             |
| **Runs at payout distribution**  | Screening happens when a payout is about to be distributed                                                                           |
| **Blocked payouts are recorded** | Blocked wallets still have their payouts persisted for audit purposes                                                                |
| **Fail-open**                    | If screening encounters an error, the wallet is allowed through — preventing legitimate wallets from being blocked by service issues |

{% hint style="info" %}
Wallet screening is separate from the blacklist: the blacklist is managed by each project for their own needs, while wallet screening is a platform-level compliance mechanism you enable with a screening provider.
{% endhint %}

## Viewing fraud activity

In the **Fraud Detection** tab of the dashboard, you can see everything Fuul detects and blocks:

* View transactions marked as pending and decide whether to approve or reject them
* See transactions that have been automatically declined
* Review flagged accounts and their activity patterns

{% embed url="<https://drive.google.com/file/d/1BaTlhH3Q6LX4-KC4ocjtRvFU1PM02Vsw/view?usp=sharing>" %}


# Quickstart

The Fuul MCP (Model Context Protocol) server lets you manage your Fuul programs directly from AI coding assistants like Claude Code. Once connected, your AI agent can query affiliate analytics, inspect incentive programs, manage payouts, and send conversion events.

{% hint style="info" %}
MCP is an open standard that lets AI assistants connect to external tools and APIs. The Fuul MCP server implements this protocol so any MCP-compatible client can interact with your Fuul account.
{% endhint %}

## What you can do

| Capability                  | Description                                                         |
| --------------------------- | ------------------------------------------------------------------- |
| Query projects & incentives | List programs, view and edit triggers, and review conversion setups |
| Affiliate analytics         | Get stats breakdowns by audience, tier, region, or status           |
| Manage payouts              | Review pending payouts and approve or reject in bulk                |
| Send events                 | Trigger custom conversion events for testing or automation          |
| Managed affiliates          | Create and update affiliate records programmatically                |

## Quickstart

This guide walks you through connecting the Fuul MCP server to Claude Code in three steps.

{% hint style="info" %}
These steps require [Claude Code](https://claude.ai/code) installed and running. The Fuul MCP server works with any MCP-compatible AI client, but the installation commands below are specific to Claude Code.
{% endhint %}

### 1. Add the marketplace

In Claude Code, add the Kuyen Labs marketplace — this is the registry that hosts the Fuul MCP plugin:

```
/plugin marketplace add kuyen-labs/mcp_server
```

### 2. Install the Fuul MCP plugin

Once the marketplace is added, install the plugin:

```
/plugin install fuul-mcp@fuul-mcp
```

### 3. Log in to your Fuul account

Run the login command from your terminal:

```bash
npx -y --package=@fuul/mcp-server@latest fuul-mcp login
```

When prompted for the API URL, enter:

```
https://api.fuul.xyz/
```

A browser window will open for OAuth authentication. Sign in with your Fuul account — the same credentials you use at [app.fuul.xyz](https://app.fuul.xyz).

{% hint style="success" %}
Once authenticated, your session token is stored at `~/.fuul/tokens.json` (Windows: `%USERPROFILE%\.fuul\tokens.json`). The MCP server reads this file automatically on every request — you only need to log in once.
{% endhint %}

### Verify the connection

Back in Claude Code, ask it to find your project:

> "Find my project called \[your project name]"

If the connection is working, Claude will return your project details directly from the Fuul API.

{% hint style="warning" %}
If you get a 401 error, your session may have expired. Re-run the login command from step 3 to refresh your token.
{% endhint %}


# Managing Incentives

The Fuul MCP lets you create, edit, and delete incentives directly from your AI assistant — without touching the dashboard.

| Section                                                                                   | Description                                                                          |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [Editing Incentives](/fuul-mcp-server/managing-incentives/editing-incentives)             | Update payout amounts, who gets rewarded, and calculation strategy                   |
| [Add & Remove Incentives](/fuul-mcp-server/managing-incentives/add-and-remove-incentives) | Create new incentives linked to existing triggers, or remove ones you no longer need |
| [Incentive Analytics](/fuul-mcp-server/managing-incentives/incentive-analytics)           | Rank programs by performance, get stats and time series for individual incentives    |


# Add & Remove Incentives

The Fuul MCP lets you create new incentives linked to existing triggers, or permanently delete ones you no longer need.

{% hint style="warning" %}
Changes made via MCP are saved as **drafts**. They do not take effect in production until you go to **app.fuul.xyz → Incentives → Publish**.
{% endhint %}

## Creating an incentive

### 1. Find your project

Tell the MCP your project name and it will look it up to get the project ID.

> "Find my project called Fuul"

### 2. Check available triggers and reward types

Before creating an incentive you need to know:

* **Which trigger** to attach it to — the trigger defines the on-chain or off-chain action that qualifies a user for a reward.
* **How rewards will be calculated** — flat per conversion, rate per volume, shared pool, or leaderboard.

{% hint style="info" %}
Not sure which reward type fits your program? See [Incentive Payouts](/core-concepts/incentive-payouts) for a full breakdown of each type.
{% endhint %}

### 3. Create the incentive

Once you know the trigger and how you want to reward users, describe what you want:

> "Create an incentive called 'Swap Rewards' linked to the swap trigger, with a fixed reward of 10 points per end user"

> "Set up a variable incentive on the deposit trigger — 5 points per USD deposited for both end users and affiliates"

> "Create a pool payout incentive with a 10,000 point budget split weekly by volume, linked to the LP trigger"

| Type of incentive | How it works                                                               |
| ----------------- | -------------------------------------------------------------------------- |
| `fixed`           | Flat reward per conversion, regardless of volume                           |
| `variable`        | Rate per unit of volume (e.g. points per USD). Requires a `base_currency`. |
| `pool`            | Fixed budget split proportionally among participants over a period         |
| `leaderboard`     | Users ranked by activity; each position receives a predetermined reward    |

### 4. Publish to production

After creating, **draft changes are not live until published**:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**

***

## Deleting an incentive

### 1. List the project's incentives

Get the current list to identify which one to remove:

> "List our incentives"

The MCP will return each incentive with its name and internal ID.

### 2. Delete the incentive

> "Delete the incentive called 'Old Swap Rewards'"

### 3. Publish to production

After deleting, go to the app and publish to apply the change:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**


# Editing Incentives

The Fuul MCP lets you inspect and update incentive configurations directly from your AI assistant — including payout amounts, who gets rewarded, and the reward calculation strategy.

{% hint style="warning" %}
Changes made via MCP are saved as **drafts**. They do not take effect in production until you go to **app.fuul.xyz → Incentives → Publish**.
{% endhint %}

## 1. Find your project and incentives

> "Find my project called Fuul and list our current configured incentives"

## 3. Edit payout amounts

You can target individual incentives or apply a change across all of them.

### Change a specific amount

| Goal                        | Example prompt                                                   |
| --------------------------- | ---------------------------------------------------------------- |
| Update end user payout only | "Set the end user payout for \[incentive name] to 5"             |
| Update referrer payout only | "Set the referrer payout for \[incentive name] to 10"            |
| Update both                 | "Double the end user and referrer payouts for \[incentive name]" |

{% hint style="info" %}
Only applies to incentives where `payee_type` is `affiliate` or `both`. If the incentive only rewards end users, you'd need to change the `payee_type` first.
{% endhint %}

### Bulk-adjust all incentives by a percentage

Useful when you want to align all reward amounts with a target APR or a program-wide budget change:

> "Reduce all incentive payouts by 20%"

## 4. Change who gets rewarded

Each incentive has a `payee_type` that controls which party receives the reward. You can change it at any time:

| Value       | Who gets rewarded       |
| ----------- | ----------------------- |
| `both`      | End user and referrer   |
| `end-user`  | End user only           |
| `affiliate` | Referrer/affiliate only |

Example prompts:

> "Update \[incentive name] to only reward referrers"

> "Change \[incentive name] to reward end users only with 15 points"

> "Set \[incentive name] to reward both sides — 2 points for end users, 6 for referrers"

## 5. Change the calculation strategy

The `calculation_strategy` controls how the reward amount is interpreted:

| Strategy            | Behavior                                                                                                                                                                                                                 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `variable`          | Amount is a **rate per unit of volume** (e.g. points per USD traded). Requires a `base_currency`.                                                                                                                        |
| `fixed`             | Amount is a **flat reward per conversion** regardless of volume.                                                                                                                                                         |
| `proportional_pool` | A fixed total budget is split among participants proportionally to their contribution (volume, revenue, or conversion count) over a period. See [Pool Distribution](/core-concepts/incentive-payouts/pool-distribution). |
| `leaderboard`       | Users are ranked by activity over a date range; each position or range of positions receives a predetermined reward. See [Leaderboard Rewards](/core-concepts/incentive-payouts/leaderboard-rewards).                    |

Example prompts:

> "Switch \[incentive name] from variable to fixed rewards, keep 15 points for end users"

> "Change \[incentive name] to variable at 6 points per USD for referrers"

> "Set up a pool distribution for \[incentive name] with a 10,000 point budget split by volume"

> "Configure \[incentive name] as a leaderboard competition — 1st place gets 1,000 points, 2nd gets 500, 3rd–10th get 100 each"

{% hint style="info" %}
When switching from `variable` to `fixed`, the `base_currency` field is automatically removed — it only applies to variable rewards. Pool and leaderboard strategies have additional required fields (budget, date range, rank configuration) that the MCP will prompt you for.
{% endhint %}

## 6. Publish to production

After editing, **draft changes are not live until published**. Go to the app and publish:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**

All changes will take effect immediately after publishing.

{% hint style="success" %}
You can review all pending draft changes in the Incentives section before publishing — useful to double-check a bulk update before it goes live.
{% endhint %}


# Incentive Analytics

See how each incentive program is performing directly from Cursor, Claude, or any agent — no dashboard, no manual exports.

Read-only tools, same credentials as the dashboard. No Project API key required.

## Ranking all incentives

List all incentives in a project ranked by the metric of your choice:

> "Which incentives are underperforming in the last 30 days?"

{% hint style="info" %}
Sorts by active users, volume, earnings, or revenue. When the date range allows it, results include week-over-week and month-over-month comparisons.
{% endhint %}

## Stats for a single incentive

Get a full snapshot of one program:

> "How is the Uniswap LP incentive performing this month?"

{% hint style="info" %}
Returns participant count, volume, revenue, earnings across all reward currencies, comparison against the project median, and WoW / MoM deltas.
{% endhint %}

## Performance over time

Pull the time series for a single incentive:

> "Show me day-by-day activity for the Morpho incentive over the last 30 days"

Supports `daily`, `weekly`, or `monthly` granularity. All three tools support: last 7 / 30 / 90 days, month-to-date, quarter-to-date, all-time, or a custom date range.

{% hint style="warning" %}
`get_incentive_history` requires a bounded date range and does not support all-time. If you use all-time on stats or breakdown, WoW / MoM comparisons will be empty — that means no prior period to compare against, not zero change.
{% endhint %}


# Managing Triggers

The Fuul MCP lets you create, edit, and delete triggers directly from your AI assistant — without touching the dashboard.

| Section                                                                             | Description                                                |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| [Editing Triggers](/fuul-mcp-server/managing-triggers/editing-triggers)             | Update trigger name, description, and editable expressions |
| [Add & Remove Triggers](/fuul-mcp-server/managing-triggers/add-and-remove-triggers) | Create new triggers or remove existing ones                |


# Add & Remove Triggers

The Fuul MCP lets you create new triggers or delete existing ones. Deleting a trigger requires first removing all incentives linked to it.

{% hint style="warning" %}
Changes made via MCP are saved as **drafts**. They do not take effect in production until you go to **app.fuul.xyz → Incentives → Publish**.
{% endhint %}

## Creating a trigger

### 1. Find your project

Tell the MCP your project name and it will look it up to get the project ID.

> "Find my project called Fuul"

### 2. Explore what you can track (optional)

If you're not sure what's supported, ask the MCP to list the available trigger types:

> "What types of triggers can I create?"

{% hint style="info" %}
Browse the full list of supported types in [Trigger Integrations](/core-concepts/trigger-integrations). Skip this step if you already know what you want to track.
{% endhint %}

### 3. Describe what you want to track

Tell the MCP what you want to track in natural language:

> "I want to incentivize the staking of my token"

> "I want to incentivize liquidity providers of the FUUL-USDC pool on Uniswap"

> "I want to track a custom off-chain event called 'User Signup'"

### 4. Answer the MCP's follow-up questions

The MCP will ask for any missing details depending on the trigger type:

| Field                    | Description                                         | Examples                                     |
| ------------------------ | --------------------------------------------------- | -------------------------------------------- |
| Token / contract address | The smart contract address of the token or protocol | `0x6b175474e89094c44da98b954eedeac495271d0f` |
| Chain                    | The blockchain network                              | Ethereum, Base, Arbitrum, Solana             |
| Event name               | For custom on-chain or off-chain events             | `Swap`, `Deposit`, `user_signup`             |

### 5. Publish to production

After creating, **draft changes are not live until published**:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**

***

## Deleting a trigger

### 1. Delete the incentives linked to that trigger

> "Delete all incentives that use the \[trigger name] trigger"

### 3. Publish from the dashboard

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**

{% hint style="warning" %}
Before deleting the trigger, you must publish so the platform removes the reference between the published metadata and the trigger. If you skip this step, `delete_trigger` will return a "Trigger is used in conversions" error.
{% endhint %}

### 4. Delete the trigger

Once published:

> "Delete the trigger called 'Aave Depositors'"

### 5. Publish to production

After deleting, go to the app and publish to apply the changes:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**


# Editing Triggers

A **trigger** defines the on-chain or off-chain action that qualifies a user for a reward — for example, a token swap, a liquidity deposit, or a custom off-chain event. Each trigger is associated with a contract (for on-chain events) and configured with expressions that determine how volume, revenue, and currency are extracted from the event.

{% hint style="info" %}
This guide covers editing existing triggers. To create new triggers or delete existing ones, see [Creating & Deleting Triggers](/fuul-mcp-server/managing-triggers/add-and-remove-triggers).
{% endhint %}

{% hint style="warning" %}
Changes made via MCP are saved as **drafts**. They do not take effect in production until you go to **app.fuul.xyz → Incentives → Publish**.
{% endhint %}

## 1. Find your project and triggers

> "Find my project called Fuul and list our current configured triggers"

## 2. Edit trigger name and description

You can update the display name and description of any trigger:

> "Rename the \[trigger name] trigger to \[new name]"

> "Update the description of \[trigger name] to \[new description]"

| Field         | What it controls                                       |
| ------------- | ------------------------------------------------------ |
| `name`        | Display name shown in the dashboard and incentives hub |
| `description` | Human-readable description of what the trigger tracks  |

## 3. What you cannot change via MCP

### Contract address

The `token_address` and `chain_id` of an existing trigger **cannot be modified via MCP** (or from the dashboard). If you need to point a trigger to a different contract, you must delete the existing trigger and create a new one with the correct address. See [Creating & Deleting Triggers](/fuul-mcp-server/managing-triggers/add-and-remove-triggers) for the full replace-trigger flow.

## 4. Publish to production

After editing, **draft changes are not live until published**:

1. Open [app.fuul.xyz](https://app.fuul.xyz)
2. Navigate to your project → **Incentives**
3. Click **Publish**

All trigger changes will take effect immediately after publishing.


# Managing Referrals

The Fuul MCP lets you read and manage referral attributions directly from your AI assistant — without going through the dashboard.

{% hint style="warning" %}
All referral management tools require a **project API key**, not the dashboard login. Pass it via `project_api_key` on each call, or set the `FUUL_MCP_PROJECT_API_KEY` environment variable.
{% endhint %}

## Use cases

### Master affiliate structures

Some programs have affiliate leaders who onboard other affiliates through direct deals — not through referral codes. For example, a regional leader might bring ten affiliates to your program verbally or via an offline agreement. In these cases, you need to manually assign those affiliates under the correct referrer to reflect the structure you negotiated with them.

The MCP handles all of this: checking current attributions, assigning a referrer directly, moving users between codes, and removing attributions entirely.

***

## 1. Check who referred a user

Before making changes, look up the current referral attribution for any user:

> "Who referred user 0xABC...?"

{% hint style="info" %}
Returns the referrer's identifier, referral code, and rebate rate if any. Returns null referrer fields if the user has no referrer assigned.
{% endhint %}

***

## 2. Assign a referrer directly (admin override)

Use this when a referral relationship was agreed upon outside the platform — no referral code was used, but you need to reflect the attribution in Fuul.

> "Set the referrer of user 0xABC... to 0xXYZ..."

{% hint style="info" %}
Assigns the referrer regardless of whether the user already has one. Does not affect referral code usage counters.
{% endhint %}

***

## 3. Redeem a referral code for a user

Use this to redeem a referral code on behalf of a user. The referrer is the code owner — no need to pass a referrer identifier:

> "Assign referral code PROMO2024 to user 0xABC..."

{% hint style="warning" %}
This only works if the user has **no existing referrer**. If they already have one, use `swap_user_referral_code` instead.
{% endhint %}

***

## 4. Move a user between referral codes

Use this to reassign a user from one referral code to another — for example, when a user entered through the wrong code and needs to be moved to the correct one:

> "Move user 0xABC... from referral code OLD to referral code NEW"

{% hint style="warning" %}
This runs as two separate steps. If the second step fails after the first one completes, the user will be left without a referrer. The MCP will flag this in the response — if that happens, complete the assignment using the direct assign or redeem code tools.
{% endhint %}

***

## 5. Remove a user from a referral code

Use this to fully unlink a user from a referral code — deletes the attribution and decrements the code's usage count:

> "Remove user 0xABC... from referral code PROMO2024"

{% hint style="info" %}
If the user was already removed, the tool returns `already_removed: true` instead of an error.
{% endhint %}


# Managing Affiliates

The Fuul MCP lets you query affiliate performance data and manage affiliate configurations directly from your AI assistant.

| Section                                                                  | Description                                                                              |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| [Affiliate Management](/fuul-mcp-server/affiliates/affiliate-management) | Register, update, and configure affiliates, audiences, and tiers                         |
| [Affiliate Analytics](/fuul-mcp-server/affiliates/affiliate-analytics)   | Query program overview, breakdowns by tier/region/status, and individual affiliate stats |


# Affiliate Management

Register, update, and configure affiliates, audiences, and tiers directly from your AI assistant.

## 1. Get your API key

1. Go to [app.fuul.xyz](https://app.fuul.xyz) and log in
2. Go to **Settings → API Keys → New API Key**
3. Select the **`service_role`** scope
4. Give it any name and click **Create**

## 2. Add the key to your environment

```
FUUL_MCP_PROJECT_API_KEY=your_key_here
```

Then tell Claude where to find it:

> "My Fuul project API key is set as FUUL\_MCP\_PROJECT\_API\_KEY in my environment — use that for managing affiliates"

{% hint style="warning" %}
Never pass the key directly in the chat — set it as an environment variable.
{% endhint %}

## 3. Look up an affiliate

> "Get the profile for CryptoKing"

{% hint style="info" %}
Returns everything the platform knows about that affiliate: their wallet address, current status (Active, Paused, etc.), region, display name (alias), which tier they belong to, which audiences they're in, and any active tier protections.
{% endhint %}

## 4. Register a new affiliate

> "Add CryptoKing (0x1234...abcd) as an affiliate on this project"

Optional fields: `alias`, `region`, `status`, `note`, `audiences`, `tier_protection`.

{% hint style="info" %}
The MCP runs a `dry_run` first and shows you the affiliate profile it will create before confirming.
{% endhint %}

## 5. Update an affiliate

> "Set the alias for 0x1234...abcd to CryptoKing"

> "Set CryptoKing's status to inactive"

> "Assign CryptoKing to the VIP audience"

{% hint style="info" %}
You can update any combination of: `alias`, `region`, `status`, `note`, `audiences`, `tier_protection` (set to `null` to clear it).
{% endhint %}

**Tier protection** — locks the affiliate to a tier for a number of days, preventing automatic downgrades:

> "Protect CryptoKing on the influencers tier for 30 days"

{% hint style="info" %}
Requires `tier_id` and `protection_days` (1–365). Optionally pass `expires_at` to set a fixed expiry date instead.
{% endhint %}

**Tier approval** — manually approve an affiliate for one or more tiers:

> "Approve CryptoKing for the influencers\_v2 tier"

Requires the tier name or ID and the team member approving.

## 6. Update an audience definition

Audiences are user segments with optional conditions. You can update the name and conditions of an existing audience:

> "Rename the 'Demo Gold' audience to 'Diamond Partners'"

> "Update the 'Demo Bronze' audience conditions to match users holding the Loyalty NFT"

{% hint style="info" %}
If updating conditions, specify whether users need to match any or all of them.
{% endhint %}

## 7. Update a tier

Tiers define differentiated payout rates for specific groups of affiliates. Each tier is associated with an audience — a list of wallets that qualifies for that tier's rates. For example, you might have a static audience of 40 influencer wallets linked to a tier that pays a higher commission rate than the default.

You can update a tier's name, description, rank, or the audience it points to:

> "Rename the 'influencers' tier to 'Gold'"

> "Set the rank of the 'Gold' tier to 1"

> "Point the 'Gold' tier to the 'Top Influencers' audience"

{% hint style="info" %}
All write operations (`create`, `update`) follow the `dry_run` → `confirmed` flow — the MCP will show you a preview before making any changes.
{% endhint %}


# Affiliate Analytics

Ask your AI assistant anything about your affiliate program — top performers, volume by region, earnings by tier — without opening the dashboard or writing a single query. Just ask and get answers instantly.

No project API key needed, just log in to your Fuul account via the MCP.

## Program overview

Get aggregated performance metrics for all affiliates in a project:

> "How are our affiliates performing this month?"

{% hint style="info" %}
Supports date ranges: 7d, 30d, 90d, MTD, QTD, all, or a custom date range. You can also filter by statuses, regions, audiences, or tiers.
{% endhint %}

## Breakdown by group

Slice affiliate performance by a specific dimension:

> "Break down affiliate performance by tier"

> "Which regions are driving the most referral volume?"

| `groupBy` value | Description                |
| --------------- | -------------------------- |
| `audience`      | Group by audience segment  |
| `tier`          | Group by affiliate tier    |
| `region`        | Group by geographic region |
| `status`        | Group by affiliate status  |

{% hint style="info" %}
You can sort results by total referral volume, revenue from referrals, earnings, or points paid.
{% endhint %}

## Individual affiliate stats

Look up the performance of a specific affiliate:

> "Show me CryptoKing's stats"

{% hint style="info" %}
Returns the affiliate's performance (referral volume, revenue, earnings, referred users) alongside their current tier, status, region, and referral codes.
{% endhint %}


# Payout Approvals

When an incentive has **Disable automatic payouts** enabled in its advanced settings, rewards don't go out automatically — they sit in a queue visible from the app under **Payouts → Pending Approval**. The Fuul MCP lets you manage that queue directly from your AI assistant.

## 1. See what's pending

> "What payouts are waiting for approval?"

{% hint style="info" %}
Returns each pending payout with the user's wallet, the conversion it came from, the reward amount and currency, and the date it was generated.
{% endhint %}

## 2. Approve payouts

> "Approve all pending payouts"

> "Approve CryptoKing's payout"

> "Approve all payouts from last week"

> "Approve all pending payouts for the 'Follow on X' conversion"

{% hint style="info" %}
Approve by specific payout IDs, by date range, or filtered by affiliate or user wallet address.
{% endhint %}

## 3. Reject payouts

> "Reject CryptoKing's payout"

> "Reject all pending payouts from this month"

> "Reject all pending payouts for the 'Follow on X' conversion"

{% hint style="info" %}
Reject by specific payout IDs, by date range, or filtered by affiliate or user wallet address.
{% endhint %}

## 4. Review payout history

> "How much have we paid out this month?"

> "How much did we pay in March?"

> "How many points did CryptoKing earn as an end user vs as a referrer?"

{% hint style="info" %}
Only token payouts. Filter by status: `open`, `unclaimed`, or `claimed`.
{% endhint %}


# Sending Events

The Fuul MCP lets you send custom conversion events directly from your AI assistant — the same events you'd send via the API, but without writing code. This is useful for testing your trigger setup, importing historical conversions, or sending events in bulk from a spreadsheet or CSV.

For full details on event payloads and the `args` format, see [Sending Custom Events](/developer-guide/sending-custom-events-through-the-api).

## 1. Get your API key

Before sending events, you need a project API key with the right permission:

1. Go to [app.fuul.xyz](https://app.fuul.xyz) and log in
2. Go to **Settings → API Keys → New API Key**
3. Select the **`send:trigger_event`** scope
4. Give it any name and click **Create**

## 2. Add the key to your environment

Add the key to your `.env` file so the MCP can access your project:

```
FUUL_MCP_PROJECT_API_KEY=your_key_here
```

{% hint style="warning" %}
Never pass the key directly in the chat — set it as an environment variable.
{% endhint %}

Then tell Claude where to find it:

> "My Fuul project API key is set as FUUL\_MCP\_PROJECT\_API\_KEY in my environment — use that for sending events"

From this point on, Claude will use it automatically on every event call.

## 3. Send a single event

> "Send a 'trade' event for user 0xABC... with a value of 100 USDC on Ethereum"

Required fields:

| Field                  | Description                                                       |
| ---------------------- | ----------------------------------------------------------------- |
| `name`                 | The trigger's event name — must match exactly (case-sensitive)    |
| `user_identifier`      | The user's wallet address or email                                |
| `user_identifier_type` | `evm_address`, `solana_address`, `stellar_address`, `email`, etc. |
| `dedup_id`             | A unique ID for this event — used to prevent duplicates           |

{% hint style="info" %}
The `dedup_id` is critical: if you send the same event twice with the same `dedup_id`, the second call returns a 409 and the event is not recorded again. Always use a unique identifier (e.g. a transaction hash or a UUID tied to the source record).
{% endhint %}

Rate limit: **100 requests/minute**.

## 4. Send events in bulk

> "Send these 50 events from this CSV"

The MCP can send up to **100 events per batch call**. Duplicate `dedup_id` values within the batch are silently skipped — the response tells you how many events were actually ingested.

{% hint style="info" %}
Rate limit: **10 requests/minute**.
{% endhint %}

## 5. Check if an event was processed

After sending, you can verify that an event was received and processed:

> "Did user 0xABC... trigger the 'trade' event?"

{% hint style="info" %}
Use this before resending a single event to avoid a 409 duplicate error — if it already exists, don't send it again.
{% endhint %}

## 6. Trace the full pipeline

To see exactly what happened downstream — trigger execution, attribution, payout, and balance movement — provide the `dedup_id` and event name:

> "Show me the full pipeline for the last event I sent"

This returns:

| Field                | Description                                                             |
| -------------------- | ----------------------------------------------------------------------- |
| `trigger_executions` | Whether the trigger fired and its status (`Accepted`, `Rejected`, etc.) |
| `attributions`       | Which conversion was matched and whether it was confirmed               |
| `payouts`            | Amounts and currencies credited                                         |
| `movements`          | The resulting balance changes                                           |

{% hint style="info" %}
Use this when an event was received but rewards didn't appear — it shows exactly where in the pipeline things stopped.
{% endhint %}


# Fuul Incentives Manager

The **Fuul Incentives Manager** is the web app for creating, managing, and optimizing incentive programs, all without requiring technical expertise. Designed with simplicity and flexibility in mind, it empowers projects to define and execute incentive strategies entirely through a no-code platform. Whether you're incentivizing liquidity, staking, referrals, or custom on-chain events, the Fuul Incentives Manager provides the tools you need to succeed.

**Key Features and Capabilities**

1. **Triggers**\
   Define the specific actions that trigger rewards in your program. Whether on-chain or off-chain, you can configure events like staking, holding tokens, providing liquidity, or trading.
2. **Incentives**\
   Set up reward structures using fixed payouts, variable payouts, pool distribution, or leaderboard incentives — paying in tokens or points. Tiers and multipliers let you reward specific user groups at different rates.
3. **Add Budget** (token rewards only)\
   Easily allocate funds to your token reward programs by deploying and managing smart contracts directly through the app.
4. **Build Incentives Hub**\
   Launch a no-code hosted hub for affiliates and participants, or build a fully custom experience using the SDK to embed leaderboards, reward displays, and claiming flows directly in your app.
5. **Insights and Analytics**\
   Monitor program performance with built-in analytics. Build custom dashboards with line charts, bar charts, and KPI cards to track the metrics that matter most to your program — all in one place.

{% hint style="info" %}
To embed leaderboards, user rewards, and claiming flows directly in your app, see [Build Your Incentives Hub](/developer-guide/build-your-incentives-hub).
{% endhint %}


# Badges

Badges are visual labels assigned to users to recognize achievements, status, or membership. They appear on dashboards and leaderboards, giving users public recognition.

## How they work

A badge is an **audience with a display layer** — it has a name, image, and description on top of a standard audience segment. Any user who belongs to that audience receives the badge.

This means badges inherit all audience capabilities:

* **Static badges** — manually curated lists of users (e.g., early supporters, approved affiliates)
* **Dynamic badges** — automatically assigned based on conditions like volume, conversions, token holdings, or points balance

## Use cases

| Badge             | Audience condition         | Applies to |
| ----------------- | -------------------------- | ---------- |
| 🥇 Gold Partner   | Referred volume > $100K    | Affiliates |
| 🐋 Whale          | Token balance > 100K       | Any user   |
| 🏆 Top Trader     | Trading volume > $1M       | Traders    |
| ⭐ Early Supporter | Static list                | Any user   |
| 🔥 Power User     | Number of conversions > 50 | Any user   |

## Configuration

Badges are configured in the **Audiences** section of the Fuul app. When creating or editing an audience, enable the badge option and set:

* **Name** — displayed to the user
* **Image** — icon or logo shown on dashboards and leaderboards
* **Description** — short explanation of what the badge represents

{% hint style="info" %}
Badges are a display feature on top of audiences. To understand how audiences and segmentation work, see [Tiers & Multipliers](/core-concepts/tiers-and-multipliers) and [Managing Audiences](/developer-guide/managing-audiences).
{% endhint %}


# Creating Triggers

A **trigger** defines the action you want to reward — a swap, a deposit, a token balance, a social action, or any custom event. When you assign a trigger to an incentive, that action becomes something that fires a payout automatically every time it happens.

## Pre-built triggers vs. custom events

There are two ways to bring actions into Fuul:

**Pre-built trigger integrations** — Fuul natively tracks onchain activity for supported protocols (AMMs, lending, staking, token holders, etc.). You pick the integration, configure it, and Fuul handles detection automatically.

**Inserting events** — If the action happens in your own backend (a sign-up, a purchase, a social action), you send it to Fuul via API or CSV upload. Use this when the action isn't onchain or isn't covered by a native integration.

Use pre-built integrations when the protocol is supported. Use event insertion when you own the data source.

{% columns %}
{% column %}
{% content-ref url="/pages/6ccgx8knXYct2hmVh3Fo" %}
[Understanding Trigger Types](/incentives-manager/creating-triggers-and-conversions/understanding-triggers-types)
{% endcontent-ref %}
{% endcolumn %}

{% column %}
{% content-ref url="/pages/RtGQ2BFsaPFctRCbLP2r" %}
[Creating an Event with CSV file](/incentives-manager/creating-triggers-and-conversions/creating-an-event-with-csv-file)
{% endcontent-ref %}
{% endcolumn %}
{% endcolumns %}


# Understanding Trigger Types

In Fuul's platform, you can set up different types of triggers for your rewards based on your needs

Triggers are a key component of Fuul's incentive programs, allowing you to define specific events that initiate rewards or actions. In this guide, you'll learn which kind of triggers you can use.

### **What Are Trigger Types?**

Triggers are events or conditions that, when met, initiate a specific action in your incentives program.

### Finding the trigger you need

When you open the create-trigger page (or step 1 of onboarding), Fuul shows two options by default:

| Trigger            | What it is                                                       |
| ------------------ | ---------------------------------------------------------------- |
| **API**            | Any action you report from your own backend through the Fuul API |
| **Stripe Payment** | A one-off payment on a linked Stripe Connect account             |

Everything else sits behind the **Crypto & quest triggers** switch. Turn it on to reveal the full catalogue, the category tabs, and the search box.

{% hint style="info" %}
New trigger types are added behind the switch, so turn it on whenever you are looking for a protocol integration and don't see it.
{% endhint %}

{% hint style="warning" %}
**"API" is the new display name for what the platform calls Custom Offchain.** Only the label changed — the trigger type stored on your program, and anything you have already configured, is unaffected. Older docs and older screenshots may still say "Custom Offchain".
{% endhint %}

<figure><img src="/files/fU4UCYOvXx9qrkuUVDzy" alt=""><figcaption></figcaption></figure>

### **Types of Triggers in Fuul**

#### Liquidity Triggers

* **CLAMM LPs** — snapshots of concentrated liquidity positions on Uniswap V3 and V3-style DEXs → [CLAMM LPs](/core-concepts/trigger-integrations/clamm-lps-e.g.-uniswap-v3)
* **Constant LPs** — LP token balances on Uniswap V2 and V2-style DEXs → [Constant LPs](/core-concepts/trigger-integrations/constant-lps-e.g.-uniswap-v2)

#### Lending & Staking Triggers

* **Lending & Borrowing** — supply and borrow positions on Compound V3, Morpho, Morpho Vaults, Euler Vaults, and Euler V2 Looping → [Lending & Borrowing](/core-concepts/trigger-integrations/lending-and-borrowing)
* **Staking** — staked balances tracked via subgraphs or receipt tokens → [Staking](/core-concepts/trigger-integrations/staking)
* **Yield** — Pendle and Spectra YT and LP positions → [Yield (Pendle)](/core-concepts/trigger-integrations/yield-pendle)

#### Trading Triggers

* **Spot DEXs** — Sushiswap, Nado, Ambient → [Trading](/core-concepts/trigger-integrations/trading)
* **HyperLiquid Builder Codes** — perpetual trading volume, fees, PnL, and maker/taker split → [HyperLiquid](/core-concepts/trigger-integrations/hyperliquid)
* **Orderly Builder Trades** → [Orderly Network](/core-concepts/trigger-integrations/orderly)
* **Pacifica Builder Trades** → [Pacifica](/core-concepts/trigger-integrations/pacifica)
* **Prediction markets** — per-market trade volume on Polymarket → [Polymarket](/core-concepts/trigger-integrations/prediction-markets-polymarket)

#### Holding Triggers

* **Token Holders** — snapshots of balances for an ERC-20, LP token, NFT, or SPL token. Sources can be a token contract, a subgraph, or a Dune query → [Token Holders](/core-concepts/trigger-integrations/token-holders)

#### Social & Quest Triggers

These triggers reward users who engage with your community.

* **X (Twitter)** — follows, posts, likes, and reposts → [𝕏 (Twitter)](/core-concepts/trigger-integrations/quests-and-social/x-twitter)
* **Discord** — server membership, channel joins, and role assignment → [Discord](/core-concepts/trigger-integrations/quests-and-social/discord)
* **Galxe & Zealy** — campaign participation and quest completion → [Galxe & Zealy](/core-concepts/trigger-integrations/quests-and-social/galxe-zealy)
* **Github Activity** — contributions on a repository

#### Custom Triggers

* **Custom Onchain** — any smart contract event or function call, with optional parameter filters → [Custom Onchain Events](/core-concepts/trigger-integrations/custom-onchain-events)
* **API** (formerly Custom Offchain) — actions reported from your own backend, a CSV upload, or Zapier → [Custom Offchain Events](/core-concepts/trigger-integrations/custom-offchain-events)
* **Subgraph Balances** — use this when the value you want to reward, such as cumulative deposits, is not available at the transaction level
* **Stripe Payment** — a completed payment on a linked Stripe Connect account, delivered by webhook with no schedule

{% hint style="info" %}
For the full catalogue with configuration details per protocol, see [Trigger Integrations](/core-concepts/trigger-integrations).
{% endhint %}

{% embed url="<https://drive.google.com/file/d/18Htr-UNOZEjD3yUU-4YRsHZpLcke-5MQ/view?usp=sharing>" %}


# Creating an Event with CSV file

Learn how to add conversion events through a CSV file

This guide walks you through rewarding users for offchain actions by uploading a CSV file.

### Steps

1. Create a new **Offchain Trigger** — set the name, description, and event name.
2. Create a **Conversion** — select the trigger you just created.
3. Create an **Incentive** — select Points, choose who to pay (Referrers, End Users, or both), and set up the reward structure (Fixed or Variable). If variable, configure the payout as 1 POINT per POINT.
4. Go to **Activity > Events > Import from CSV** and upload your file. Make sure your CSV includes:
   * `event_name` — must match the event name used when creating the offchain trigger
   * `value_amount` — the amount for each row
   * `value_currency` — set to `POINT` for all rows

{% embed url="<https://drive.google.com/file/d/1a3cQF__FRagw6DrlOX3onOb3nHYRYaDo/view?usp=sharing>" %}


# Incentives Hub

The Incentives Hub is the frontend your affiliates and users interact with — it shows rewards, leaderboards, referral links, and the claiming interface. Fuul gives you two ways to deploy it:

|                       | [No-Code (Fuul Hosted)](/incentives-manager/incentives-hub/building-no-code-landing-pages) | [Custom Build (SDK)](/developer-guide/build-your-incentives-hub) |
| --------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Who hosts it**      | Fuul                                                                                       | You                                                              |
| **Dev work required** | None                                                                                       | Yes                                                              |
| **Branding control**  | Configurable via Hub Editor                                                                | Full control                                                     |
| **Time to launch**    | Minutes                                                                                    | Days to weeks                                                    |
| **User journey**      | Users interact with the full incentive program on the Fuul-hosted page                     | Fully embedded                                                   |
| **Best for**          | Getting to market fast                                                                     | Full white-label experience                                      |


# Fuul Hub

The Fuul Hub is a no-code hosted page that handles wallet connection, referrals, leaderboards, and reward tracking out of the box. All content is editable from the Hub Editor.

## Prerequisites

{% hint style="info" %}
This guide assumes you've already created a project, set up triggers, and configured your incentive program in the Fuul dashboard. See the [Quickstart](/getting-started/quickstart) if you haven't.
{% endhint %}

## Setup

### 1. Select the hosted solution

Go to **Overview** in the Fuul dashboard. In the **Your program URLs** section you'll see your Fuul Hub link and a **Go to customize page** button.

### 2. Customize your hub

Click **Go to customize page** to open the Hub Editor. The editor is organized into sections:

| Section             | What you can configure                                       |
| ------------------- | ------------------------------------------------------------ |
| **Theme**           | Colors and fonts                                             |
| **Hero**            | Title, subtitle, cover image, profile image                  |
| **Program rewards** | Reward cards per trigger — title, description, and CTA       |
| **Leaderboard**     | Show/hide leaderboard, leaderboard type, custom column names |
| **Other sections**  | Footer and drawer                                            |
| **Metadata**        | Page title tag, meta description, social share image         |

{% hint style="info" %}
The editor defaults to English. Use the language selector in the top-left to add additional languages (Spanish, Portuguese) and enter translated copy for each.
{% endhint %}

### 3. Share your hub URL

Your hosted hub URL is available in the dashboard. Share it with affiliates, who can generate their own tracking links from the hub.

## What users see

| Section               | What it shows                                                                                     |
| --------------------- | ------------------------------------------------------------------------------------------------- |
| **Referrals**         | Wallet connection and referral onboarding — users connect, sign, and get attributed to a referrer |
| **Program rewards**   | Cards for each active trigger, showing what users earn per action                                 |
| **Leaderboard**       | Ranking of top participants by earnings                                                           |
| **My earnings**       | Each user's personal reward history and claimable balance                                         |
| **Participant count** | Total number of participants in the program                                                       |

{% hint style="info" %}
Need the hub fully embedded in your app? Use the [SDK integration](/developer-guide/build-your-incentives-hub) instead.
{% endhint %}

{% embed url="<https://drive.google.com/file/d/1NRqlNe_eikybAGmDwAp5nXtnxF1vmIE3/view?usp=sharing>" %}

## Cookie consent

The Fuul Hub includes a built-in cookie consent banner. SDK initialization is gated behind user consent.

| Status       | Behavior                                                                                |
| ------------ | --------------------------------------------------------------------------------------- |
| **Accepted** | Consent state persisted in a cookie and `localStorage` (180-day expiry, `SameSite=Lax`) |
| **Declined** | `fuul.tracking_id` and all `fuul.sent_*` keys cleared from `localStorage`               |

Users can change their choice at any time via **Cookie preferences** in the footer.

The banner copy is available in English, Spanish (es-AR), and Portuguese (pt-BR) — it follows the language selected in the Hub Editor.


# SDK Setup & Installation

The Fuul Web SDK gives you access to all the functions needed to integrate Fuul into your website — tracking referrals, displaying leaderboards, managing affiliate codes, claiming rewards, and more.

{% hint style="info" %}
This guide assumes you have already created a Fuul account and generated an API key. See [API Key Management](/developer-guide/api-key-management) for details.
{% endhint %}

## 1. Install the SDK

```bash
npm install @fuul/sdk
```

```bash
# or using yarn
yarn add @fuul/sdk
```

## 2. Initialize

Before using any SDK method, initialize it with your API key at the root of your app:

```typescript
import { Fuul } from '@fuul/sdk';

Fuul.init({ apiKey: 'your-api-key' });
```

{% hint style="warning" %}
When using **Next.js App Router**, initialize the SDK in a client-rendered component (`'use client'`).
{% endhint %}

## Which API key should I use?

The API key you pass to `Fuul.init()` depends on what your app needs to do:

| Use case                                          | Key type              |
| ------------------------------------------------- | --------------------- |
| Display leaderboards, rewards, or user data       | `read-only`           |
| Track pageviews and wallet connections (frontend) | `send:tracking_event` |
| Send custom trigger events (backend only)         | `send:trigger_event`  |
| Manage audiences programmatically (backend only)  | `service_role`        |

{% hint style="info" %}
Most frontend integrations use the `send:tracking_event` key, which covers both reading data and sending tracking events.
{% endhint %}

## What's next?

After initializing the SDK, the typical integration steps are:

| Step                                         | Page                                                                          |
| -------------------------------------------- | ----------------------------------------------------------------------------- |
| Track referral visits and wallet connections | [Tracking Referrals](/developer-guide/tracking-referrals-in-your-app)         |
| Let users create affiliate links or codes    | [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes) |
| Display leaderboard rankings                 | [Leaderboard Data](/developer-guide/getting-leaderboard-data)                 |
| Show individual user rewards                 | [Individual Rewards](/developer-guide/getting-individual-rewards)             |
| Build a claiming page for onchain rewards    | [Claiming Onchain Rewards](/developer-guide/claiming-onchain-rewards)         |

{% hint style="info" %}
For a high-level overview of which features require SDK integration, see [Build Your Incentives Hub](/developer-guide/build-your-incentives-hub).
{% endhint %}


# API Key Management

Fuul provides five types of API keys, each designed for specific use cases. Using the right key type ensures your integration is secure and your data stays protected.

## Key types

| Key                         | Environment  | Permissions                       | Use for                                                                      |
| --------------------------- | ------------ | --------------------------------- | ---------------------------------------------------------------------------- |
| **Read-Only**               | Frontend     | Read data only                    | Displaying leaderboards, rewards, and conversion info                        |
| **send:tracking\_event**    | Frontend     | Read + send tracking events       | Tracking `pageview`, `connect_wallet`, and custom frontend `sendEvent` calls |
| **send:trigger\_event**     | Backend only | Read + send trigger events        | Sending custom offchain trigger events (e.g., social actions)                |
| **terms\_conditions:write** | Backend only | Record and read terms acceptances | Recording that a wallet accepted your terms document                         |
| **service\_role**           | Backend only | Full access                       | Creating/updating audiences programmatically                                 |

{% hint style="info" %}
`terms_conditions:write` is a narrow scope: it grants the two endpoints in [Terms & Conditions](/developer-guide/terms-conditions) and nothing else. Mint one when an external service or a teammate needs to record acceptances without holding a `service_role` key.
{% endhint %}

{% hint style="warning" %}
**Never expose `send:trigger_event` or `service_role` keys in the frontend.** A malicious user could send fake events or modify your program data.
{% endhint %}

## Creating an API key

1. Go to **Settings > API keys** in the Fuul dashboard and click **New API Key**
2. Select the key type you need
3. Name it and click **Create**
4. Copy the key from the modal — it won't be shown again

## Which key should I use?

Most frontend integrations only need a single `send:tracking_event` key. This covers both reading data (leaderboards, rewards) and sending the tracking events required for referral attribution.

| Scenario                                               | Recommended key          |
| ------------------------------------------------------ | ------------------------ |
| White-label hub with referral tracking                 | `send:tracking_event`    |
| Read-only dashboard or leaderboard widget              | `read-only`              |
| Backend sending custom events (Discord, Twitter, etc.) | `send:trigger_event`     |
| Backend recording terms acceptances only               | `terms_conditions:write` |
| Backend managing audiences via API                     | `service_role`           |

{% hint style="info" %}
For more details on audience management, see [Managing Audiences](/developer-guide/managing-audiences).
{% endhint %}


# Build Your Incentives Hub

This guide helps you plan what to integrate based on the features you want in your incentives hub. Each section points to the corresponding SDK documentation for implementation details.

{% hint style="info" %}
**Don't want to build anything?** Use the [No-Code Setup](broken://pages/3ZluGciHtTpNp3sKd98C) to launch a Fuul-hosted hub with zero development.
{% endhint %}

## What do you need?

### Referral tracking

If your program includes referrals, you need to track when users arrive via referral links and identify themselves.

| What to integrate                                                             | Purpose                                                                |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [SDK Setup](/developer-guide/getting-started-with-fuul-web-sdk)               | Install and initialize the Fuul SDK                                    |
| [Tracking Referrals](/developer-guide/tracking-referrals-in-your-app)         | Send pageview and wallet connection events to track the referral chain |
| [Affiliate Links & Codes](/developer-guide/creating-affiliate-links-or-codes) | Let affiliates generate tracking links and custom codes                |
| [Referral Codes](/developer-guide/referral-codes)                             | Let users accept referral codes directly                               |

### Leaderboards

If you want to display rankings on your site:

| What to integrate                                                             | Purpose                                        |
| ----------------------------------------------------------------------------- | ---------------------------------------------- |
| [Leaderboard Data — Tokens](/developer-guide/getting-leaderboard-data/tokens) | Show top earners by onchain token payouts      |
| [Leaderboard Data — Points](/developer-guide/getting-leaderboard-data/points) | Show top earners by points                     |
| [Leaderboard Data — Volume](/developer-guide/getting-leaderboard-data/volume) | Show top users by trading or conversion volume |

### User rewards

If you want users to see their individual earnings:

| What to integrate                                                                 | Purpose                                                |
| --------------------------------------------------------------------------------- | ------------------------------------------------------ |
| [Individual Rewards — Tokens](/developer-guide/getting-individual-rewards/tokens) | Show a user's onchain token earnings                   |
| [Individual Rewards — Points](/developer-guide/getting-individual-rewards/points) | Show a user's point balance                            |
| [Referred Metrics](/developer-guide/referred-metrics)                             | Show referral stats (volume, earnings, referred users) |

### Onchain claiming

If your program distributes token rewards, users need a way to claim them:

| What to integrate                                                     | Purpose                                                                            |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| [Claiming Onchain Rewards](/developer-guide/claiming-onchain-rewards) | Build a claim button that lets users redeem their token rewards via smart contract |

### Social verification

If your program includes social actions:

| What to integrate                                           | Purpose                                           |
| ----------------------------------------------------------- | ------------------------------------------------- |
| [Verifying X Follows](/developer-guide/verifying-x-follows) | Verify that a user has followed your account on X |

## Putting it together

A typical white-label hub combines several of these features. Here's what a full implementation looks like:

| Hub feature                   | SDK features needed                                          |
| ----------------------------- | ------------------------------------------------------------ |
| **Affiliate onboarding page** | SDK Setup + Tracking Referrals + Affiliate Links & Codes     |
| **User dashboard**            | Individual Rewards (Tokens and/or Points) + Referred Metrics |
| **Leaderboard page**          | Leaderboard Data (pick the relevant metric)                  |
| **Claim rewards page**        | Claiming Onchain Rewards                                     |
| **Full hub**                  | All of the above                                             |

{% hint style="success" %}
For a working example, check out the [Fuul SDK Next.js example](https://github.com/fuul-app/fuul-sdk-nextjs-example/tree/main) — a complete implementation with RainbowKit wallet connection.
{% endhint %}

## Explorer API

The Explorer API provides public listing endpoints for building discovery pages or aggregator integrations. These are the same endpoints that power the Fuul Explorer.

| Endpoint                                           | Description                                               | Reference                                                           |
| -------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------- |
| `GET /explorer/v1/listings/incentive-rewards`      | List incentive programs ranked by distributed payouts     | [View](https://fuul.readme.io/reference/getincentiverewardslisting) |
| `GET /explorer/v1/listings/referral-rewards`       | List referral programs ranked by distributed payouts      | [View](https://fuul.readme.io/reference/getreferralrewardslisting)  |
| `GET /explorer/v1/projects/{projectId}`            | Get project details (name, description, links, thumbnail) | [View](https://fuul.readme.io/reference/getprojectexplorerdata)     |
| `GET /explorer/v1/projects/{idOrSlug}/users-count` | Get total user count for a project                        | [View](https://fuul.readme.io/reference/getprojectuserscount)       |


# Tracking Referrals

For Fuul to attribute conversions to the right referrer, your app must send two tracking events: **`pageview`** and **`connect_wallet`** (via `identifyUser`).

{% hint style="info" %}
These events require the `send:tracking_event` API key. See [API Key Management](/developer-guide/api-key-management).
{% endhint %}

## How attribution works

```
User clicks referral link → pageview event → user connects wallet → identifyUser event
                                   ↓                                        ↓
                         Captures referrer info                Links wallet to tracking session
                                   ↓                                        ↓
                            Later, user converts → Fuul attributes conversion to the referrer
```

## 1. Send pageview

Call `sendPageview` on every page load. The SDK automatically captures the referrer's affiliate code from the URL (`?af=code`, or the legacy `?referrer=code`) and stores a tracking ID in localStorage.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.sendPageview();
```

{% hint style="info" %}
The SDK reads referral parameters from the URL automatically. You don't need to persist them when navigating between pages.
{% endhint %}

## 2. Identify the user

Call `identifyUser` every time a user connects a wallet or logs in — including when they switch wallets during a session. This sends a `connect_wallet` event that links their identity to the tracking session.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address', // evm_address | solana_address | xrpl_address | sui_address | stellar_address | email
  signature: '0xabc...',
  message: 'Sign to verify your identity',
});
```

### Smart contract wallets

For smart contract wallets (EIP-1271), add the `accountChainId` parameter:

```typescript
await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address',
  signature: '0xabc...',
  message: 'Sign to verify your identity',
  accountChainId: 1, // Chain ID where the smart contract wallet is deployed
});
```

Supported chains for smart contract wallets:

| Network    | Chain ID |
| ---------- | -------- |
| Ethereum   | 1        |
| Arbitrum   | 42161    |
| Optimism   | 10       |
| Base       | 8453     |
| Polygon    | 137      |
| zkSync Era | 324      |
| BNB Chain  | 56       |
| Avalanche  | 43114    |
| Mode       | 34443    |
| Abstract   | 2741     |
| Bob        | 60808    |
| Berachain  | 80094    |

### XRPL signatures

For XRPL wallets, include the `signaturePublicKey`:

```typescript
await Fuul.identifyUser({
  identifier: 'rAddress...',
  identifierType: 'xrpl_address',
  signature: '0xabc...',
  signaturePublicKey: '0x1234...',
  message: 'Sign to verify your identity',
});
```

### Stellar signatures

Stellar wallets identify with a `stellar_address` on **pubnet**. Proof of possession is a [SEP-53](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) message signature, not an EVM-style one, so it will not verify through viem or wagmi.

```typescript
await Fuul.identifyUser({
  identifier: 'GABC...',
  identifierType: 'stellar_address',
  signature: '...',           // SEP-53 message signature
  message: 'Sign to verify your identity',
});
```

**Address rules:**

| Prefix | Accepted | Notes                                                                     |
| ------ | -------- | ------------------------------------------------------------------------- |
| `G…`   | Yes      | Standard ed25519 public key StrKey                                        |
| `M…`   | No       | Muxed accounts are rejected                                               |
| `C…`   | No       | Contract addresses are rejected. See the note on `stellar_contract` below |
| `S…`   | No       | Secret keys are rejected, and must never be sent anywhere                 |

{% hint style="warning" %}
`@fuul/sdk` exports a seventh `UserIdentifierType` value, `StellarContract` (`'stellar_contract'`), which you must never pass as a user identifier. It exists for enum parity with the currency side of the API, where a `C…` StrKey names a Soroban token rather than a person. Passing it to an identify, attribution, or payout endpoint returns a **500**, not a validation error.

The six values in the lists on this page are the complete set of valid user identifier types. For denominating an event's value in a Stellar asset, see [Sending Custom Events](/developer-guide/sending-custom-events-through-the-api#stellar-assets).
{% endhint %}

{% hint style="warning" %}
Stellar addresses are **case-sensitive** and are never lowercased by Fuul. Unlike EVM addresses, `GABC…` and `gabc…` are not the same user. Pass the address exactly as the wallet returns it, or you will create a second, unmatched identity.
{% endhint %}

{% hint style="warning" %}
Stellar is supported for **identification and attribution only**. A Stellar address can accept referral codes, be attributed conversions, appear on leaderboards, and earn points. It cannot receive an onchain token payout: a payout targeting a `stellar_address` fails with `UnsupportedIdentifierType`.

For programs paying tokens, use points for Stellar users, or collect an EVM or Solana payout address separately.
{% endhint %}

On the [Fuul Hosted Hub](https://github.com/fuul-automation/gitbook-docs/tree/dev/incentives-hub/no-code.md), Stellar wallet connection (Freighter, Lobstr, and others) is handled for you. No extra integration work is needed.

## 3. Send frontend events (optional)

Beyond `pageview` and `connect_wallet`, the SDK can send arbitrary named events from the browser with `sendEvent`. Use it for frontend interactions you want recorded against the current tracking session — a button click, a completed form, a step in an onboarding flow.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.sendEvent('waitlist_joined', { plan: 'pro' });
```

The signature is positional: an event name, then an optional `args` object. The SDK attaches the tracking ID from the current session automatically, so the event lands on the same tracking session as the pageview that started it.

{% hint style="warning" %}
**`sendEvent` cannot trigger a conversion.** It runs with a `send:tracking_event` key, which is not allowed to create trigger events. Anything that should produce a reward has to be sent from your backend with a `send:trigger_event` key — see [Sending Custom Events](/developer-guide/sending-custom-events-through-the-api).

`sendEvent` is browser-only. It throws outside a browser context, like `sendPageview` and `identifyUser`.
{% endhint %}

`sendPageview` also takes an optional page name if you want to override the default (`document.location.pathname`):

```typescript
await Fuul.sendPageview('/product/123');
```

## Sending identifyUser from the backend

If you identify users server-side, send the `connect_wallet` event via the REST API. You'll need the `tracking_id` from the browser's `localStorage` (key: `fuul.tracking_id`) — it must match the one sent in the pageview event.

```python
import requests

url = "https://api.fuul.xyz/api/v1/events"

payload = {
    "metadata": {
        "tracking_id": "trackingId123"
    },
    "name": "connect_wallet",
    "user": {
        "identifier": "0x1234...",
        "identifier_type": "evm_address"  # evm_address | solana_address | xrpl_address | sui_address | stellar_address | email | uuid
    },
    "signature": "0xabc...",
    "signature_message": "Sign to verify your identity",
    "account_chain_id": 1  # Only for smart contract wallets
}

headers = {
    "content-type": "application/json",
    "authorization": "Bearer your-api-key"
}

response = requests.post(url, json=payload, headers=headers)
```

**cURL:**

```bash
curl -X POST https://api.fuul.xyz/api/v1/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "metadata": { "tracking_id": "trackingId123" },
    "name": "connect_wallet",
    "user": {
      "identifier": "0x1234...",
      "identifier_type": "evm_address"
    },
    "signature": "0xabc...",
    "signature_message": "Sign to verify your identity"
  }'
```

{% hint style="info" %}
Valid `identifier_type` values: `evm_address` | `solana_address` | `xrpl_address` | `sui_address` | `stellar_address` | `email` | `uuid`
{% endhint %}

## Signature types

Fuul supports two signature verification methods:

| Type                 | Verification                                                                 | Use case                |
| -------------------- | ---------------------------------------------------------------------------- | ----------------------- |
| Regular message      | [Viem verifyMessage](https://viem.sh/docs/actions/public/verifyMessage.html) | Standard EOA wallets    |
| Typed data (EIP-712) | [Viem verifyTypedData](https://viem.sh/docs/actions/public/verifyTypedData)  | Structured data signing |

For typed data signatures, stringify the typed data object:

```typescript
import { Fuul } from '@fuul/sdk';

const typedData = { address, domain, types, primaryType, message };

await Fuul.identifyUser({
  identifier: '0x1234...',
  identifierType: 'evm_address',
  message: JSON.stringify(typedData),
  signature: '0xabc...',
});
```

{% hint style="info" %}
Requiring users to sign a message ensures event validity and prevents attribution fraud. Mandatory for wallet-based identifiers; optional for `uuid`.
{% endhint %}


# Sending Custom Events

Fuul natively tracks onchain actions, but projects can also send custom offchain events via the backend API — for any action that happens outside the blockchain and isn't covered by a native integration.

## Sending individual events

Send events via the [Send Event API endpoint](https://fuul.readme.io/reference/sendevent).

**cURL example:**

```bash
curl -X POST https://api.fuul.xyz/api/v1/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-send-trigger-event-key" \
  -d '{
    "name": "custom_conversion",
    "user": {
      "identifier": "0x1234...",
      "identifier_type": "evm_address"  // evm_address | solana_address | xrpl_address | sui_address | stellar_address | email | uuid
    },
    "args": {
      "value": {
        "amount": "1000000",
        "currency": {
          "identifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          "identifier_type": "evm_address",
          "chain_identifier": 1
        }
      }
    }
  }'
```

### Event arguments (`args`)

The `args` object lets you attach metadata to each event. These are the standardized keys:

| Key                | Type   | Description                                                             |
| ------------------ | ------ | ----------------------------------------------------------------------- |
| `value`            | object | Transaction volume — used to calculate variable payouts by default      |
| `revenue`          | object | Revenue generated — used for analytics, optionally for variable payouts |
| `transaction_hash` | string | Onchain transaction hash (when reporting onchain events)                |
| `chain_id`         | number | Chain ID where the event occurred                                       |

### Value and revenue format

Both `value` and `revenue` follow the same structure. The `amount` must be in the **smallest unit** of the currency (e.g., WEI for ETH tokens):

```json
{
  "args": {
    "value": {
      "amount": "1000000",
      "currency": {
        "identifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "identifier_type": "evm_address",
        "chain_identifier": 1
      }
    },
    "revenue": {
      "amount": "100000",
      "currency": {
        "identifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "identifier_type": "evm_address",
        "chain_identifier": 1
      }
    }
  }
}
```

{% hint style="warning" %}
Amounts must be in the smallest unit (WEI for ERC-20 tokens). Incorrect formatting will lead to inaccurate payout calculations.
{% endhint %}

### Points and USD values

For Points or USD-denominated values, use the currency `name` directly:

```json
{
  "args": {
    "value": {
      "amount": "1000000",
      "currency": {
        "name": "POINT"
      }
    }
  }
}
```

| Currency name | Description       |
| ------------- | ----------------- |
| `POINT`       | Points (offchain) |
| `USD`         | US Dollar value   |

### Stellar assets

An event's value can be denominated in a Stellar asset. The currency is named by its Soroban contract address, using a different identifier type from the one used for users:

```json
{
  "args": {
    "value": {
      "amount": "10000000",
      "currency": {
        "identifier": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA",
        "identifier_type": "stellar_contract",
        "chain_identifier": 148
      }
    }
  }
}
```

| Field              | Value                                                                          |
| ------------------ | ------------------------------------------------------------------------------ |
| `identifier`       | The asset's Soroban contract address — a 56-character StrKey starting with `C` |
| `identifier_type`  | `stellar_contract`                                                             |
| `chain_identifier` | `148` (Stellar pubnet)                                                         |
| Decimals           | **7**, not 18                                                                  |

{% hint style="warning" %}
**`stellar_contract` names a currency. `stellar_address` names a user.** They are not interchangeable, and each is rejected in the other's position. The asset in `value.currency` is a `C…` contract address; the person in `user.identifier` is a `G…` account address. See [Stellar signatures](/developer-guide/tracking-referrals-in-your-app#stellar-signatures).
{% endhint %}

{% hint style="warning" %}
Stellar StrKeys are **uppercase base32 and case-sensitive**. Never lowercase one the way you might an EVM address — you will reference a currency that does not exist.

Stellar assets use **7 decimals**. An amount written with 18 decimals is off by 10^11.
{% endhint %}

## Sending batch events

To send multiple events at once, build an array of event objects and use the [Send Batch Events endpoint](https://fuul.readme.io/reference/sendbatchevents).

```json
[
  {
    "name": "custom_conversion",
    "user": {
      "identifier": "0xabc123...",
      "identifier_type": "evm_address"
    },
    "args": {
      "value": {
        "amount": "1000000",
        "currency": {
          "identifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          "identifier_type": "evm_address",
          "chain_identifier": 1
        }
      }
    }
  },
  {
    "name": "custom_conversion",
    "user": {
      "identifier": "0xdef456...",
      "identifier_type": "evm_address"
    },
    "args": {
      "value": {
        "amount": "2000000",
        "currency": {
          "identifier": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          "identifier_type": "evm_address",
          "chain_identifier": 1
        }
      }
    }
  }
]
```

{% hint style="info" %}
You can also check the status of a previously sent event using the [Check Event Status endpoint](https://fuul.readme.io/reference/checkeventstatus).
{% endhint %}


# Webhooks

Webhooks send real-time HTTP notifications to your server when rewards are distributed. Use them to sync payout data with your own systems — update balances, notify users, or trigger downstream workflows.

## When are webhooks triggered?

Webhooks fire when qualifying movements are created:

| Movement type      | Movement reason                         |
| ------------------ | --------------------------------------- |
| `point`            | `end_user_payout` or `affiliate_payout` |
| `onchain-currency` | `end_user_payout` or `affiliate_payout` |
| `airdrop`          | `end_user_payout` or `affiliate_payout` |

{% hint style="warning" %}
Webhooks fire for **all movement statuses**, including `rejected`. Always check the `status` field before processing — see [Payload fields](#payload-fields) below.
{% endhint %}

## Webhook payload

Each webhook delivers a `reward.earned` event as an HTTP POST with a JSON body:

```json
{
  "event_type": "reward.earned",
  "movement": {
    "type": "point",
    "reason": "end_user_payout",
    "status": "accepted",
    "status_details": null,
    "user_identifier": "0x1234...",
    "user_identifier_type": "evm_address",
    "amount": "1000",
    "currency": {
      "name": "POINT"
    },
    "conversion_name": "Trading Volume",
    "created_at": "2025-06-15T14:30:00Z"
  }
}
```

### Payload fields

| Field                           | Description                                                                                   |
| ------------------------------- | --------------------------------------------------------------------------------------------- |
| `event_type`                    | Always `reward.earned`                                                                        |
| `movement.type`                 | `point`, `onchain-currency`, or `airdrop`                                                     |
| `movement.reason`               | `end_user_payout` or `affiliate_payout`                                                       |
| `movement.status`               | `accepted`, `rejected`, or `pending`                                                          |
| `movement.status_details`       | Rejection reason (e.g., wallet screening failure). `null` when accepted                       |
| `movement.user_identifier`      | Wallet address or identifier of the recipient                                                 |
| `movement.user_identifier_type` | `evm_address`, `solana_address`, `sui_address`, `xrpl_address`, `stellar_address`, or `email` |
| `movement.amount`               | Reward amount (token amounts in smallest unit)                                                |
| `movement.currency`             | Currency details (name, address, chain ID for onchain tokens)                                 |
| `movement.conversion_name`      | Name of the conversion that triggered the payout                                              |
| `movement.created_at`           | ISO 8601 timestamp                                                                            |

## Handling webhooks

Your endpoint should receive the POST request, process the event, and return a `200` status code:

```typescript
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks/fuul', (req, res) => {
  const event = req.body;

  if (event.event_type === 'reward.earned') {
    const { status, status_details, type, amount, user_identifier } = event.movement;

    if (status === 'accepted') {
      // Process the reward — update your database, notify the user, etc.
      console.log(`Reward: ${amount} ${type} → ${user_identifier}`);
    } else if (status === 'rejected') {
      // Log rejected movements for investigation
      console.warn(`Rejected: ${user_identifier} — ${status_details}`);
    }
  }

  // Always return 200 quickly
  res.status(200).send('OK');
});
```

**cURL test** (simulate a webhook delivery locally):

```bash
curl -X POST http://localhost:3000/webhooks/fuul \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "reward.earned",
    "movement": {
      "type": "point",
      "reason": "end_user_payout",
      "status": "accepted",
      "status_details": null,
      "user_identifier": "0x1234...",
      "user_identifier_type": "evm_address",
      "amount": "500",
      "currency": { "name": "POINT" },
      "conversion_name": "Referral Signup",
      "created_at": "2025-06-15T14:30:00Z"
    }
  }'
```

## Retries

Failed deliveries (non-2xx responses or timeouts) are retried automatically. To avoid missed events:

* Return a `2xx` status code as quickly as possible
* If processing takes time, acknowledge the request immediately and handle the event asynchronously

## Best practices

| Practice                                | Why                                                                      |
| --------------------------------------- | ------------------------------------------------------------------------ |
| Return `200` immediately, process async | Prevents timeouts and unnecessary retries                                |
| Handle duplicates idempotently          | Retries may deliver the same event more than once                        |
| Check the `status` field                | Not all movements are `accepted` — rejected movements are also delivered |
| Log `status_details` for rejections     | Contains the reason (e.g., wallet screening failure) for debugging       |
| Use HTTPS for your endpoint             | Protects payload data in transit                                         |

## Managing webhooks

Webhooks are configured via the REST API:

| Endpoint                       | Method | Description                 | Reference                                                              |
| ------------------------------ | ------ | --------------------------- | ---------------------------------------------------------------------- |
| `/v1/webhooks`                 | POST   | Create a webhook endpoint   | [View](https://fuul.readme.io/reference/post_v1-webhooks)              |
| `/v1/webhooks`                 | GET    | List your webhook endpoints | [View](https://fuul.readme.io/reference/get_v1-webhooks)               |
| `/v1/webhooks/{id}`            | GET    | Get a specific webhook      | [View](https://fuul.readme.io/reference/get_v1-webhooks-id)            |
| `/v1/webhooks/{id}`            | PATCH  | Update a webhook endpoint   | [View](https://fuul.readme.io/reference/patch_v1-webhooks-id)          |
| `/v1/webhooks/{id}`            | DELETE | Delete a webhook endpoint   | [View](https://fuul.readme.io/reference/delete_v1-webhooks-id)         |
| `/v1/webhooks/{id}/deliveries` | GET    | View delivery history       | [View](https://fuul.readme.io/reference/get_v1-webhooks-id-deliveries) |

{% hint style="info" %}
Webhooks are currently only configurable via the REST API — webapp configuration is not yet available.
{% endhint %}


# Affiliate Links & Codes

Affiliates share tracking links to refer users to your project. By default, links use the affiliate's wallet address as the identifier. Fuul also lets affiliates create custom codes for cleaner, branded URLs.

```
# Default link (wallet address)
https://yourwebsite.com?af=0x1f9090aae28b8a3dceadf281b0f12828e676c326

# With a custom code
https://yourwebsite.com?af=my-affiliate-code
```

## Generating tracking links

Use `generateTrackingLink` to build a referral URL for an affiliate:

```typescript
import { Fuul } from '@fuul/sdk';

const trackingLink = await Fuul.generateTrackingLink(
  'https://yourwebsite.com',  // Base URL where pageview events are implemented
  '0x1234...',                 // Affiliate's address
  'evm_address'                // Identifier type
);

// If the affiliate has registered a code, it is used as the `af` value.
// Otherwise the raw identifier is used: "https://yourwebsite.com?af=0x1234..."
```

You can also append optional tracking parameters:

```typescript
const trackingLink = await Fuul.generateTrackingLink(
  'https://yourwebsite.com',
  '0x1234...',
  'evm_address',
  {
    title: 'campaign-name',
    format: 'banner',
    place: 'homepage'
  }
);
```

## Creating affiliate codes

Affiliates can create codes from the Fuul Hosted Hub, or you can add this to your own site using the SDK.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.createAffiliateCode({
  userIdentifier: '0x1234...',
  identifierType: 'evm_address',
  signature: '0xabc...',
  code: 'my-affiliate-code',
  accountChainId: 1, // Required for EIP-1271 signature verification (smart contract wallets on EVM)
  userRebateRate: 0.05, // optional, sets the rebate rate at creation time (0 to 0.2, max 2 decimals)
});
```

{% hint style="info" %}
The message to sign must follow this exact format:

*I confirm that I am creating the ${affiliateCode} code on Fuul*
{% endhint %}

### Code rules

* Alphanumeric characters and dashes (`-`) only
* Maximum 30 characters

### Error handling

| Error                   | Cause                                           |
| ----------------------- | ----------------------------------------------- |
| `ValidationError`       | Invalid characters in the code                  |
| `InvalidSignatureError` | Signature doesn't match the address and message |
| `AddressInUseError`     | Address already has a code registered           |
| `CodeInUseError`        | Code is already taken                           |

## Updating affiliate codes

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.updateAffiliateCode({
  userIdentifier: '0x1234...',
  identifierType: 'evm_address',
  signature: '0xabc...',
  code: 'my-new-code',
});
```

{% hint style="info" %}
The message to sign must follow this exact format:

*I confirm that I am updating my code to ${affiliateCode} on Fuul*
{% endhint %}

## Getting affiliate info

Returns the affiliate profile for a user: identity (`user_identifier`, `user_identifier_type`), `region`, `current_tier`, and `codes` — an array of all referral codes owned by this affiliate, ordered oldest-first. Each entry includes `code`, `created_at`, `uses`, `clicks`, `total_users`, `total_earnings`, and `rebate_rate`.

`current_tier` is a root-level field (`{ id, name, slug, rank } | null`) — it is not part of each `codes[]` entry. It is `null` when the project has no tier configuration or when no API key is provided.

```typescript
import { Fuul, UserIdentifierType } from '@fuul/sdk';

const affiliate = await Fuul.getAffiliateInfo('0x1234...', UserIdentifierType.EvmAddress);
// Returns the Affiliate object, or null if none exists
```

{% hint style="warning" %}
The `user_rebate_rate` and `rebate_rates[]` fields are no longer returned by the server. The SDK's `getAffiliateCode` method is also deprecated — use `getAffiliateInfo`.
{% endhint %}

{% hint style="info" %}
When authenticated with a Bearer API key, if the affiliate exists but has no codes linked to that project, the endpoint returns `200` with `codes: []` — not `404`. A `404` means the affiliate identifier does not exist at all.
{% endhint %}

### Per-code statistics

`uses`, `clicks`, `total_users`, and `total_earnings` are computed per code, so an affiliate holding several codes can see which one is working.

{% hint style="warning" %}
These four fields previously returned a constant `0` on this endpoint. They now carry real values. If your frontend hides the stats block when it sees zeros, or treats `0` as "not supported", revisit that logic.
{% endhint %}

Two limits are worth knowing when you reconcile these numbers against your own:

| Field    | Limitation                                                                                                                                                                          |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| All four | Attribution is matched on the referrer identifier as it was recorded on the event. Mixed-case identifiers can split what is really one affiliate, undercounting the total           |
| `clicks` | Counts pageview events. A code shared somewhere that never loads your site — accepted by hand, or entered directly in your app — reports `0` clicks while still showing real `uses` |

For the multi-level referral tree (R1-R4), use the separate endpoint `GET /v1/affiliate-portal/referral-tree` (SDK: `Fuul.getReferralTree`).

## Checking code availability

```typescript
import { Fuul } from '@fuul/sdk';

// Check if a code has NOT been registered yet
const isFree = await Fuul.isAffiliateCodeFree('my-code');
// true = available, false = taken

// Check if a code exists AND still has remaining uses
const isAvailable = await Fuul.isAffiliateCodeAvailable('my-code');
```

## Updating rebate rates

Set a custom rebate rate for an affiliate — overrides the project default for that specific user:

```typescript
import { Fuul, UserIdentifierType } from '@fuul/sdk';

await Fuul.updateRebateRate({
  userIdentifier: '0x1234...',
  identifierType: UserIdentifierType.EvmAddress,
  signature: '0xabc...',
  code: 'my-affiliate-code',
  rebateRate: 0.05,  // 5% referral commission. Must be between 0 and 0.2 (0% to 20%) with at most 2 decimal places.
});
```

{% hint style="info" %}
The signature message is **static** — it includes neither the code nor the rate:

`I confirm that I am updating my rebate rate on Fuul`

For XRPL wallets, include `signaturePublicKey`. For smart contract wallets, include `accountChainId`.
{% endhint %}

## API reference

| Feature                 | API endpoint                                       | Reference                                                               |
| ----------------------- | -------------------------------------------------- | ----------------------------------------------------------------------- |
| Create affiliate code   | `POST /v1/affiliates`                              | [View](https://fuul.readme.io/reference/createaffiliatecode)            |
| Update affiliate code   | `POST /v1/affiliates/{userIdentifier}`             | [View](https://fuul.readme.io/reference/updateaffiliatecode)            |
| Update rebate rate      | `POST /v1/affiliates/{userIdentifier}/rebate-rate` | —                                                                       |
| Get affiliate code      | `GET /v1/affiliates/{userIdentifier}`              | [View](https://fuul.readme.io/reference/getaffiliatecode)               |
| Check if code is free   | `GET /v1/affiliates/codes/{code}`                  | [View](https://fuul.readme.io/reference/get_v1-affiliates-codes-code)   |
| Check code availability | `GET /v1/affiliates/codes/{code}/availability`     | [View](https://fuul.readme.io/reference/checkaffiliatecodeavailability) |


# Affiliate Dashboard

The affiliate dashboard is the affiliate-facing section of your incentives hub. It gives affiliates a real-time view of their performance, earnings, and referral activity.

## Aggregate stats

Show an overview of the affiliate's activity across the program:

```typescript
import { Fuul } from '@fuul/sdk';

const stats = await Fuul.getAffiliateStats({
  user_identifier: '0x1234...',
  this_month: true, // or provide from/to for a custom date range
});
```

| Field                      | Description                                                                                                                         |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `total_earnings`           | Rewards earned, per currency (all levels combined, includes POINT rewards)                                                          |
| `referred_volume`          | USD volume from Level 1 (direct) referrals                                                                                          |
| `r2_volume`                | USD volume from Level 2 referrals                                                                                                   |
| `r3_volume`                | USD volume from Level 3 referrals                                                                                                   |
| `r4_volume`                | USD volume from Level 4 referrals                                                                                                   |
| `multilevel_volume`        | USD volume from Level 2 + Level 3 + Level 4 referrals                                                                               |
| `total_volume`             | Combined Level 1 through Level 4 volume                                                                                             |
| `end_user_volume`          | USD volume attributed to the affiliate's own activity as an end user                                                                |
| `referred_revenue`         | Revenue attributed to Level 1 (direct) referrals                                                                                    |
| `r2_revenue`               | Revenue attributed to Level 2 referrals                                                                                             |
| `r3_revenue`               | Revenue attributed to Level 3 referrals                                                                                             |
| `r4_revenue`               | Revenue attributed to Level 4 referrals                                                                                             |
| `referred_attributions`    | Count of Level 1 attribution events (scoped to the same date range as volumes)                                                      |
| `r1_earnings`              | Confirmed payout commissions for Level 1 referrals — array of `{ amount, currency }` in native token units (excludes POINT payouts) |
| `r2_earnings`              | Confirmed payout commissions for Level 2 referrals — array of `{ amount, currency }` in native token units (excludes POINT payouts) |
| `r3_earnings`              | Confirmed payout commissions for Level 3 referrals — array of `{ amount, currency }` in native token units (excludes POINT payouts) |
| `r4_earnings`              | Confirmed payout commissions for Level 4 referrals — array of `{ amount, currency }` in native token units (excludes POINT payouts) |
| `referred_users`           | Total unique users referred                                                                                                         |
| `active_referrers`         | Same value as `referred_users` — alias for the same count                                                                           |
| `active_referred_users_r2` | Distinct referred users with attributed volume at Level 2 (same date scope as volumes)                                              |
| `active_referred_users_r3` | Distinct referred users with attributed volume at Level 3                                                                           |
| `active_referred_users_r4` | Distinct referred users with attributed volume at Level 4                                                                           |
| `total_referrers`          | All-time distinct referred users. Always returns the full historical count — not scoped by `from`/`to` or `this_month`              |
| `assigned_referrers`       | All-time referred users registered for this affiliate. Always returns the full historical count — not scoped by date filters        |
| `current_tier`             | Tier shown to the affiliate, including tier-protection overlay — `{ id, name, slug, rank }` or `null`                               |
| `effective_tier`           | Tier after audience and default rules, without protection overlay — `{ id, name, slug, rank }` or `null`                            |

## New traders

Show users who converted for the first time via this affiliate:

```typescript
const newTraders = await Fuul.getAffiliateNewTraders({
  user_identifier: '0x1234...',
  this_month: true,
});
// Returns: [{ referrer_identifier, total_new_traders }]
```

## Per-referral breakdown

Show volume and earnings for each individual referred user:

```typescript
const breakdown = await Fuul.getPayoutsByReferrer({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
  referrer_scope: 'active', // 'active' (default) | 'all'
  from_date: '2024-01-01', // optional, ISO 8601 — must be used together with to_date
  to_date: '2024-01-31',   // optional, inclusive upper bound
});
```

`referrer_scope` controls which referred users are included: `active` (default) returns only referred users with volume or non-zero earnings; `all` also includes referred users with no attributions yet — zero volume and empty or zero earnings.

## Leaderboard position

Show where the affiliate ranks among all affiliates in the program:

```typescript
const leaderboard = await Fuul.getPayoutsLeaderboard({
  currency_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // required
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  user_type: 'affiliate',
});
```

{% hint style="info" %}
`currency_address` is required on `getPayoutsLeaderboard`. Rankings are per reward currency, so a program paying two tokens has two leaderboards.
{% endhint %}

## Referral tree (multi-level programs)

For multi-level referral programs, display the affiliate's full downline:

```typescript
import { Fuul } from '@fuul/sdk';

const tree = await Fuul.getReferralTree({
  user_identifier: '0x1234...',
});
```

## Time-series breakdown

Show an affiliate's performance over time (volume, revenue, earnings) grouped by day, week, or month:

```typescript
const breakdown = await Fuul.getStatsBreakdown({
  user_identifier: '0x1234...',
  group_by: 'month',           // day | week | month
  date_range: '30d',           // 7d | 30d | MTD | QTD | custom
  currency_id: 'usdc-uuid',    // optional — scope earnings to one reward currency
});
```

Each row in `results` has this shape:

| Field               | Description                                               |
| ------------------- | --------------------------------------------------------- |
| `date`              | Start of the bucket, per `group_by`                       |
| `direct_volume`     | USD volume from Level 1 (direct) referrals in this bucket |
| `indirect_volume`   | USD volume from Level 2–4 referrals, combined             |
| `direct_revenue`    | Revenue attributed to Level 1 referrals                   |
| `indirect_revenue`  | Revenue attributed to Level 2–4 referrals, combined       |
| `attributions`      | Attribution event count                                   |
| `referred_users`    | Distinct referred users active in the bucket              |
| `earnings`          | Commission earned in the bucket                           |
| `earnings_currency` | Currency of `earnings`, or `null`                         |

{% hint style="warning" %}
This breakdown splits volume and revenue **direct vs indirect**, not per level. There are no `r1_volume` / `r2_volume` / `r3_volume` fields, and no bare `revenue` field. If you need a per-level split, use [Paid volumes by level](#paid-volumes-by-level), which returns L1 through L4 separately.
{% endhint %}

{% hint style="info" %}
Leaderboard endpoints (`getPayoutsLeaderboard`) filter conversions via `conversion_external_ids` (array). The stats endpoints (`getAffiliateStats`, `getStatsBreakdown`) filter via `conversion_external_id` (singular, number) or `conversion_name`; the legacy `conversion_id` param on `getAffiliateStats` is deprecated. Audience filtering is only available on the project-wide `getAffiliateTotalStats` endpoint via the `audiences` array; per-affiliate endpoints do not accept audience filters.
{% endhint %}

## Multi-level referral stats

For programs with multi-level referral structures, the stats distinguish between referral levels:

| Level                                                | Description                                            |
| ---------------------------------------------------- | ------------------------------------------------------ |
| **Level 1 (referred\_volume)**                       | Volume from users directly referred by the affiliate   |
| **Level 2 + Level 3 + Level 4 (multilevel\_volume)** | Volume from second, third, and fourth-degree referrals |
| **Total (total\_volume)**                            | Combined Level 1 + Level 2 + Level 3 + Level 4 volume  |

This separation lets affiliates understand how much value comes from their direct referrals vs their extended network.

## Paid volumes by level

Show the volume, revenue, and attribution counts that actually drove payouts — filtered to payout-eligible conversions only, broken down by referral level:

```typescript
import { Fuul } from '@fuul/sdk';

const volumes = await Fuul.getAffiliatePaidVolumesByLevel({
  user_identifier: '0x1234...',
  this_month: true, // or provide from/to for a custom date range
});
```

| Field                                | Description                                                   |
| ------------------------------------ | ------------------------------------------------------------- |
| `payout_eligible_l1_volume`          | Payout-eligible trading volume from Level 1 referrals, in USD |
| `payout_eligible_l2_volume`          | Payout-eligible trading volume from Level 2 referrals, in USD |
| `payout_eligible_l3_volume`          | Payout-eligible trading volume from Level 3 referrals, in USD |
| `payout_eligible_l4_volume`          | Payout-eligible trading volume from Level 4 referrals, in USD |
| `payout_eligible_l1_revenue`         | Attributed revenue at Level 1, in USD                         |
| `payout_eligible_l2_revenue`         | Attributed revenue at Level 2, in USD                         |
| `payout_eligible_l3_revenue`         | Attributed revenue at Level 3, in USD                         |
| `payout_eligible_l4_revenue`         | Attributed revenue at Level 4, in USD                         |
| `payout_eligible_l1_attributions`    | Payout-eligible attribution event count at Level 1            |
| `payout_eligible_l2_attributions`    | Payout-eligible attribution event count at Level 2            |
| `payout_eligible_l3_attributions`    | Payout-eligible attribution event count at Level 3            |
| `payout_eligible_l4_attributions`    | Payout-eligible attribution event count at Level 4            |
| `payout_eligible_total_volume`       | Sum of L1 + L2 + L3 + L4 payout-eligible volume               |
| `payout_eligible_total_revenue`      | Sum of L1 + L2 + L3 + L4 payout-eligible revenue              |
| `payout_eligible_total_attributions` | Sum of L1 + L2 + L3 + L4 payout-eligible attribution counts   |

## Milestone bonuses

Some programs pay affiliates a bonus on top of their commission when they hit a milestone. Two are available, each scoped to a set of conversions you pass as `trigger_refs` (a comma-separated list).

### Qualified user bonus

Counts the referred users that qualified for the given conversions over a window, and returns the total USD bonus owed:

```typescript
import { Fuul } from '@fuul/sdk';

const bonus = await Fuul.getQualifiedUserBonus({
  user_identifier: '0x1234...',
  from: '2026-01-01',        // inclusive, YYYY-MM-DD
  to: '2026-01-31',          // inclusive, YYYY-MM-DD
  trigger_refs: 'ref-1,ref-2',
  claimable_date: '2026-02-15', // optional — when the bonus becomes claimable
});

console.log(bonus.qualified_user_count, bonus.bonus_usd);
```

### High-volume taker bonus

Returns the affiliate's aggregate taker volume and the bonus it earns:

```typescript
const bonus = await Fuul.getHighVolumeTakerBonus({
  user_identifier: '0x1234...',
  trigger_refs: 'ref-1,ref-2',
  this_month: 'true',        // current calendar month in UTC
});

console.log(bonus.aggregate_taker_volume, bonus.bonus);
```

{% hint style="warning" %}
`bonus` is a display string the server has already formatted, not a number. It comes back as literal text like `"$16,000 USDT0"`, or an em dash when the affiliate is below the 50M threshold. Render it directly and don't parse it as a numeric value.
{% endhint %}

{% hint style="info" %}
`this_month` and the `from`/`to` pair are mutually exclusive on `getHighVolumeTakerBonus`. Passing both returns HTTP 400. The same rule applies to `getAffiliatePaidVolumesByLevel`, where all-time is expressed by omitting `from`, `to`, and `this_month` entirely.
{% endhint %}

## Admin: manage affiliates programmatically

Create, list, and update affiliates via the API — for workflows that run outside the dashboard. Requires a project API key.

| Endpoint                               | Description                    |
| -------------------------------------- | ------------------------------ |
| `POST /api/v1/project-affiliates`      | Create a new affiliate         |
| `GET /api/v1/project-affiliates`       | List affiliates with filtering |
| `PATCH /api/v1/project-affiliates/:id` | Update affiliate properties    |

{% hint style="warning" %}
`tier_protection` and `approve_project_tier_ids` are mutually exclusive — set only one per request.
{% endhint %}

## API reference

| Feature                      | API endpoint                                       | Reference                                                                              |
| ---------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Aggregate stats              | `GET /v1/affiliate-portal/stats`                   | [View](https://fuul.readme.io/reference/getaffiliateportalstats)                       |
| Total stats (all affiliates) | `GET /v1/affiliate-portal/total-stats`             | [View](https://fuul.readme.io/reference/get_v1-affiliate-portal-total-stats)           |
| Referral tree                | `GET /v1/affiliate-portal/referral-tree`           | [View](https://fuul.readme.io/reference/get_v1-affiliate-portal-referral-tree)         |
| Global breakdown             | `GET /v1/affiliate-portal/global-breakdown`        | [View](https://fuul.readme.io/reference/get_v1-affiliate-portal-global-breakdown)      |
| Per-referral breakdown       | `GET /v1/payouts/by-referrer`                      | [View](https://fuul.readme.io/reference/getpayoutsbyreferrer)                          |
| Leaderboard position         | `GET /v1/payouts/leaderboard/payouts`              | [View](https://fuul.readme.io/reference/getpayoutsleaderboard)                         |
| Payouts summary              | `GET /v1/payouts/summary`                          | [View](https://fuul.readme.io/reference/getpayoutssummary)                             |
| Paid volumes by level        | `GET /v1/affiliate-portal/paid-volumes-by-level`   | [View](https://fuul.readme.io/reference/get_v1-affiliate-portal-paid-volumes-by-level) |
| Time-series breakdown        | `GET /v1/affiliate-portal/stats-breakdown`         | —                                                                                      |
| Qualified user bonus         | `GET /v1/affiliate-portal/qualified-user-bonus`    | —                                                                                      |
| High-volume taker bonus      | `GET /v1/affiliate-portal/high-volume-taker-bonus` | —                                                                                      |


# Referral Codes

Referral codes let projects create shareable codes for their users. When a user accepts a referral code, a permanent referrer-user relationship is created. The SDK provides methods to list, generate, check, use, and delete referral codes.

Referral codes are generated by the **project** on behalf of users — they are auto-generated (random 7-char alphanumeric) and scoped to a single project. Affiliate codes can also be [activated as referral codes](/core-concepts/affiliates/referral-codes-vs-invite-codes#using-an-affiliate-code-as-a-referral-code) within a project.

{% hint style="info" %}
Referral codes are different from [affiliate codes](/developer-guide/creating-affiliate-links-or-codes). Affiliate codes are custom, created by the affiliate with a wallet signature, and embedded in tracking links (`?af=code`) for automatic attribution. Referral codes are project-generated and require the user to explicitly accept the code.

For a full comparison, see [Affiliate Codes vs Referral Codes](/core-concepts/affiliates/referral-codes-vs-invite-codes).
{% endhint %}

## List a user's referral codes

```typescript
import { Fuul, UserIdentifierType } from '@fuul/sdk';

const result = await Fuul.listUserReferralCodes({
  user_identifier: '0x1234...',
  user_identifier_type: UserIdentifierType.EvmAddress,
});
```

## Generating referral codes

Generate 1-50 random codes (7-char alphanumeric) on behalf of a user:

```typescript
import { Fuul, UserIdentifierType } from '@fuul/sdk';

const codes = await Fuul.generateReferralCodes({
  user_identifier: '0x1234...',
  user_identifier_type: UserIdentifierType.EvmAddress,
  quantity: 5,        // 1-50
  max_uses: 10,       // per code, or null for unlimited
});
// Returns: [{ code }, ...]
```

## Check referral status

Check whether a user was referred and with which code:

```typescript
const status = await Fuul.getReferralStatus({
  user_identifier: '0x1234...',
  user_identifier_type: UserIdentifierType.EvmAddress,
});

if (status.referred) {
  console.log('User was referred with code:', status.code);
}
```

## Check if a code is available

```typescript
const result = await Fuul.getReferralCode({ code: 'abc1234' });

if (result.available) {
  console.log('Referral code is available!');
}
```

## Accept a referral code

When a user accepts a referral code, a permanent referrer-user relationship is created — all future conversions by this user will be attributed to the referrer.

```typescript
await Fuul.useReferralCode({
  code: 'abc1234',
  user_identifier: '0x1234...',
  user_identifier_type: UserIdentifierType.EvmAddress,
  signature: '0xabc...',
  signature_message: 'I am using invite code abc1234',
  chain_id: 1, // Only for smart contract wallets
});
```

{% hint style="info" %}
The signed message must follow this exact format: `I am using invite code ${code}`

Note: the signature says "invite code" for legacy reasons — this applies to all referral codes, regardless of how your project uses them.

Requiring a signature ensures event validity and prevents fraud. This is mandatory.
{% endhint %}

## Set a referrer via API

If you manage your own referral system (e.g. a fintech or exchange with existing user relationships), you can create or update referrer-referee relationships directly through the API instead of using referral codes ([API reference](https://fuul.readme.io/reference/put_v1-user-referrers)):

```bash
curl -X PUT https://api.fuul.xyz/api/v1/user-referrers   -H "Content-Type: application/json"   -H "Authorization: Bearer your-service-role-key"   -d '{
    "user_identifier": "0x1234...",
    "user_identifier_type": "evm_address",
    "referrer_identifier": "0x5678...",
    "referrer_identifier_type": "evm_address",
    "referral_code": "abc1234"
  }'
```

If you include a `referral_code`, the code is validated and its rebate rate is locked into the relationship. The response includes a `referral_code_id` you can use to trace which code established the referral.

{% hint style="info" %}
This endpoint requires a `service_role` API key. If the user already has a referrer, the existing relationship is overwritten.
{% endhint %}

## Remove a referrer via API

To delete an admin-managed referrer-user relationship, use the `DELETE /api/v1/user-referrers` endpoint ([API reference](https://fuul.readme.io/reference/delete_v1-user-referrers)):

```bash
curl -X DELETE https://api.fuul.xyz/api/v1/user-referrers \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-service-role-key" \
  -d '{
    "user_identifier": "0x1234...",
    "user_identifier_type": "evm_address"
  }'
```

{% hint style="info" %}
This endpoint requires a `service_role` API key. It removes the referrer relationship for the specified user.

Add `?force=true` to the query string to force-delete all referral code associations for this user in a single call — including any that match the current referrer. Without `force`, the endpoint returns `422` if matching associations exist.
{% endhint %}

## List referral relationships via API

If you mirror Fuul's referral data into your own systems, use `GET /api/v1/user-referrers` to read the project-wide referral bridge for incremental ingestion. Each result represents one direct (level 1) referrer-user relationship, with the upstream chain nested under `referral_chain`:

```bash
curl -X GET "https://api.fuul.xyz/api/v1/user-referrers?updated_since=2026-06-01T00:00:00Z&limit=500" \
  -H "Authorization: Bearer your-service-role-key"
```

**Response shape:**

```json
{
  "results": [
    {
      "user_identifier": "0x1234...",
      "user_identifier_type": "evm_address",
      "referrer_identifier": "0x5678...",
      "referrer_identifier_type": "evm_address",
      "referral_chain": {
        "referrer_2": "0x9abc...",
        "referrer_3": null,
        "referrer_4": null
      },
      "referral_code": "abc1234",
      "source": "code_acceptance",
      "reason": "via_endpoint",
      "rebate_rate": "0.1",
      "created_at": "2026-06-01T14:30:00Z",
      "updated_at": "2026-06-01T14:30:00Z"
    }
  ],
  "next_cursor": "01J...",
  "count": 1
}
```

| Field                                              | Description                                                                                |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `referrer_identifier` / `referrer_identifier_type` | The direct (level 1) referrer                                                              |
| `referral_chain.referrer_2` … `referrer_4`         | Upstream levels 2–4, computed live at read time; `null` when there is no upstream referrer |
| `rebate_rate`                                      | Rebate rate locked into the relationship (from the referral code, when present)            |
| `next_cursor`                                      | Pass back as `after_id` to fetch the next page; `null` on the last page                    |

**Query params:** `created_since`, `updated_since` (ISO 8601), `after_id` (cursor), `limit` (default `500`, max `1000`).

{% hint style="warning" %}
This endpoint requires a `service_role` API key.

A change to an upstream relationship can alter a descendant's derived levels 2–4 **without** updating that descendant's `updated_at`. If you rely on `updated_since` for incremental syncs, run a periodic full backfill (paging with `after_id` and no time filter) to catch chain changes.
{% endhint %}

## Get user referrer

Get the current referrer for a specific user:

```typescript
import { Fuul } from '@fuul/sdk';

const referrer = await Fuul.getUserReferrer({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
});
```

## Delete a referral

Allows users to remove a referral relationship they have, freeing up a use on the referral code. The referral code's usage count will be incremented by one.

```typescript
await Fuul.deleteReferral({
  code: 'abc1234',
  user_identifier: '0x1234...',
  user_identifier_type: UserIdentifierType.EvmAddress,
  referrer_identifier: '0xabcde...',
  referrer_identifier_type: UserIdentifierType.EvmAddress,
  signature: '0xabc...',
  signature_message: 'I am deleting referral for user 0x1234... from code abc1234',
});
```

{% hint style="info" %}
The signed message must follow this exact format: `I am deleting referral for user ${user_identifier} from code ${code}`
{% endhint %}

## Admin: referral code lookup

Search and inspect referral codes across all users in your project (requires admin API key):

| Endpoint                                                          | Description                                                                                              |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `GET /api/v1/projects/:projectId/affiliates/referral-codes`       | List referral codes with optional `search` param — ILIKE match on code or user identifier, max 200 chars |
| `GET /api/v1/projects/:projectId/affiliates/referral-codes/stats` | Aggregated stats for referral codes                                                                      |

## API reference

| Feature                             | API endpoint                                 | Reference                                                                        |
| ----------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------- |
| List user's codes                   | `GET /v1/referral-codes`                     | [View](https://fuul.readme.io/reference/getreferralcodesbyowner)                 |
| Generate codes                      | `POST /v1/referral-codes`                    | [View](https://fuul.readme.io/reference/generatereferralcodesbatch)              |
| Check code availability             | `GET /v1/referral-codes/{code}`              | [View](https://fuul.readme.io/reference/getreferralcodesbyowner-code)            |
| Update code                         | `PATCH /v1/referral-codes/{code}`            | [View](https://fuul.readme.io/reference/updatereferralcode)                      |
| Check referral status               | `GET /v1/referral-codes/status`              | [View](https://fuul.readme.io/reference/getreferralcodesbyowner-status)          |
| Use referral code                   | `PATCH /v1/referral-codes/{code}/use`        | [View](https://fuul.readme.io/reference/updatereferralcode-use)                  |
| Delete referral                     | `DELETE /v1/referral-codes/{code}/referrals` | [View](https://fuul.readme.io/reference/delete_v1-referral-codes-code-referrals) |
| Set referrer via API                | `PUT /v1/user-referrers`                     | [View](https://fuul.readme.io/reference/put_v1-user-referrers)                   |
| Remove referrer via API             | `DELETE /v1/user-referrers`                  | [View](https://fuul.readme.io/reference/delete_v1-user-referrers)                |
| List referral relationships via API | `GET /v1/user-referrers`                     | [Section above](#list-referral-relationships-via-api)                            |
| Get user referrer                   | `GET /v1/user/referrer`                      | [View](https://fuul.readme.io/reference/getuserreferrer)                         |


# Leaderboard Data

Retrieve and display leaderboard rankings for your incentive programs. Fuul provides five leaderboard types:

| Leaderboard                                                                   | SDK method                    | What it ranks                                             |
| ----------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------- |
| [Tokens](/developer-guide/getting-leaderboard-data/tokens)                    | `getPayoutsLeaderboard`       | Onchain token rewards earned                              |
| [Points](/developer-guide/getting-leaderboard-data/points)                    | `getPointsLeaderboard`        | Point rewards earned                                      |
| [Volume](/developer-guide/getting-leaderboard-data/volume)                    | `getVolumeLeaderboard`        | Trading/transaction volume generated                      |
| [Revenue](/developer-guide/getting-leaderboard-data/volume#volume-vs-revenue) | `getRevenueLeaderboard`       | Revenue generated, e.g. fees                              |
| Referred users                                                                | `getReferredUsersLeaderboard` | Number of users referred (`GET /v1/leaderboard/referred`) |

{% hint style="warning" %}
`getReferredUsersLeaderboard` is the one leaderboard that does not sit under `/v1/payouts`. Its endpoint is `GET /v1/leaderboard/referred`.
{% endhint %}

{% hint style="info" %}
Leaderboard data is updated hourly. Recent conversions and payouts will appear within a maximum of one hour.
{% endhint %}

### Common parameters

Filters are not uniform across the five methods. Check the "Supported by" column before passing one:

| Parameter                 | Type      | Supported by                                         | Description                                                                                            |
| ------------------------- | --------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `currency_address`        | string    | **Required** on Tokens · optional on Volume, Revenue | Token contract address on the payout chain                                                             |
| `user_identifier`         | string    | Tokens, Points, Volume, Revenue                      | Filter to a specific user (for individual rewards)                                                     |
| `identifier_type`         | string    | Tokens, Points, Volume, Revenue                      | `'evm_address'`, `'solana_address'`, `'sui_address'`, `'xrpl_address'`, `'stellar_address'`, `'email'` |
| `user_type`               | string    | Tokens, Volume, Revenue                              | `'affiliate'` or `'end_user'` (omit for all)                                                           |
| `fields`                  | string    | Tokens, Points                                       | Comma-separated extra fields: `'tier,referred_volume,enduser_volume,enduser_revenue,referred_users'`   |
| `conversion_external_ids` | number\[] | Tokens                                               | Array of conversion IDs to filter by                                                                   |
| `page` / `page_size`      | number    | All                                                  | Pagination, `page_size` max 100                                                                        |

{% hint style="warning" %}
Two exceptions worth knowing before you write the call:

* `getPayoutsLeaderboard` **requires** `currency_address`. Omitting it is a TypeScript error.
* `getPointsLeaderboard` does not accept `user_type`, `from`, `to`, `currency_address`, or `conversion_external_ids`. Points are ranked across the whole program.
* `getReferredUsersLeaderboard` accepts `page` and `page_size` only — no user or conversion filters.
  {% endhint %}

{% hint style="info" %}
Leaderboard responses are paginated. Use `page` and `page_size` (max 100 per page) to retrieve the full leaderboard — e.g., page 1 returns ranks 1–100, page 2 returns ranks 101–200, and so on.
{% endhint %}


# Tokens

Retrieve the onchain token payouts leaderboard using the `getPayoutsLeaderboard` method.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPayoutsLeaderboard({
  currency_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  user_type: 'affiliate',
});
```

Example response:

```json
{
  "total_results": 156,
  "page": 1,
  "page_size": 10,
  "results": [
    {
      "address": "0x1234...",
      "total_amount": 5200.50,
      "rank": 1,
      "total_attributions": 48
    },
    {
      "address": "0x5678...",
      "total_amount": 3100.25,
      "rank": 2,
      "total_attributions": 31
    }
  ]
}
```

| Parameter          | Required | Description                                                           |
| ------------------ | -------- | --------------------------------------------------------------------- |
| `currency_address` | Yes      | Token contract address on the payout chain                            |
| `user_type`        | No       | `'affiliate'` or `'end_user'`                                         |
| `fields`           | No       | Extra fields: `'tier,referred_volume,enduser_volume,enduser_revenue'` |

{% hint style="info" %}
Projects can pay out different tokens for different conversions. Use the `currency_address` of the specific token you want to query.
{% endhint %}

{% hint style="info" %}
No `chain_id` is required — the SDK automatically uses the chain where the program is hosted.
{% endhint %}

### With extra fields

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPayoutsLeaderboard({
  currency_address: '0xA0b86...',
  fields: 'tier,referred_volume,enduser_volume,enduser_revenue',
});
```

{% hint style="info" %}
The `referred_volume` values are returned in USD.
{% endhint %}

{% hint style="info" %}
Leaderboard responses are paginated. Use `page` and `page_size` (max 100 per page) to retrieve the full leaderboard.
{% endhint %}

For full endpoint details, see the [API reference](https://fuul.readme.io/reference/getpayoutsleaderboard).


# Points

Retrieve the points leaderboard using the `getPointsLeaderboard` method.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPointsLeaderboard({});
```

Example response:

```json
{
  "total_results": 240,
  "page": 1,
  "page_size": 10,
  "results": [
    {
      "address": "0x1234...",
      "total_amount": 15000,
      "rank": 1,
      "total_attributions": 85
    }
  ]
}
```

| Parameter         | Required | Description                                                           |
| ----------------- | -------- | --------------------------------------------------------------------- |
| `fields`          | No       | Extra fields: `'tier,referred_volume,enduser_volume,enduser_revenue'` |
| `user_identifier` | No       | Filter to a specific user's row                                       |
| `identifier_type` | No       | Identifier type for the `user_identifier` filter                      |
| `page`            | No       | Page number (default 1)                                               |
| `page_size`       | No       | Results per page (max 100)                                            |

{% hint style="warning" %}
The points leaderboard takes no other filters. `user_type`, `currency_address`, `from`, `to`, and `conversion_external_ids` are not supported — they were removed from `GetPointsLeaderboardParams` and ranking always covers the whole program. Use `getPayoutsLeaderboard` if you need to rank by currency or by conversion.
{% endhint %}

### With extra fields

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPointsLeaderboard({
  fields: 'tier,referred_volume,enduser_volume,enduser_revenue',
});
```

{% hint style="info" %}
The `referred_volume` values are returned in USD.
{% endhint %}

{% hint style="info" %}
Leaderboard responses are paginated. Use `page` and `page_size` (max 100 per page) to retrieve the full leaderboard.
{% endhint %}

For full endpoint details, see the [API reference](https://fuul.readme.io/reference/getpointsleaderboard).


# Volume

Retrieve the volume leaderboard using the `getVolumeLeaderboard` method. This shows transaction volume generated by users.

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getVolumeLeaderboard({
  user_type: 'affiliate',
});
```

{% hint style="info" %}
`user_type` is optional. Use `'affiliate'` for referred volume or `'end_user'` for direct user volume.
{% endhint %}

Example response:

```json
{
  "total_results": 89,
  "page": 1,
  "page_size": 10,
  "results": [
    {
      "address": "0x1234...",
      "total_amount": "1250000.00",
      "rank": 1
    }
  ]
}
```

{% hint style="info" %}
Volume values are returned in USD.
{% endhint %}

{% hint style="info" %}
Leaderboard responses are paginated. Use `page` and `page_size` (max 100 per page) to retrieve the full leaderboard.
{% endhint %}

`getVolumeLeaderboard` calls `GET /v1/payouts/leaderboard/volume`.

## Volume vs revenue

Volume and revenue are two separate leaderboards backed by two separate endpoints. Don't mix them up:

| SDK method              | Endpoint                              | Ranks by                                                                                               |
| ----------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `getVolumeLeaderboard`  | `GET /v1/payouts/leaderboard/volume`  | Transaction volume generated (USD)                                                                     |
| `getRevenueLeaderboard` | `GET /v1/payouts/leaderboard/revenue` | Revenue generated, e.g. fees ([API reference](https://fuul.readme.io/reference/getrevenueleaderboard)) |

`getRevenueLeaderboard` takes the same shape of filters, including `user_identifier`, `identifier_type`, and `user_type`.


# Individual Rewards

Retrieve reward data for individual users — their earnings, ranking position, and payout history.

| Reward type                                                  | Page                  | SDK methods                                                                     |
| ------------------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------- |
| [Tokens](/developer-guide/getting-individual-rewards/tokens) | Onchain token rewards | `getPayoutsLeaderboard`, `getUserPayoutsByConversion`, `getUserPayoutMovements` |
| [Points](/developer-guide/getting-individual-rewards/points) | Point rewards         | `getPointsLeaderboard`, `getUserPointsByConversion`, `getUserPointsMovements`   |

{% hint style="info" %}
Rewards data is updated hourly. Recent conversions and payouts will appear within a maximum of one hour.
{% endhint %}

{% hint style="info" %}
For totals across all conversions, call `GET /v1/payouts/totals/{userIdentifier}` directly ([API reference](https://fuul.readme.io/reference/getuserpayouttotals)). There is no SDK wrapper for this endpoint.
{% endhint %}


# Tokens

Retrieve onchain token rewards for individual users.

### Rewards for a specific user

Use `getPayoutsLeaderboard` filtered by `user_identifier` to get an individual user's token rewards:

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPayoutsLeaderboard({
  currency_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  from: '2024-01-01',                    // Optional
  to: '2024-12-31',                      // Optional
  user_type: 'affiliate',                // Optional: 'affiliate' | 'end_user'
  conversion_external_ids: [1, 2, 3],    // Optional: filter by conversion IDs
});
```

Example response:

```json
{
  "total_results": 1,
  "page": 1,
  "page_size": 10,
  "results": [
    {
      "address": "0x1234...",
      "total_amount": 200,
      "rank": 1,
      "total_attributions": 10
    }
  ]
}
```

{% hint style="info" %}
`total_amount` is already formatted with the token's decimals.
{% endhint %}

Add extra fields like tier and volume:

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPayoutsLeaderboard({
  currency_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // required on every call
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  fields: 'tier,referred_volume,referred_users',
});
```

### Payouts grouped by conversion

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getUserPayoutsByConversion({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  from: '2024-01-01',
  to: '2024-12-31',
});
```

### Payout history (movements)

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getUserPayoutMovements({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
});
```

Each movement includes a `payout_status` field indicating where the payout is in its lifecycle:

| Status                         | Description                                         |
| ------------------------------ | --------------------------------------------------- |
| `pending_recipient_acceptance` | Waiting for the recipient to accept the reward      |
| `pending_approval`             | Waiting for project admin approval                  |
| `pending_transaction`          | Approved, waiting for the transaction to be sent    |
| `sending_transaction`          | Transaction is being sent to the blockchain         |
| `pending_confirmation`         | Transaction sent, waiting for on-chain confirmation |
| `confirmed`                    | Payout confirmed successfully                       |
| `failed`                       | Transaction failed                                  |
| `rejected`                     | Payout rejected by admin or system                  |
| `deferred`                     | Payout deferred (e.g., below minimum threshold)     |

## API reference

| SDK method                   | API endpoint                          | Reference                                                           |
| ---------------------------- | ------------------------------------- | ------------------------------------------------------------------- |
| `getPayoutsLeaderboard`      | `GET /v1/payouts/leaderboard/payouts` | [View](https://fuul.readme.io/reference/getpayoutsleaderboard)      |
| `getUserPayoutsByConversion` | `GET /v1/payouts`                     | [View](https://fuul.readme.io/reference/getuserpayoutsbyconversion) |
| `getUserPayoutMovements`     | `GET /v1/payouts/movements`           | [View](https://fuul.readme.io/reference/getuserpayoutmovements)     |


# Points

Retrieve point rewards for individual users.

## Points for a specific user

Use `getPointsLeaderboard` filtered by `user_identifier`:

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getPointsLeaderboard({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
});
```

Example response:

```json
{
  "total_results": 1,
  "page": 1,
  "page_size": 10,
  "results": [
    {
      "address": "0x1234...",
      "total_amount": 15000,
      "rank": 42,
      "total_attributions": 85
    }
  ]
}
```

## Points grouped by conversion

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getUserPointsByConversion({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  from: '2024-01-01',
  to: '2024-12-31',
});
```

### Paginating the response

| Field           | Behavior                                                                                                                         |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `total_results` | The number of rows matching the query. Compare it against how many rows you have fetched, not against a sum of point amounts     |
| Page size       | Pages fill to `page_size` even on a program paying several currencies — the filter to point rows runs in the query, not after it |
| Ordering        | Stable. Point amounts tie constantly, so rows are ordered with `conversion_external_id` and `reason` as tiebreakers              |

{% hint style="info" %}
Page through until you have collected `total_results` rows. Earlier behavior made `total_results` and the returned row count disagree, so drop any workaround you built around that.
{% endhint %}

## Points history (movements)

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getUserPointsMovements({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
});
```

Each movement includes a `payout_status` field. For point-type movements, this is always `confirmed` since no on-chain transaction is needed.

{% hint style="info" %}
Rewards data is updated hourly. Recent conversions and payouts will appear within a maximum of one hour.
{% endhint %}

## API reference

| SDK method                  | API endpoint                         | Reference                                                           |
| --------------------------- | ------------------------------------ | ------------------------------------------------------------------- |
| `getPointsLeaderboard`      | `GET /v1/payouts/leaderboard/points` | [View](https://fuul.readme.io/reference/getpointsleaderboard)       |
| `getUserPointsByConversion` | `GET /v1/payouts`                    | [View](https://fuul.readme.io/reference/getuserpayoutsbyconversion) |
| `getUserPointsMovements`    | `GET /v1/payouts/movements`          | [View](https://fuul.readme.io/reference/getuserpayoutmovements)     |


# Referred Metrics

Retrieve referral performance data — which users were referred by an affiliate, how much volume they generated, and how much the referrer earned.

## Referred users for a specific affiliate

Use `getPayoutsByReferrer` to get payout and volume information for each user referred by a specific affiliate:

```typescript
import { Fuul } from '@fuul/sdk';

const results = await Fuul.getPayoutsByReferrer({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
  referrer_scope: 'active',   // 'active' (default) or 'all' — see below
  from_date: '2026-01-01',    // optional — ISO 8601, omit for all-time
  to_date: '2026-06-08',      // optional — must be paired with from_date
});
```

The response is an array of records where each key is a referred user's address. Each entry contains:

| Field                      | Type           | Description                                                                                    |
| -------------------------- | -------------- | ---------------------------------------------------------------------------------------------- |
| `volume`                   | number         | Total volume generated by the referred user (USD)                                              |
| `direct_eligible_volume`   | number         | L1 volume from this user that triggered a payout (USD) — a subset of `volume`                  |
| `indirect_eligible_volume` | number         | Volume from this user's downline (L2–L4) that triggered a payout, mapped to this L1 user (USD) |
| `earnings`                 | array          | L1-only commission per currency, each with `currency` (address, chainId) and `amount`          |
| `total_commission_earned`  | array          | Combined L1–L4 commission per currency (same shape as `earnings`)                              |
| `date_joined`              | string \| null | When the referred user first converted; `null` for users with no confirmed attributions        |
| `referral_code`            | string \| null | Referral code that established this relationship, if any                                       |
| `user_rebate_rate`         | number \| null | Rebate rate applied to this referred user, if set                                              |

{% hint style="info" %}
`volume` is the total volume generated by the referred user. `direct_eligible_volume` is the subset of that volume for which a payout was actually generated — not all volume may qualify depending on incentive rules, budgets, or fraud checks.
{% endhint %}

{% hint style="info" %}
`referrer_scope: 'active'` (default) returns only referred users with non-zero volume or earnings. Use `'all'` to also include users who were referred but have no activity yet (`volume: 0`, `earnings: []`, `date_joined: null`).
{% endhint %}

## Referred volume for a set of users

Use `getReferredVolume` to retrieve the referred volume for multiple users at once:

```typescript
import { Fuul } from '@fuul/sdk';

const results = await Fuul.getReferredVolume({
  user_identifiers: ['0x1234...', '0x5678...'],
  identifier_type: 'evm_address',
});
```

## Referred volume leaderboard

To get the full referred volume leaderboard, use the volume leaderboard with `user_type = 'affiliate'`:

{% content-ref url="/pages/DzzfdNeaVZLSZSvN0K8Q" %}
[Volume](/developer-guide/getting-leaderboard-data/volume)
{% endcontent-ref %}

## API reference

| Feature             | API endpoint                                  | Reference                                                                           |
| ------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------- |
| Payouts by referrer | `GET /v1/payouts/by-referrer`                 | [View](https://fuul.readme.io/reference/getpayoutsbyreferrer)                       |
| Referred volume     | `GET /v1/payouts/leaderboard/referred-volume` | [View](https://fuul.readme.io/reference/get_v1-payouts-leaderboard-referred-volume) |


# Verifying X Follows

Verify whether a user follows a specific X (Twitter) account using Fuul's secure, client-side verification flow.

{% hint style="warning" %}
Due to X API limitations, access to user data requires an OAuth authentication token. For security reasons, Fuul does **not** allow authentication tokens to be transmitted to or stored on its servers. All verification must occur through the dedicated frontend flow described below.
{% endhint %}

## Verification URL

Direct users to the verification page for your project:

```
https://app.fuul.xyz/verify-social/{project-slug}/x
```

Replace `{project-slug}` with your actual project slug.

### Optional redirect

Append a `redirectUrl` parameter to redirect the user after verification completes:

```
https://app.fuul.xyz/verify-social/{project-slug}/x?redirectUrl={redirectUrl}
```

## Color customization

The verification modal adopts the **background** and **primary** colors defined in the **Page Customization** section on the Incentives page in the Fuul webapp, ensuring consistent branding throughout the flow.


# Whitelabel TVL & APR

Display real-time TVL (Total Value Locked) and APR (Annual Percentage Rate) on your custom incentives hub. Fuul automatically calculates and refreshes these metrics for programs built on lending, liquidity, and staking protocols.

## How it works

Fuul computes TVL and APR at the conversion level for pool-based incentive programs:

* **TVL** — total value currently locked in the underlying protocol pool, denominated in USD
* **APR** — estimated annualized return based on the configured reward rate and current TVL

Metrics are automatically refreshed every \~3 hours for active pool incentives.

## Fetching TVL & APR

Use the public incentives endpoint — no API key required ([API reference](https://fuul.readme.io/reference/get_v1-incentives)):

```typescript
// GET /v1/incentives?protocol=your-protocol-code
```

The response includes TVL and APR per conversion:

```json
{
  "conversions": [
    {
      "name": "Deposit USDC",
      "tvl": "12500000.00",
      "apr": "0.145",
      "liquidity_pool_protocol": {
        "code": "morpho-vaults",
        "name": "Morpho Vaults"
      }
    }
  ]
}
```

Display `apr` as a percentage (e.g., `0.145` → `14.5% APR`).

## Protocol support

| Protocol type                              | TVL source                    |
| ------------------------------------------ | ----------------------------- |
| **Uniswap V3 and V3-style DEXs**           | Subgraph query                |
| **Lending pools** (Morpho, Compound, etc.) | `totalSupply()` contract call |
| **Morpho Vaults**                          | Subgraph query                |
| **Euler V2 looping**                       | Subgraph query                |

## Use cases

* Show depositors the current APR next to each incentive
* Display protocol TVL to signal liquidity depth and program health
* Build comparison tables of multiple active incentives with their live rates

{% hint style="info" %}
APR is calculated based on the reward rate configured in your program divided by the current TVL. It updates automatically as TVL changes — no manual refresh needed.
{% endhint %}


# Claim Flow Integration

If your program distributes token rewards, users need a way to claim them. This section covers what to display and how to wire up the claiming flow.

## Show claimable balances

Before asking users to submit a transaction, show them what they have available to claim ([API reference](https://fuul.readme.io/reference/getclaimabletotals)):

```typescript
import { Fuul } from '@fuul/sdk';

const totals = await Fuul.getClaimCheckTotals({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
});
```

{% hint style="info" %}
Returns: claimed and unclaimed arrays grouped by currency.
{% endhint %}

## Pending acceptance payouts

Some programs require users to explicitly accept payouts before they become claimable. This is useful for compliance flows or programs where users must agree to terms before receiving rewards.

| Endpoint                                     | Description                                                                | Reference                                            |
| -------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------- |
| `GET /v1/payouts/pending-acceptance`         | Check if a user has payouts waiting for acceptance                         | [View](https://docs.fuul.xyz/for-devs/api-endpoints) |
| `POST /v1/payouts/pending-acceptance/accept` | Accept pending payouts — body: `{ recipient_address, signature, message }` | [View](https://docs.fuul.xyz/for-devs/api-endpoints) |

{% hint style="info" %}
After accepting, the payouts become claimable through the regular [claim check flow](/developer-guide/claiming-onchain-rewards/get-claim-checks).
{% endhint %}

## Fetch claim checks

When the user is ready to claim, fetch their signed vouchers ([API reference](https://fuul.readme.io/reference/generateclaimsignature)):

```typescript
const claimChecks = await Fuul.getClaimableChecks({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
});
```

Each check comes back with its own `id` (a uuid) alongside the voucher fields, so you can correlate a signed voucher with a specific reward row.

Pass these directly to the Fuul contract claim function. See the chain-specific guides for the full transaction:

* [EVM Claiming](/developer-guide/claiming-onchain-rewards/evm)
* [SVM Claiming](/developer-guide/claiming-onchain-rewards/svm-solana)

{% hint style="info" %}
If your project has claim check aggregation enabled, checks start as `open` and must be finalized with `Fuul.closeClaimChecks` before `getClaimableChecks` returns them. See [Get Claim Checks](/developer-guide/claiming-onchain-rewards/get-claim-checks#claim-check-aggregation-open-checks).
{% endhint %}

{% hint style="warning" %}
The Fuul contract charges a small native token fee per claim transaction. Always read the fee dynamically using `getFeesInformation()`.
{% endhint %}

## Show payout status history

Show the lifecycle status of a user's payouts per conversion — when each payout was created, approved, and its current status ([API reference](https://fuul.readme.io/reference/getuserpayoutmovements)):

```typescript
const movements = await Fuul.getUserPayoutMovements({
  user_identifier: '0x1234...',
  identifier_type: 'evm_address',
  page: 1,
  page_size: 20,
});
```

Each movement in `results`:

| Field                               | Description                                                                                                                    |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `date`                              | When the movement was created                                                                                                  |
| `payout_id`                         | Identifier of the payout this movement belongs to                                                                              |
| `currency_address`                  | Reward token address. There is no `currency` field                                                                             |
| `chain_id`                          | Chain of the reward token, as a **string**                                                                                     |
| `is_referrer`                       | `true` when the user earned this as a referrer rather than as an end user                                                      |
| `conversion_id` / `conversion_name` | The conversion that produced the payout                                                                                        |
| `total_amount`                      | Amount as a raw string in the token's smallest unit                                                                            |
| `volume_usd`                        | USD volume of the underlying conversion                                                                                        |
| `project_name`                      | Project the payout belongs to                                                                                                  |
| `payout_status`                     | Lifecycle status — see [Tokens](/developer-guide/getting-individual-rewards/tokens#payout-history-movements) for the full list |
| `payout_status_details`             | Reason text when the status needs one, `null` otherwise                                                                        |
| `enduser_address`                   | Address of the end user whose action generated the payout                                                                      |
| `user_identifier`                   | Recipient of the movement                                                                                                      |
| `referrer_identifier`               | The referrer credited, or `null`                                                                                               |
| `dedup_id`                          | Deduplication key of the source event, or `null`                                                                               |

{% hint style="info" %}
`referrer_identifier`, `enduser_address`, and `dedup_id` let you join a movement back to the event that caused it. Useful when reconciling Fuul payouts against your own records.
{% endhint %}

## Show onchain claim history

Show a user's completed onchain claim transactions with their transaction hash ([API reference](https://fuul.readme.io/reference/get_v1-claim-checks-claim-history)):

```typescript
const history = await Fuul.getClaimHistory({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
  page: 1,
  page_size: 25,
});
```

{% hint style="info" %}
Returns: results (array), total\_count, next\_page.
{% endhint %}

{% hint style="warning" %}
amount is a raw integer string — divide by `10 ** currency_decimals` before displaying to users.

A single transaction that settled multiple currencies produces multiple consecutive rows sharing the same hash.
{% endhint %}

## Claim on behalf of users

Projects can submit claim transactions on behalf of users — tokens land in the user's wallet with no action required from them. See the [EVM Claiming guide](/developer-guide/claiming-onchain-rewards/evm) for implementation details.

## Public claimable rewards

To display unclaimed reward balances without requiring an API key or SDK initialization, use the public claimable rewards endpoint. See [Public Claimable Rewards](/developer-guide/claimable-rewards).

## Admin: rewards payouts overview

These endpoints are for project dashboards — they return claim check data across all users in the project.

| Endpoint                                      | Description                                                          | Reference                                                                           |
| --------------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `GET /v1/claim-checks/rewards-payouts`        | List all claim checks with filtering by status, date range, and user | [View](https://fuul.readme.io/reference/get_v1-claim-checks-rewards-payouts)        |
| `GET /v1/claim-checks/rewards-payouts/totals` | Aggregated totals grouped by currency                                | [View](https://fuul.readme.io/reference/get_v1-claim-checks-rewards-payouts-totals) |

{% hint style="info" %}
Use `status=claimed` or `status=unclaimed` to filter, and `from_date`/`to_date` for date ranges. Use `source_user_identifier` + `source_user_identifier_type` to filter by email or UUID users (mutually exclusive with deposit-address filters). Filter by payout type with `reason`: `affiliate_payout` | `end_user_payout` | `agency_payout`.
{% endhint %}

{% hint style="warning" %}
When `require_tax_info` is enabled on your project, `GET /v1/claim-checks/rewards-payouts` applies the tax gate **per row**: affiliate rows without approved tax info are returned with a restricted payload. The `/totals` endpoint is not affected by the tax gate.
{% endhint %}


# Public Claimable Rewards

The claimable rewards endpoint lets you display a user's unclaimed reward balances on any frontend — without requiring an API key or SDK initialization.

## When to use this vs the SDK

| Method                      | Auth required | Returns                         | Use case                                                                         |
| --------------------------- | ------------- | ------------------------------- | -------------------------------------------------------------------------------- |
| `GET /v1/claimable-rewards` | No            | Unclaimed balances per currency | Display pending rewards on a landing page, portfolio tracker, or DeFi aggregator |
| `Fuul.getClaimableChecks()` | Yes (API key) | Signed vouchers for claiming    | Build a claim button that submits an onchain transaction                         |

Use the public endpoint when you just want to **show** how much a user can claim. Use the SDK method when you need the actual **claim checks** to execute a transaction.

## Fetching claimable rewards

Call `GET /v1/claimable-rewards?user_identifier=0x1234...&user_identifier_type=evm_address`. The response includes unclaimed reward amounts grouped by currency, with token details (name, address, chain ID, decimals).

## Example: show rewards before wallet connect

A common pattern is to display claimable rewards on a landing page using just the user's address from the URL — before they connect a wallet or initialize the SDK:

```typescript
const params = new URLSearchParams(window.location.search);
const address = params.get('address');

if (address) {
  const res = await fetch(
    `https://api.fuul.xyz/api/v1/claimable-rewards?user_identifier=${address}&user_identifier_type=evm_address`
  );
  const data = await res.json();
  // Display unclaimed balances
}
```

Example response:

```json
[
  {
    "currency_name": "USDC",
    "currency_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "currency_chain_id": 1,
    "currency_decimals": 6,
    "amount": "500000000"
  }
]
```

{% hint style="info" %}
`amount` is in the token's smallest unit (e.g., 500000000 = 500 USDC with 6 decimals).
{% endhint %}

{% hint style="info" %}
This endpoint is public and requires no authentication. It's designed for protocol frontends, portfolio dashboards, and aggregator integrations.
{% endhint %}

## Next steps

Once the user is ready to claim, initialize the SDK and use the full [Claim Flow Integration](/developer-guide/claim-frontend) to fetch signed vouchers and submit the onchain transaction.

For full endpoint details, see the [API reference](https://fuul.readme.io/reference/get_v1-claimable-rewards).


# Claiming Onchain Rewards

This section covers how to claim onchain token rewards from the Fuul protocol. Start by [getting your claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) using `@fuul/sdk`, then follow the guide for your chain.

| Guide                                                                          | Networks                                                 | SDK Package        |
| ------------------------------------------------------------------------------ | -------------------------------------------------------- | ------------------ |
| [Get Claim Checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) | All                                                      | `@fuul/sdk`        |
| [EVM Claiming](/developer-guide/claiming-onchain-rewards/evm)                  | Ethereum, Arbitrum, Base, HyperEVM, Ink, Monad, Optimism | —                  |
| [SVM Claiming (Solana)](/developer-guide/claiming-onchain-rewards/svm-solana)  | Solana Mainnet, Devnet, Fogo Mainnet                     | `@fuul/sdk-solana` |

{% content-ref url="/pages/yPSQ3elABcPPAS74pOJu" %}
[Get Claim Checks](/developer-guide/claiming-onchain-rewards/get-claim-checks)
{% endcontent-ref %}

{% content-ref url="/pages/HG4bf9HRBEBXSjMYGSHC" %}
[EVM](/developer-guide/claiming-onchain-rewards/evm)
{% endcontent-ref %}

{% content-ref url="/pages/7J1PeSGPWJBzQjev098c" %}
[SVM (Solana)](/developer-guide/claiming-onchain-rewards/svm-solana)
{% endcontent-ref %}


# Get Claim Checks

Claim checks are signed vouchers that allow users to claim their rewards onchain. This step is the same regardless of whether you're claiming on EVM or SVM.

Use `Fuul.getClaimableChecks` from `@fuul/sdk`:

```typescript
import { Fuul } from '@fuul/sdk';

const claimChecks = await Fuul.getClaimableChecks({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address', // evm_address | solana_address | xrpl_address | sui_address | stellar_address | email
});
```

{% hint style="warning" %}
Not every identifier type can receive an onchain payout. A payout targeting a `stellar_address` fails with `UnsupportedIdentifierType`: Stellar is supported for identification and attribution only. See [Stellar signatures](/developer-guide/tracking-referrals-in-your-app#stellar-signatures).
{% endhint %}

Each claim check carries its own `id` (a uuid) alongside the voucher fields (`project_address`, `to`, `currency`, `currency_type`, `amount`, `reason`, `token_id`, `deadline`, `proof`, `signatures`). Use it to correlate a signed voucher with the rows returned by `getClaimChecks`, to deduplicate, or to reference a single check when closing.

You can also call the [claim check endpoint](https://fuul.readme.io/reference/generateclaimsignature) on the Fuul API directly.

{% hint style="info" %}
`getClaimableChecks` was introduced in SDK version **7.8.0**. Upgrade if you're on an earlier version. The per-check `id` was added in **7.44.0**.
{% endhint %}

## Get claim totals

To show users their claimed and unclaimed balances without building a full transaction ([API reference](https://fuul.readme.io/reference/getclaimabletotals)):

```typescript
import { Fuul } from '@fuul/sdk';

const totals = await Fuul.getClaimCheckTotals({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address', // or 'solana_address'
});
// Returns { claimed: [...], unclaimed: [...] } grouped by currency
```

## Claim check aggregation (open checks)

Some projects enable **claim check aggregation**, which batches multiple rewards into a single on-chain transaction per currency. This is optional and configured per project.

When aggregation is enabled, claim checks start with status `open` — they accumulate rewards over time without being signed yet. New rewards for the same user, currency, and reason are merged into the existing open check by summing amounts and extending deadlines. This reduces on-chain transactions from one per reward event to one per currency/reason combination.

To finalize an open check and make it claimable, call `POST /claim-checks/close`:

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.closeClaimChecks({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
});
```

`closeClaimChecks` returns the same `ClaimResponse` shape as `getClaimableChecks`, so the per-check `id` is available from either method. You can also pass `claim_check_ids` to close a specific subset instead of everything open.

After closing, the check transitions from `open` to `unclaimed` and receives its signature — at which point it's returned by `getClaimableChecks` and ready to claim onchain.

{% hint style="info" %}
If aggregation is not enabled for a project, claim checks are signed immediately at creation time and are returned as `unclaimed` from the start. No `close` call is needed.
{% endhint %}

## List claim checks by status

To inspect a user's checks without generating vouchers, use `getClaimChecks`. This is the SDK method behind `GET /claim-checks`:

```typescript
import { Fuul, ClaimCheckStatus } from '@fuul/sdk';

const { claim_checks } = await Fuul.getClaimChecks({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
  status: ClaimCheckStatus.Open, // optional — omit to return every status
});

claim_checks.forEach((check) => {
  console.log(`${check.id}: ${check.amount} ${check.currency_name} (${check.status})`);
});
```

| Status                                       | Meaning                                                                  |
| -------------------------------------------- | ------------------------------------------------------------------------ |
| `ClaimCheckStatus.Open` (`'open'`)           | Aggregating rewards, not signed yet. Call `closeClaimChecks` to finalize |
| `ClaimCheckStatus.Unclaimed` (`'unclaimed'`) | Signed and ready to claim onchain                                        |
| `ClaimCheckStatus.Claimed` (`'claimed'`)     | Already redeemed onchain                                                 |

Each row returns `id`, `currency_address`, `currency_chain_id`, `currency_name`, `currency_decimals`, `reason`, `amount`, `status`, `deadline_seconds`, and `created_at`. Note that these are metadata rows, not vouchers: they carry no `proof` or `signatures`. Use `getClaimableChecks` when you need to submit a transaction.

## Next steps

Once you have your claim checks, follow the guide for your chain:

* [EVM Claiming](/developer-guide/claiming-onchain-rewards/evm) for Ethereum, Arbitrum, Base, HyperEVM, Ink, Monad, Optimism
* [SVM Claiming (Solana)](/developer-guide/claiming-onchain-rewards/svm-solana) — for Solana Mainnet, Devnet, Fogo Mainnet


# EVM

This guide shows how to claim onchain token rewards from the Fuul protocol on EVM networks. The flow is: [get claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) from the API, read the fee from the contract, and submit a claim transaction.

{% hint style="info" %}
Before following this guide, make sure you've [fetched your claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) using `@fuul/sdk`.
{% endhint %}

## 1. Understand the ClaimCheck struct

The `claim` function on the smart contract accepts an array of `ClaimCheck` structs:

```solidity
struct ClaimCheck {
    address projectAddress;
    address to;
    address currency;
    IFuulProject.TokenType currencyType;
    uint256 amount;
    ClaimReason reason;
    uint256 tokenId;
    uint256 deadline;
    bytes32 proof;
    bytes[] signatures;
}
```

| Enum          | Values                                                 |
| ------------- | ------------------------------------------------------ |
| `TokenType`   | `0` = NATIVE, `1` = ERC20, `2` = ERC721, `3` = ERC1155 |
| `ClaimReason` | `0` = AFFILIATE\_PAYOUT, `1` = END\_USER\_PAYOUT       |

The `claimChecks` array can contain multiple elements belonging to different projects and currencies.

## 2. Read the claim fee

The protocol charges a small native token fee per claim. Read it dynamically from the `FuulFactory` contract — **never hardcode it**:

```typescript
const feesInfo = await publicClient.readContract({
  address: '0xa0080A60EE9f1985151161Fa6b09652Dc46afdEF', // FuulFactory
  abi: fuulFactoryAbi,
  functionName: 'getFeesInformation',
  args: [projectAddress],
});

const totalFee = feesInfo.nativeUserClaimFee * BigInt(claimChecks.length);
```

## 3. Submit the claim transaction

All claims go through the `FuulManager` contract. Here's a complete example using viem/wagmi:

```typescript
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';

const FUUL_MANAGER = '0x8a0836dA623ea1083c85acB958DeEa3716b39dc6';

// Transform API response to contract-ready format
const contractChecks = claimChecks.map((check) => ({
  projectAddress: check.project_address,
  to: check.to,
  currency: check.currency,
  currencyType: check.currency_type,
  amount: BigInt(check.amount),
  reason: check.reason,
  tokenId: BigInt(check.token_id),
  deadline: BigInt(check.deadline),
  proof: check.proof,
  signatures: check.signatures,
}));

// Submit with fee
writeContract({
  address: FUUL_MANAGER,
  abi: fuulManagerAbi,
  functionName: 'claim',
  args: [contractChecks],
  value: totalFee,
});
```

## Contract addresses

All supported networks use the same addresses:

| Contract        | Address                                      |
| --------------- | -------------------------------------------- |
| **FuulManager** | `0x8a0836dA623ea1083c85acB958DeEa3716b39dc6` |
| **FuulFactory** | `0xa0080A60EE9f1985151161Fa6b09652Dc46afdEF` |

| Network  | Chain ID |
| -------- | -------- |
| Ethereum | 1        |
| Arbitrum | 42161    |
| Base     | 8453     |
| HyperEVM | 999      |
| Ink      | 57073    |
| Monad    | 143      |
| Optimism | 10       |

{% hint style="warning" %}
Always read the claim fee from the contract using `getFeesInformation()`. The fee is configurable per project and may change.
{% endhint %}


# SVM (Solana)

This guide shows how to claim onchain token rewards from the Fuul protocol on Solana using the `@fuul/sdk-solana` package. The flow is: [get claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) from the API, initialize the Solana SDK, build claim instructions, and submit the transaction.

{% hint style="info" %}
Before following this guide, make sure you've [fetched your claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks) using `@fuul/sdk`.
{% endhint %}

## Prerequisites

| Requirement         | Version | Notes                                      |
| ------------------- | ------- | ------------------------------------------ |
| Node.js             | >= 18   | Required for native crypto support         |
| TypeScript          | >= 5.0  | Strict mode recommended                    |
| `@solana/web3.js`   | ^1.95.0 | Peer dependency                            |
| `@coral-xyz/anchor` | ^0.30.0 | Peer dependency                            |
| Wallet adapter      | Any     | `@solana/wallet-adapter-react` recommended |

```bash
npm install @fuul/sdk-solana @solana/web3.js @coral-xyz/anchor
```

## 1. Configure bundler (Next.js)

The SDK uses Node.js `Buffer` API. Configure webpack to provide it in the browser.

```typescript
// next.config.ts
import type { NextConfig } from 'next';
const webpack = require('webpack');

const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        net: false,
        tls: false,
        buffer: require.resolve('buffer/'),
      };
      config.plugins.push(
        new webpack.ProvidePlugin({
          Buffer: ['buffer', 'Buffer'],
        })
      );
    }
    return config;
  },
};

export default nextConfig;
```

{% hint style="info" %}
You also need to install the `buffer` package: `npm install buffer`.
{% endhint %}

## 2. Initialize the SDK

```typescript
'use client';

import { Connection } from '@solana/web3.js';
import { FuulSdk, Network } from '@fuul/sdk-solana';

const connection = new Connection('https://api.devnet.solana.com', {
  commitment: 'confirmed',
  confirmTransactionInitialTimeout: 60000,
});

const sdk = new FuulSdk(connection, Network.DEVNET);
```

{% hint style="warning" %}
The `Network` enum must match your RPC endpoint. Using `Network.DEVNET` with a mainnet RPC (or vice versa) will cause "Account does not exist" errors.
{% endhint %}

## 3. Fetch on-chain data

Before building a claim, fetch the project nonce and authorized signer from the chain:

```typescript
import * as anchor from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';

async function fetchClaimRequirements(sdk: FuulSdk, projectAddress: PublicKey) {
  const program = sdk.getProgram();

  // Fetch project account to get nonce
  const projectAccount = await program.account.project.fetch(projectAddress);
  const projectNonce = projectAccount.nonce as anchor.BN;

  // Fetch global config to get authorized signer
  const globalConfig = await sdk.getGlobalConfig();
  if (!globalConfig) {
    throw new Error('Global config not found');
  }

  const signers = globalConfig.rolesMapping.roles
    .filter((r) => Object.keys(r.role)[0] === 'signer')
    .map((r) => r.account);

  if (signers.length === 0) {
    throw new Error('No authorized signers found');
  }

  return { projectNonce, signer: signers[0], program };
}
```

## 4. Build claim instructions

Construct the claim message from the API response and get transaction instructions from the SDK:

```typescript
import {
  ClaimMessage,
  ClaimMessageData,
  MessageDomain,
  TokenType,
  ClaimReason,
} from '@fuul/sdk-solana';

async function buildClaimInstructions(
  sdk: FuulSdk,
  walletPublicKey: PublicKey,
  claim: {
    projectAddress: PublicKey;
    recipient: PublicKey;
    tokenMint: PublicKey;
    amount: bigint;
    deadline: number;
    reasonCode: number;
    proof: Uint8Array;
    signature: Uint8Array;
  }
) {
  const { projectNonce, signer, program } = await fetchClaimRequirements(
    sdk,
    claim.projectAddress
  );

  // Build message domain
  const domain = new MessageDomain({
    programId: program.programId,
    version: 1,
    deadline: BigInt(claim.deadline),
  });

  // Build claim data
  const claimData = new ClaimMessageData({
    amount: claim.amount,
    project: claim.projectAddress,
    recipient: claim.recipient,
    tokenType: TokenType.FungibleSpl,
    tokenMint: claim.tokenMint,
    proof: Buffer.from(claim.proof),
    reason:
      claim.reasonCode === 0
        ? ClaimReason.AffiliatePayout
        : ClaimReason.EndUserPayout,
  });

  const claimMessage = new ClaimMessage({ data: claimData, domain });

  // SDK handles ATA creation and PDA derivation internally
  const instructions = await sdk.claim({
    authority: walletPublicKey,
    projectNonce,
    message: claimMessage,
    signatures: [
      {
        signature: claim.signature,
        signer: signer,
      },
    ],
  });

  return instructions;
}
```

{% hint style="info" %}
The SDK resolves Associated Token Accounts (ATAs), PDA addresses, compute budget, and account metas internally. **Do not** create ATAs manually — this causes `IllegalOwner` errors.
{% endhint %}

## 5. Execute the transaction

```typescript
import { Transaction, Connection, PublicKey } from '@solana/web3.js';

async function executeClaim(
  connection: Connection,
  instructions: TransactionInstruction[],
  walletPublicKey: PublicKey,
  signTransaction: (tx: Transaction) => Promise<Transaction>
): Promise<string> {
  const transaction = new Transaction();
  transaction.add(...instructions);

  const { blockhash, lastValidBlockHeight } =
    await connection.getLatestBlockhash('confirmed');

  transaction.recentBlockhash = blockhash;
  transaction.lastValidBlockHeight = lastValidBlockHeight;
  transaction.feePayer = walletPublicKey;

  // Simulate first (recommended for debugging)
  const simulation = await connection.simulateTransaction(transaction);
  if (simulation.value.err) {
    console.error('Simulation failed:', simulation.value.logs);
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
  }

  // Sign and send
  const signedTx = await signTransaction(transaction);
  const signature = await connection.sendRawTransaction(signedTx.serialize(), {
    skipPreflight: false,
    preflightCommitment: 'confirmed',
  });

  // Confirm
  await connection.confirmTransaction(
    { signature, blockhash, lastValidBlockHeight },
    'confirmed'
  );

  return signature;
}
```

## 6. Complete Next.js example

```typescript
'use client';

import { useWallet } from '@solana/wallet-adapter-react';
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { FuulSdk, Network } from '@fuul/sdk-solana';
import { useState } from 'react';

export function ClaimButton({ claimData }: { claimData: ValidatedClaimCheck }) {
  const { publicKey, signTransaction } = useWallet();
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');

  async function handleClaim() {
    if (!publicKey || !signTransaction) {
      alert('Please connect your wallet');
      return;
    }

    setStatus('loading');

    try {
      const connection = new Connection(
        process.env.NEXT_PUBLIC_SOLANA_RPC_URL || 'https://api.devnet.solana.com',
        'confirmed'
      );

      const sdk = new FuulSdk(connection, Network.DEVNET);

      const instructions = await buildClaimInstructions(sdk, publicKey, claimData);
      const signature = await executeClaim(
        connection,
        instructions,
        publicKey,
        signTransaction
      );

      console.log('Claim successful:', signature);
      setStatus('success');
    } catch (error) {
      console.error('Claim failed:', error);
      setStatus('error');
    }
  }

  return (
    <button onClick={handleClaim} disabled={status === 'loading'}>
      {status === 'loading' ? 'Processing...' : 'Claim Rewards'}
    </button>
  );
}
```

## Key types

```typescript
enum Network {
  FOGO_MAINNET = 'fogo-mainnet',
  FOGO_TESTNET = 'fogo-testnet',
  MAINNET = 'mainnet',
  DEVNET = 'devnet',
  TESTNET = 'testnet',
  LOCALHOST = 'localhost',
}

enum TokenType {
  Native = 'native',
  FungibleSpl = 'fungibleSpl',
  NonFungibleSpl = 'nonFungibleSpl',
}

enum ClaimReason {
  AffiliatePayout = 'affiliatePayout',
  EndUserPayout = 'endUserPayout',
}

type Signature = {
  signature: Uint8Array;
  signer: PublicKey;
};
```

## What the SDK handles internally

Do **not** pass these manually — the SDK derives them:

| What                      | How the SDK derives it                       |
| ------------------------- | -------------------------------------------- |
| Program ID                | From `Network` enum via internal IDL mapping |
| Associated Token Accounts | Created inside `sdk.claim()` if needed       |
| PDA addresses             | Derived from seeds (project, config, etc.)   |
| Global config address     | Derived from program ID                      |
| Compute budget            | Determined by SDK                            |
| Account metas             | Built from instruction requirements          |

## Troubleshooting

| Symptom                         | Cause                                     | Fix                                                                |
| ------------------------------- | ----------------------------------------- | ------------------------------------------------------------------ |
| `Buffer is not defined`         | Missing polyfill in browser               | Add webpack `ProvidePlugin` for Buffer                             |
| `IllegalOwner`                  | Manual ATA creation when SDK handles it   | Remove manual `createAssociatedTokenAccountInstruction`            |
| `Account does not exist`        | Network mismatch (devnet vs mainnet)      | Ensure `Network` enum matches your RPC endpoint                    |
| `Invalid program id`            | Hardcoded program ID                      | Use `sdk.getProgram().programId`                                   |
| `Signature verification failed` | Wrong signer or corrupted signature bytes | Use signer from `sdk.getGlobalConfig()`, verify signature encoding |
| `window is not defined`         | Importing SDK in server component         | Add `'use client'` directive or dynamic import with `ssr: false`   |
| `Blockhash not found`           | Wrong commitment level                    | Use `'confirmed'` for both connection and transaction              |
| `ConstraintSeeds`               | Wrong PDA derivation                      | Let SDK derive PDAs — don't calculate manually                     |

{% hint style="warning" %}
Always simulate the transaction before signing with `connection.simulateTransaction()`. Check `simulation.value.logs` for detailed error messages.
{% endhint %}


# Airdrop Distributor

Fuul enables projects to run **airdrop distributions** after their Token Generation Event (TGE) in a way that is simple, secure, and effective. By leveraging the same platform that powers points campaigns and onchain payouts, projects can transition seamlessly into an airdrop without additional complexity.

## Benefits

| Benefit                   | Description                                                                                                                        |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Seamless transition**   | Projects already using Fuul for points can easily extend to their airdrop — audience and allocation logic are already in place     |
| **Sybil resistance**      | Fair participation by filtering out fake or duplicate accounts                                                                     |
| **Multi-network support** | Run airdrops across all networks supported by Fuul distributions                                                                   |
| **Staking incentives**    | Tie rewards to staking behavior — set penalties for claiming without staking or use tiered penalty systems based on staking period |
| **No-code & white-label** | Configure and brand the airdrop experience without technical resources                                                             |

## Step by step

1. **Run a points program on Fuul** (optional)\
   If a project already runs a points or onchain rewards program, those results make TGE allocations straightforward. Projects may also run an airdrop without a prior program.
2. **Open the Airdrop Distributor and deploy onchain**\
   Define token, network, offchain validation, and staking options, then deploy the airdrop smart contracts from the dashboard in under 5 minutes.
3. **Registration period** (optional)\
   Require participants to register before the claim opens. This confirms intent and filters out ineligible wallets. Registration can leverage external sybil detection tools and can be offered through a self-hosted or white-label page.
4. **Distribute via a branded claim page**\
   Use a self-hosted claim page or a Fuul-hosted page. Recipients verify eligibility and claim in a few clicks — payouts are executed onchain.

{% hint style="info" %}
For self-hosted claim pages, projects must integrate:

* The CSV with users and their claiming amounts
* The claiming call to the Airdrop Distributor contract
* The connection with the Airdrop Distributor contract subgraph to get claiming information
  {% endhint %}


# Proof of Humanity

During the registration period, projects can require participants to verify they are real humans before becoming eligible to claim. This adds a layer of sybil resistance on top of Fuul's built-in detection by linking each wallet to a unique, verified identity.

## How it works

1. User visits the registration page
2. User is prompted to verify their humanity via an external verification provider
3. Verification result is recorded on-chain or passed to Fuul
4. Only verified wallets are included in the eligible claiming set

{% hint style="info" %}
Proof of humanity verification happens during the **registration period** — before the claim window opens. Wallets that do not complete verification are excluded from claiming regardless of their allocation.
{% endhint %}

## Supported verification methods

| Method               | Description                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------- |
| **Worldcoin**        | Biometric verification using the Worldcoin iris scan — strongest guarantee of uniqueness |
| **Gitcoin Passport** | Aggregates multiple identity signals (social, onchain history) into a humanity score     |
| **Custom**           | Projects can integrate any external sybil detection tool and pass the result to Fuul     |

## Use cases

* Prevent bots and sybil accounts from claiming large airdrop allocations
* Comply with regulatory requirements that restrict distributions to verified individuals
* Increase community trust by showing that the airdrop reached real users

{% hint style="warning" %}
Proof of humanity verification is configured during deployment of the Airdrop Distributor contract. It cannot be added retroactively once the contract is deployed.
{% endhint %}


# Claim & Stake Penalties

Projects can tie airdrop distributions to staking behavior — requiring or incentivizing recipients to stake their claimed tokens. Penalties apply when users claim without staking, encouraging long-term token alignment over immediate selling.

## How it works

When claim & stake penalties are enabled, claiming tokens without staking them results in a reduced payout. The penalty can be a flat rate or tiered based on how long the user stakes.

**Example: flat penalty**

```
Full allocation:        1,000 tokens
Claiming without stake: 700 tokens  (30% penalty)
Penalty burned/returned: 300 tokens
```

**Example: tiered by staking period**

| Staking period | Penalty | User receives |
| -------------- | ------- | ------------- |
| No stake       | 50%     | 500 / 1,000   |
| 30 days        | 25%     | 750 / 1,000   |
| 90 days        | 10%     | 900 / 1,000   |
| 180 days       | 0%      | 1,000 / 1,000 |

## Configuration

Penalty settings are defined at deployment time in the Airdrop Distributor contract:

* **Penalty rate** — percentage deducted when claiming without staking
* **Staking contract** — the address where tokens must be staked
* **Staking tiers** — optional time-based tiers with decreasing penalty rates
* **Penalty destination** — burned, returned to treasury, or redistributed

{% hint style="info" %}
Penalties are enforced by the smart contract — they cannot be bypassed by the user once the contract is deployed.
{% endhint %}

## Use cases

* Align token recipients with long-term protocol success
* Reduce immediate sell pressure after TGE
* Reward committed community members with a higher effective allocation


# Audited Claiming Contract

The Fuul Airdrop Distributor runs on audited smart contracts. Projects deploy their own instance of the contract directly from the Fuul dashboard — no custom contract development required.

## Security

The Airdrop Distributor contract has been audited. The full audit report is publicly available:

{% embed url="<https://github.com/fuul-protocol/protocol-contracts-v2/blob/main/audits/Fuul_Protocol_audit_30_12_2025.pdf.md>" %}

## Contract addresses

The Airdrop Distributor is deployed per-project at launch time. The underlying factory and manager contracts are shared across all projects:

| Contract        | Address                                      |
| --------------- | -------------------------------------------- |
| **FuulManager** | `0x8a0836dA623ea1083c85acB958DeEa3716b39dc6` |
| **FuulFactory** | `0xa0080A60EE9f1985151161Fa6b09652Dc46afdEF` |

**Supported networks:**

| Network  | Chain ID |
| -------- | -------- |
| Ethereum | 1        |
| Arbitrum | 42161    |
| Base     | 8453     |
| HyperEVM | 999      |
| Ink      | 57073    |
| Monad    | 143      |
| Optimism | 10       |

## Contract repository

{% embed url="<https://github.com/fuul-protocol/protocol-contracts-v2>" %}

{% hint style="info" %}
Projects deploy their own airdrop contract instance through the Fuul dashboard. The deployed contract is a project-specific instance of the audited factory — no manual contract deployment is required.
{% endhint %}


# Claiming Portal

The claiming portal is the interface through which airdrop recipients verify their eligibility and claim their tokens. Fuul supports two options: a Fuul-hosted portal with zero setup, or a self-hosted page with full branding control.

## Fuul-hosted portal

The fastest way to launch. Fuul generates a branded claiming page for your project — recipients visit the URL, connect their wallet, verify eligibility, and claim in a few clicks.

**Setup:** configured directly in the Fuul dashboard when deploying the Airdrop Distributor. No development work required.

## Self-hosted portal

For projects that want full control over the claiming experience — custom design, custom domain, additional steps (e.g., registration forms, sybil checks).

To build a self-hosted claiming page, integrate three components:

| Component                  | Purpose                                                                           |
| -------------------------- | --------------------------------------------------------------------------------- |
| **User allocations CSV**   | The list of eligible wallets and their claiming amounts                           |
| **Claiming contract call** | Transaction to the Airdrop Distributor contract that transfers tokens to the user |
| **Airdrop subgraph**       | Query to check which addresses have already claimed, and how much remains         |

{% hint style="info" %}
The [EVM Claiming guide](/developer-guide/claiming-onchain-rewards/evm) covers how to build the contract interaction. The subgraph endpoint for your deployment is available in the Fuul dashboard after deploying the contract.
{% endhint %}

## Claiming flow

Regardless of which option you use, the user experience follows the same steps:

1. User connects wallet
2. Portal checks eligibility against the allocation list
3. If eligible, user sees their allocation and a claim button
4. User signs and submits the claim transaction
5. Tokens are transferred onchain to their wallet

{% hint style="warning" %}
If claim & stake penalties are enabled, the portal must show the penalty terms clearly before the user submits — including how much they will receive based on their staking choice.
{% endhint %}


# Managing Audiences

Programs can set **audiences** for different tier payouts or allowlists. Each audience can have entries that are:

| Type        | Description                                           |
| ----------- | ----------------------------------------------------- |
| **Static**  | Entered manually or via API                           |
| **Dynamic** | Users that matched a specific condition automatically |

## Getting user audiences

Use `getUserAudiences` to check which audiences a user belongs to:

```typescript
import { Fuul } from '@fuul/sdk';

await Fuul.getUserAudiences({
  user_identifier: '0x1234...',
  user_identifier_type: 'evm_address',
});
```

## Updating audiences via API

Audience write operations (add, remove, batch, badges) are HTTP-only. The SDK exposes `getUserAudiences` only; all other audience management is done via the Fuul API:

| Action               | API endpoint                                                        | Reference                                                                   |
| -------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Get user's audiences | `GET /v1/audiences/audience-segments/user`                          | [View](https://fuul.readme.io/reference/getaudiencesegments)                |
| List all segments    | `GET /v1/audiences/audience-segments`                               | [View](https://fuul.readme.io/reference/get_v1-audiences-audience-segments) |
| Get segment entries  | `GET /v1/audience-segments/{segmentId}/entries`                     | [View](https://fuul.readme.io/reference/getsegmententries)                  |
| Add entries (batch)  | `POST /v1/audience-segments/{segmentId}/entries/batch`              | [View](https://fuul.readme.io/reference/addsegmententriesbatch)             |
| Remove entry         | `DELETE /v1/audience-segments/{segmentId}/entries/{userIdentifier}` | [View](https://fuul.readme.io/reference/removesegmententry)                 |
| Create badge         | `POST /v1/audience-segments/{segmentId}/badge`                      | [View](https://fuul.readme.io/reference/uploadsegmentbadge)                 |
| Update badge         | `PATCH /v1/audience-segments/{segmentId}/badge`                     | [View](https://fuul.readme.io/reference/updatesegmentbadge)                 |
| Delete badge         | `DELETE /v1/audience-segments/{segmentId}/badge`                    | [View](https://fuul.readme.io/reference/deletesegmentbadge)                 |

### Example: add users to an audience

```bash
curl -X POST https://api.fuul.xyz/api/v1/audience-segments/{segmentId}/entries/batch \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-service-role-key" \
  -d '{
    "entries": [
      { "identifier": "0x1234...", "identifier_type": "evm_address" },
      { "identifier": "0x5678...", "identifier_type": "evm_address" }
    ]
  }'
```

{% hint style="info" %}
A **service\_role** API key is required for audience management endpoints.
{% endhint %}

{% hint style="warning" %}
**Dashboard add-entry behavior:** The dashboard uses a dedicated endpoint (`POST /api/v1/projects/:projectId/audiences/:audienceId/entries`) that returns **409 Conflict** when the user is already in the audience. This prevents silent duplicates. The public batch endpoint (`POST /api/v1/audience-segments/:audienceId/entries/batch`) keeps its upsert/dedup semantics — duplicates are silently skipped, which is the expected behavior for programmatic bulk imports.
{% endhint %}


# Terms & Conditions

Projects can require users to accept a terms document before participating in a program. Fuul records the acceptance once per wallet and makes it readable from any surface holding a project API key, so a user who already accepted in your app is not prompted again in the affiliate portal.

{% hint style="info" %}
This feature is configured by Fuul, not from the dashboard. Reach out at <ecosystem@fuul.xyz> to enable it for your project and set your terms document URL. Until it is enabled, `GET /status` reports `terms_acceptance_required: false` and `POST /accept` returns `400`.
{% endhint %}

## How it works

1. Your frontend calls `GET /status` for the connected wallet
2. If `terms_acceptance_required` is `true` and `terms_accepted` is `false`, you show the document at `terms_document_url` and ask the user to accept
3. Your backend calls `POST /accept`, declaring which surface collected the acceptance
4. Every other surface reading `/status` for that wallet now sees `terms_accepted: true`

The record attests that a system asserted the acceptance. Neither endpoint carries a user session, and nothing cryptographically binds the wallet to the request, so this is not a signature scheme. If you need proof of possession, verify the wallet yourself before calling `/accept`.

## Endpoints

Base path: `/api/v1/project-terms-conditions`

| Endpoint  | Method | Required scope                             | Description                                                     |
| --------- | ------ | ------------------------------------------ | --------------------------------------------------------------- |
| `/accept` | POST   | `terms_conditions:write`                   | Records an acceptance. Returns `201`                            |
| `/status` | GET    | `terms_conditions:write` or `service_role` | Returns the project's config plus one wallet's acceptance state |

Both endpoints return the same response shape, so an accept never needs a follow-up read.

### Request fields

| Field                  | Where       | Description                                                           |
| ---------------------- | ----------- | --------------------------------------------------------------------- |
| `user_identifier`      | Both        | The wallet address                                                    |
| `user_identifier_type` | Both        | `evm_address`, `solana_address`, or `xrpl_address`                    |
| `source`               | Accept only | `partner_site`, `affiliates_portal`, or `other`. Required, no default |

{% hint style="warning" %}
The accepted identifier types are **narrower than the platform's full list**. Only `evm_address`, `solana_address`, and `xrpl_address` are valid here. `sui_address` is rejected with a `400` even though it appears in identifier type lists elsewhere in these docs, and `email` / `uuid` are rejected as well: an acceptance is anchored to a wallet.
{% endhint %}

### Response

```json
{
  "terms_acceptance_required": true,
  "terms_document_url": "https://yourproject.com/terms-v2.pdf",
  "terms_accepted": true,
  "accepted_at": "2026-07-23T13:52:18.000Z"
}
```

| Field                       | Description                                                                   |
| --------------------------- | ----------------------------------------------------------------------------- |
| `terms_acceptance_required` | Whether the gate is on for this project                                       |
| `terms_document_url`        | The document currently in force. Only present when the gate is on             |
| `terms_accepted`            | Whether this wallet has already accepted                                      |
| `accepted_at`               | When the acceptance was recorded, on the server clock. `null` if not accepted |

## Example

Check status before showing your program UI:

```typescript
const res = await fetch(
  `https://api.fuul.xyz/api/v1/project-terms-conditions/status?user_identifier=${address}&user_identifier_type=evm_address`,
  { headers: { Authorization: `Bearer ${apiKey}` } }
);

const status = await res.json();

if (status.terms_acceptance_required && !status.terms_accepted) {
  showTermsModal(status.terms_document_url);
}
```

Record the acceptance from your backend:

```bash
curl -X POST https://api.fuul.xyz/api/v1/project-terms-conditions/accept \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "user_identifier": "0x1234...",
    "user_identifier_type": "evm_address",
    "source": "partner_site"
  }'
```

## Idempotency

Acceptance is unique per `(project, wallet, identifier type)`. Calling `/accept` again for a wallet that already accepted returns `201` and leaves the original record untouched: `source`, the document URL, and `accepted_at` stay frozen to whichever surface accepted first.

This means you can call `/accept` without checking `/status` first, and retries are safe.

## What acceptance does and does not cover

|                          | Behavior                                                                                                                                                        |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Scope**                | One general acceptance per wallet, not per document revision. There is no version field                                                                         |
| **Document changes**     | Each record snapshots the document URL that was live when the user accepted. Publish every revision under a new immutable URL so that snapshot stays meaningful |
| **Re-prompting**         | Changing your document text does **not** re-prompt users who already accepted                                                                                   |
| **Turning the gate off** | Existing records are kept and become inert, not deleted. Turning the gate back on does not re-prompt anyone who already accepted                                |

{% hint style="warning" %}
If you overwrite your terms document at the same URL instead of publishing a new one, the stored records will point at a document whose contents have changed since users accepted it. Use a versioned path such as `/terms-v2.pdf`.
{% endhint %}

## Related

* Collecting tax forms from affiliates is a separate flow with its own gate → [Tax Information](/core-concepts/affiliates/tax-information)
* Programs that require users to accept individual payouts before claiming → [Claim Flow Integration](/developer-guide/claim-frontend)




---

[Next Page](/llms-full.txt/1)

