Merkl Developer Portal logo
post

/v4/confidential-rewards/zama

Start a Zama encryption job

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 one Merkl-fixed
address as both the consuming contract and the submitting msg.sender, alongside the chain ID
and the chain's fhEVM ACL address. Both are echoed back as contractAddress and userAddress,
so you can always see what your proofs are bound to. The address is currently a placeholder, so
proofs are structurally valid but NOT redeemable on-chain — they exercise the pipeline only. 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. Overflow returns 422 with a count, never
a recipient or an amount, and 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 is a whole-campaign total, 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.

Query parameters

pagestring (integer) | integerdefault: 0

0-indexed page number

itemsstring (integer) | integerdefault: 32

Number of items returned by page

chainIdnumber | string
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.

fheChainIdnumber | string

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.

fheTypeenumdefault: euint64

FHE type to encrypt amounts into. euint64 is what ERC-7984 confidential tokens use and is the default; it caps a single amount at 2^64-1, so raw 18-decimal wei amounts above ~18.44 tokens need amountDecimals or a wider type.

amountDecimalsnumber | string

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.

Responses

200
Response for status 200
Schema
object
jobIdstring
required
Job ID to poll for the encrypted leaderboard
Example
{
  "jobId": "string"
}