Rewards

Start a Zama encryption job

Changed on

Starts a background job that FHE-encrypts a campaign's leaderboard page and returns a job ID immediately. Poll GET /v4/confidential-rewards/zama/jobs/{jobId}; once status is completed, result holds the response described below. Encryption is too slow for one request, which is why it is a job.

The job ID is a hash of the request, so repeating a request joins the running or finished job instead of encrypting twice. A failed job is retried. Job state lives in Redis for 30 minutes.

The job's result is a campaign's leaderboard with every reward amount FHE-encrypted for Zama's confidential distribution: (recipient, encryptedAmount, inputProof) triples ready for FHE.fromExternal. Merkl performs the encryption, so the distributor never receives a cleartext amount.

Only amounts are confidential. Recipient addresses are returned in cleartext — the distributor must know who to pay. The participant set and count are therefore visible.

The proof binding is fixed by this route, not parameters. Every proof binds the campaign's reward-airdrop contract as both the consuming contract and the submitting msg.sender, alongside the chain ID and the chain's fhEVM ACL address. That address is the CREATE2 clone Zama's airdrop factory deploys for the onchain campaign ID, so it is known before deployment. Both are echoed back as contractAddress and userAddress. A proof cannot be rebound afterwards; it has to be re-encrypted.

One proof covers several recipients — recipientsPerProof (32 for euint64) entries share an inputProof, grouped by proofId. Submit a group together, or pass the same proof in each of several transactions; both verify.

Amounts must fit the FHE type. euint64 (the ERC-7984 type, and the default) caps one amount at 2^64-1, which is only ~18.44 tokens of an 18-decimal reward. Use amountDecimals to request a coarser denomination, or a wider fheType. The check covers the whole campaign, not the page: one overflowing amount fails every page with a 422 that carries a campaign-wide count, never a recipient or an amount. It never wraps.

Amounts are settled committed rewards, aggregated per recipient across reasons. Uncommitted engine state (TempLeaves) is excluded.

Private campaigns are refused. Campaigns that hide their leaderboard ARE served, to any caller with access to the campaign: the recipient list and count are cleartext by design, and no field reduces to one recipient's amount. truncatedDust, excludedZeroAmount and the overflow check are whole-campaign, never per page.

Results are ordered by recipient address, which is independent of the amounts — an amount-ordered response would reveal the full ranking even though every amount is encrypted. Not configurable, and it makes paging repeatable.

post/v4/confidential-rewards/zama

Request

  • Base URL: https://api.merkl.xyz (the document declares no server; this is the origin it was published from)
  • URL: https://api.merkl.xyz/v4/confidential-rewards/zama
  • Auth: apiKey (a scheme the document does not define)

Query parameters

string integer
OR
integer

0-indexed page number

string integer
OR
integer

Number of items returned by page

string numeric required
OR
number required

Distribution chain ID of the Merkl campaign (numeric, e.g. 1 for Ethereum).

campaignIdstring required

Onchain campaign ID (32-byte hex hash, e.g. 0x93cf385c...). NOT the internal Campaign.id.

string numeric
OR
number

Chain the encrypted inputs will be consumed on, if different from chainId. The proof binds this chain's ID and fhEVM ACL address, so it must match the chain the distributor is deployed on. Defaults to chainId.

string
OR
string
OR
string
string numeric
OR
number

Denomination to express amounts in, as a number of decimals. Omit for raw reward-token wei. Set below the reward token's own decimals to rescale (e.g. 6 on an 18-decimal token divides by 1e12) so amounts fit euint64. Rescaling truncates: the response reports the exact dust dropped.

hideboolean

If true, excludes the campaign creator, the Merkl multisig and the Merkl dumper.

Response

Response for status 200

jobIdstring required

Job ID to poll for the encrypted leaderboard

Changes