Customization Options
Personalize reward distribution to make your campaign unique
Merkl campaigns are highly configurable. Beyond the default reward logic, you can layer in eligibility filters, access controls, boosts, privacy modes, and advanced flows like referrals or raffles to shape exactly who earns what.
The most common options can be enabled directly in Merkl Studio at the Personalize step (you can simulate a campaign creation to explore the UI without actually launching one).
If your use case requires something not exposed in the UI — for example, whitelisting based on a custom onchain condition, restricting forwarding to specific protocols, or wiring a custom boost API — reach out to the Merkl team and we'll help wire it in.

Below is a non-exhaustive overview of what's possible, with examples.
Eligibility
Blacklisting
You can exclude specific addresses from receiving rewards. This is typically used to filter out exploiters, sanctioned wallets, internal team addresses, or contracts you don't want to incentivize.

If a forwarder is blacklisted, all users behind it become ineligible too.
Example — blacklisting a staking contract:
- A user holds 10 USDA but has staked 6 USDA in a blacklisted staking contract (a forwarder).
- Only the 4 USDA in the user's wallet qualifies for rewards.
Whitelisting
You can restrict rewards to a specific set of addresses — individual users, forwarders, or a mix.
Example — Uniswap V3 with three Automated Liquidity Managers acting as forwarders:
- The campaign creator whitelists two of the three managers and one direct user.
- Only liquidity provided through those two managers, plus the whitelisted user's direct position, earn rewards.
- Among whitelisted addresses, rewards are still split proportionally to liquidity share.
Whitelisting overrides blacklisting. As soon as a whitelist is set, every address not on it is implicitly excluded.
Multiple campaigns can run on the same opportunity (e.g. a Uniswap v4 ETH-USDC pool) with different eligibility rules. Two users in the same pool may earn from different campaigns depending on whose whitelist they're on. Users should check campaign details on the opportunity page to confirm what they're eligible for.
Dynamic whitelist & blacklist via the Key-Value Store API
A campaign's whitelist and blacklist can always be updated by overriding the campaign onchain. For lists that change frequently — large allowlists, ongoing KYC additions, exploit-driven blacklists — onchain updates are inconvenient.
Merkl exposes a Key-Value Store API that lets you mutate eligibility lists offchain. Each store has a unique key, a type (WHITELIST or BLACKLIST), and an access mode. Once the campaign is wired to reference a store, the engine reads its current contents on every reward computation, so changes propagate within roughly two hours.
Setup (one-time, handled by the Merkl team):
- Merkl provisions a store for you with a chosen
key,type, andAPI_KEYaccess mode, and links it to your campaign's hook. - You receive the store key and an
X-API-Keycredential.
Updating entries — PUT /v4/key-value-stores/{key}/entries/batch
Headers:
X-API-Key: your-api-key
Content-Type: application/json
Body (up to 1000 entries per request):
{
"entries": [
{
"address": "0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701",
"value": "{\"isBlacklisted\":true}"
},
{
"address": "0xeaC6A75e19beB1283352d24c0311De865a867DAB",
"value": "{\"isBlacklisted\":true}"
}
]
}
The endpoint is an upsert: existing entries are overwritten, new ones are inserted. To remove an address from the list, call DELETE /v4/key-value-stores/{key}/entries/{address}.
The value is a JSON string whose shape depends on the store type:
BLACKLIST→{"isBlacklisted": true}WHITELIST→{"isWhitelisted": true}
If the same campaign is wired to both a whitelist and a blacklist store, whitelist precedence still applies: as soon as the whitelist contains at least one entry, every other address is implicitly excluded.
This is the recommended pattern for private LP deals: as whitelisted addresses don't appear in the Merkl app.
Protocol whitelist / blacklist
Because reward forwarding can route a single campaign's rewards across many downstream protocols, you can also restrict eligibility at the protocol level — only forward to (or exclude) positions held in specific protocols, rather than enumerating addresses.
Access Control
You can gate eligibility on arbitrary onchain conditions or events. Multiple access controls can be combined within the same campaign.
View functions
Almost any onchain condition exposed via a view function can be plugged into a campaign's eligibility check. The examples below are commonly used and ready to wire in.
Token snapshot
Reward only users who hold at least a minimum amount of a given token at a specific snapshot block.
OFAC compliance
Filter out addresses flagged by the U.S. Office of Foreign Assets Control by pointing the campaign at a sanctions registry contract such as the Chainalysis Sanctions Oracle on Ethereum.
World ID
Restrict participation to humans verified via World ID to filter out bots and Sybil farms.
Health factor
On lending protocols like Aave, you can require a health factor below a chosen threshold so that only addresses with active borrows qualify.
Onchain events
Eligibility can also be derived from indexed onchain events. The engine builds a set of qualifying addresses from the events you care about and intersects it with the campaign's recipients.
Smart accounts
Many smart-wallet factories emit a creation event when a user deploys their account. Indexing those events lets you run a campaign that only rewards users on a specific smart-wallet stack.
Token holding (duration-gated)
You can require recipients to hold a minimum amount of a given token (which can differ from the reward token) for a minimum duration before becoming eligible.
Example — campaign requires 500 stUSD held for 30 days:
- User holding 600 stUSD for 44 days → eligible.
- User holding 400 stUSD for 60 days → not eligible (fails amount).
- User holding 800 stUSD for 12 days → not eligible (fails duration).
This is useful for steering rewards toward long-term holders rather than short-term flippers.
Privacy
You can launch private campaigns where eligibility rules and configuration are hidden from public view. Only the campaign creator and explicitly authorized addresses can see the details.
Common use cases:
- Confidential airdrops where the eligibility criteria themselves are sensitive.
- Private payments and settlements.
- Strategic incentive programs with undisclosed terms.
Because all Merkl rewards are aggregated in the Distributor contract and processed alongside high transaction volume, it's computationally infeasible to correlate a specific deposit with an individual claim — providing strong privacy guarantees.
You can let recipients see their own allocation, or restrict visibility entirely to the creator and authorized addresses. See the Private Campaigns guide for setup details.
Boosts
Token / NFT boost
You can boost rewards for users who hold a specific token or NFT — similar to Curve's vote-escrowed model, but more flexible (no hard 2.5× cap).
Boost formula:
Where:
- B: boost multiplier applied to the user's rewards.
- b: custom boost factor chosen by the campaign creator.
- R: total rewards per epoch.
- r: user's rewards per epoch.
- V: total supply of the boost token / NFT.
- v: user's holdings of the boost token / NFT.
Example: to achieve a 10% boost, set the boost multiplier to 1.1.
NFT-based boosting is also supported — reach out to the Merkl team to set it up.
API boost
For dynamic boosts that depend on data Merkl can't see directly (offchain scoring, KYC tiers, partner-controlled allowlists), you can expose an HTTP endpoint that the Merkl engine calls during reward computation.
Four boost composition modes are available:
| Function | Formula |
|---|---|
MULTIPLY | amount = amount × boost |
MULTIPLY_WITH_OFFSET | amount = amount × (1 + boost) |
ADD | amount = amount + boost |
REPLACE | amount = boost |
Implementation
The customization option takes the following parameters:
url— endpoint the engine will POST to.boostingFunction—MULTIPLY,MULTIPLY_WITH_OFFSET,ADD, orREPLACE.sendScores— whether to include each address's current campaign score in the request.defaultBoost— fallback behavior when an eligible address is missing from your response. Either:ZERO_ADDRESS: use the boost value you returned for0x000…000as the default. Since the zero address is always excluded from reward computation, it's safe to repurpose. You must include an entry for the zero address when using this mode.ERROR: abort the campaign computation if any eligible address is missing from the response.
Request body sent by the engine:
// sendScores = true
let body: { address: string; score: string }[]; // checksummed addresses
// sendScores = false
let body: { addresses: string[] }; // checksummed addresses
The engine first POSTs the full address list. If your endpoint fails or times out, it halves the payload and retries — recursively — until requests succeed. To keep this efficient, support at least 250 addresses per request.
Expected response shape:
const data: {
address: string;
boost: string; // bigint in BASE 9 — e.g. 1× = "1000000000"
}[];
Endpoint requirements:
- Always return valid JSON in the format above — a malformed response aborts reward computation.
- Don't include duplicate addresses — only the first boost value is used.
- Boost values are in base 9:
1×is"1000000000",2×is"2000000000",0.5×is"500000000".
Dynamic whitelist via API boost
You can implement a whitelist that grows over time without redeploying the campaign. To do so:
- Use
MULTIPLYas the boosting function. - Return
"1000000000"(1× in base 9) for whitelisted addresses. - Return
"0"for the zero address withdefaultBoost = ZERO_ADDRESS— every non-whitelisted address inherits a 0× boost. - Make sure addresses are checksummed.
[
{ "address": "0x1234567890AbcdEF1234567890aBcdef12345678", "boost": "1000000000" },
{ "address": "0xabcdef1234567890abcdef1234567890abcdef1234", "boost": "1000000000" },
{ "address": "0x0000000000000000000000000000000000000000", "boost": "0" }
]
Dynamic blacklist via API boost
Mirror of the above — keep a mutable blacklist without recreating the campaign:
- Use
MULTIPLYas the boosting function. - Return
"0"for blacklisted addresses. - Return
"1000000000"(1×) for the zero address withdefaultBoost = ZERO_ADDRESS— every non-blacklisted address inherits a 1× boost.
[
{ "address": "0x1234567890AbcdEF1234567890aBcdef12345678", "boost": "0" },
{ "address": "0xabcdef1234567890abcdef1234567890abcdef1234", "boost": "0" },
{ "address": "0x0000000000000000000000000000000000000000", "boost": "1000000000" }
]
Advanced Logic
Bridge liquidity tracking
Campaigns can be restricted to users who bridged liquidity from another chain before participating, so that only genuinely new cross-chain liquidity is rewarded — not movements within the same chain.
Currently supported with Jumper, but any bridge can be wired in on request.
Referral programs
You can run an onchain referral program on top of a campaign — rewarding both the referrer and the invitee, with optional whitelist gates and customizable splits.
Highlights:
- Unlimited programs — run as many as you need in parallel.
- Onchain referral codes — users generate unique codes to share.
- White-label integration — embed the flow in your own frontend (contact Merkl).
- Cross-protocol — works on any protocol or behavior already supported by Merkl.
- Customizable splits — distribute rewards across users, referrers, invitees, or even non-participating addresses.
- Participation gating — optional whitelist, optional fee to mint a referral code.
- Onchain confirmation — users sign a transaction to commit their referral, preventing spoofing.
Reach out to the Merkl team for help wiring up a referral program.
Raffle
Instead of distributing rewards proportionally, you can run a raffle that picks a set of lucky winners.
Customization
- Frequency — daily, weekly, or any cadence you choose.
- Number of winners — one grand prize, or any number of winners per draw.
- Selection method:
- Equal odds — every participant has the same chance.
- Whales first — chances are weighted by campaign score.
- Parallel raffles — run multiple draws simultaneously, each with its own rules.
Reproducibility
Seed. The seed is the block hash of the first block after the timestamp set by the campaign creator.
Winning numbers. Generated using the XORShift128Plus PRNG, seeded with the block hash above. The step equals the minimum user score divided by 1000 (or 1 when score isn't used).
function getSelectedNumbers(
seed: number,
numberOfWinners: number,
totalScore: number,
step: number,
): number[] {
const random = new XORShift128Plus(seed)
const selectedNumbers = []
for (let i = 0; i < numberOfWinners; i++) {
selectedNumbers.push(random.randBelow(totalScore / step) * step)
}
return selectedNumbers
}
Picking winners. Each generated number maps to a cumulative-weight range across the participant list (sorted by address). The participant whose range contains the number wins.
For snapshots and airdrops, the participant list is known up front. For dynamic campaigns, a common trick is to first run the same campaign with the same parameters but without the raffle option and a small budget — the resulting list of recipients then becomes your input set for the raffle.
Worked example — 5 users, weighted raffle, 1 winner
| User | Weight |
|---|---|
| A | 10 |
| B | 20 |
| C | 30 |
| D | 40 |
| E | 50 |
1. Total weight: 10 + 20 + 30 + 40 + 50 = 150.
2. Random number: the PRNG draws a number in [0, 150). Suppose it picks 107.
3. Cumulative ranges:
| User | Range |
|---|---|
| A | 0–10 |
| B | 10–30 |
| C | 30–60 |
| D | 60–100 |
| E | 100–150 |
4. Winner: 107 falls in 100–150 → User E wins.