This repository was archived by the owner on Jan 20, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
docs: update documentation for new features #137
Open
alexis-merkl
wants to merge
3
commits into
main
Choose a base branch
from
feature/smart-contract-operators-claim-features
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,15 +78,30 @@ You can claim all your rewards per chain at once to optimize gas costs! | |
|
|
||
| Rewards on Merkl do not increase block by block, but can be claimed at a frequency which depends on the chain. You can check the claim frequency on the [Status page](https://app.merkl.xyz/status). | ||
|
|
||
| Note that, by default, rewards can only be claimed by the address that earned them. You can however approve an operator to claim on your behalf by calling the function `toggleOperator` on the [distributor smart contract](https://app.merkl.xyz/status). However, rewards will still be sent to the original address that earned them. | ||
| ### Operator System: Delegating Claim Rights | ||
|
|
||
| So to sum up, assuming Alice earned the rewards: | ||
| By default, rewards can only be claimed by the address that earned them. However, Merkl provides a flexible operator system that allows you to delegate the right to claim rewards to another address, while still receiving the rewards yourself. | ||
|
|
||
| * by default only Alice can claim and rewards are sent to Alice. | ||
| * by calling `toggleOperator`, Alice can allow Bob to claim on her behalf. Then, Bob can claim for Alice by sending Alice's proof to the contract, and rewards are then sent to Alice. | ||
| **How regular operators work:** | ||
|
|
||
| You can approve an operator to claim on your behalf by calling the function `toggleOperator` on the [distributor smart contract](https://app.merkl.xyz/status). When an operator claims on your behalf, the rewards are still sent to your original address—the operator only facilitates the transaction. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add in this section that if you toggleOperator address(0) then anyone can claim on your behalf
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be even more explicit in the last sentence (+ I would remove the em dash): "the rewards are still sent to your original address. The operator’s role is limited to sign the claim transaction." |
||
|
|
||
| If you call `toggleOperator` with `address(0)`, then anyone can claim on your behalf without requiring individual operator approvals. | ||
|
|
||
| **Example:** | ||
|
|
||
| Assuming Alice earned the rewards: | ||
|
|
||
| - **Default behavior**: Only Alice can claim, and rewards are sent to Alice. | ||
| - **With operator**: By calling `toggleOperator`, Alice can allow Bob to claim on her behalf. Bob can then claim for Alice by submitting Alice's proof to the contract, and rewards are still sent to Alice's address. | ||
| - **With address(0)**: By calling `toggleOperator(address(0))`, Alice allows anyone to claim on her behalf. Any address can then claim for Alice by submitting Alice's proof to the contract, and rewards are still sent to Alice's address. | ||
|
|
||
| If you can't call `toggleOperator` and are stuck, please [open a tech ticket in our Discord ](https://discord.com/channels/1209830388726243369/1210212731047776357), the team may be able to call it on your behalf. | ||
|
|
||
| **Admin rights to claim for everyone:** | ||
|
|
||
| In some cases, certain addresses may be granted admin rights that allow them to claim rewards on behalf of all users without requiring individual operator approvals. These admin rights are granted on a case-by-case basis and enable streamlined reward distribution for specific use cases, such as protocol-level reward forwarding or automated claim processes. | ||
|
|
||
| ### Claiming from a multisig | ||
|
|
||
| When claiming rewards from a multisig address, we recommend delegating the claim process to an operator. This is because Merkl reward proofs are only valid for on average four hours (varies depending on the chain). If you fail to gather the necessary signatures and execute the claim transaction before a Merkle root is updated, your claim transaction will fail. | ||
|
|
@@ -108,6 +123,179 @@ To structure the claim: | |
|
|
||
| <figure><img src=".gitbook/assets/DistributorClaim.png" alt=""><figcaption><p>Claiming Merkl rewards using a block explorer</p></figcaption></figure> | ||
|
|
||
| ## Claim Recipient System | ||
|
|
||
| The claim recipient system allows you to redirect where claimed rewards are sent, instead of always sending to the user who earned them. | ||
|
|
||
| ### Function: `setClaimRecipient()` | ||
|
|
||
| **Signature:** | ||
| ```solidity | ||
| function setClaimRecipient(address recipient, address token) external | ||
| ``` | ||
|
|
||
| **Parameters:** | ||
|
|
||
| - `recipient`: Address that will receive the claimed tokens. Use `address(0)` to remove the custom recipient (revert to default) | ||
| - `token`: Token for which to set the recipient. Use `address(0)` to set a **global recipient** that applies to all tokens without a specific recipient | ||
|
|
||
| **Access:** Callable by any user (sets recipient for `msg.sender`) | ||
|
|
||
| **How it works:** | ||
|
|
||
| - Sets a custom recipient for your own claims | ||
| - Setting `recipient = address(0)` removes the custom recipient for that token | ||
| - When `token = address(0)`, it sets `claimRecipient[user][address(0)]` as a global fallback | ||
|
|
||
| **Priority order when claiming:** | ||
|
|
||
| When claiming rewards, the system follows this priority order: | ||
|
|
||
| 1. **Token-specific recipient**: `claimRecipient[user][token]` (if set and not `address(0)`) | ||
| 2. **Global recipient**: `claimRecipient[user][address(0)]` (if set) | ||
| 3. **Default**: User's own address | ||
|
|
||
| Token-specific recipients take precedence over the global recipient. | ||
|
|
||
| **Examples:** | ||
|
|
||
| ```solidity | ||
| // Define a global recipient for all tokens | ||
| setClaimRecipient(vaultAddress, address(0)); | ||
| // ✅ claimRecipient[user][address(0)] = vaultAddress | ||
| // ✅ All tokens without a specific recipient will go to vaultAddress | ||
|
|
||
| // Define a specific recipient for aglaMerkl (takes priority over global) | ||
| setClaimRecipient(aglaMerklVaultAddress, aglaMerkl); | ||
| // ✅ claimRecipient[user][aglaMerkl] = aglaMerklVaultAddress | ||
| // ✅ aglaMerkl goes to aglaMerklVaultAddress (not the global vault) | ||
|
|
||
| // Remove the specific recipient for aglaMerkl | ||
| setClaimRecipient(address(0), aglaMerkl); | ||
| // ✅ claimRecipient[user][aglaMerkl] = address(0) | ||
| // ✅ aglaMerkl will now use the global recipient (vaultAddress) | ||
| ``` | ||
|
|
||
| **Example flow:** | ||
|
|
||
| ```solidity | ||
| // Setup | ||
| setClaimRecipient(vaultAglaMerkl, aglaMerkl); // Specific for aglaMerkl | ||
| setClaimRecipient(defaultVault, address(0)); // Global fallback | ||
|
|
||
| // When claiming: | ||
| // - aglaMerkl → goes to vaultAglaMerkl (token-specific) | ||
| // - WETH → goes to defaultVault (global fallback) | ||
| // - DAI → goes to defaultVault (global fallback) | ||
| ``` | ||
|
|
||
| **Use cases:** | ||
|
|
||
| - Routing rewards to smart contracts that cannot directly claim | ||
| - Redirecting rewards to user-controlled addresses | ||
| - Protocol-level reward forwarding mechanisms | ||
|
|
||
| --- | ||
|
|
||
| ## Claim Callback System (Automatic Actions) | ||
|
|
||
| The `claimWithRecipient()` function supports passing arbitrary data (`bytes`) to recipient contracts, enabling automatic actions like swaps via aggregators when rewards are claimed. | ||
|
|
||
| ### How it works | ||
|
|
||
| When you call `claimWithRecipient()` with non-empty `datas`, the system: | ||
|
|
||
| 1. Transfers tokens to the recipient address | ||
| 2. Automatically calls `onClaim()` on the recipient contract (if it implements `IClaimRecipient`) | ||
| 3. Passes the custom data to the callback, allowing the recipient to perform actions like swaps, deposits, or conversions | ||
|
|
||
| ### Function: `claimWithRecipient()` | ||
|
|
||
| **Signature:** | ||
| ```solidity | ||
| function claimWithRecipient( | ||
| address[] calldata users, | ||
| address[] calldata tokens, | ||
| uint256[] calldata amounts, | ||
| bytes32[][] calldata proofs, | ||
| address[] calldata recipients, | ||
| bytes[] memory datas // Custom data for callback | ||
| ) external | ||
| ``` | ||
|
|
||
| **Parameters:** | ||
|
|
||
| - `datas`: Array of arbitrary `bytes` data, one per claim. This data is passed to the recipient's `onClaim()` callback. | ||
|
|
||
| ### IClaimRecipient Interface | ||
|
|
||
| Recipient contracts must implement this interface: | ||
|
|
||
| ```solidity | ||
| interface IClaimRecipient { | ||
| function onClaim(address user, address token, uint256 amount, bytes memory data) | ||
| external returns (bytes32); | ||
| } | ||
| ``` | ||
|
|
||
| The callback must return `CALLBACK_SUCCESS` (keccak256("IClaimRecipient.onClaim")) or the claim will revert. | ||
|
|
||
| **Important notes:** | ||
|
|
||
| - If `data.length == 0`, no callback is made (standard transfer only) | ||
| - If the callback reverts, the token transfer still occurred (uses `try/catch`) | ||
| - The `datas` array must have the same length as other arrays | ||
| - Data encoding is flexible - you can encode any parameters your recipient contract needs | ||
|
|
||
| ### Use cases | ||
|
|
||
| - **Automatic swaps**: Swap rewards immediately via DEX aggregators (1inch, 0x, etc.) | ||
| - **Auto-deposits**: Deposit rewards into liquidity pools or yield farms | ||
| - **Token conversions**: Convert rewards to a different token automatically | ||
| - **Complex routing**: Perform multiple actions based on encoded data | ||
|
|
||
| ### Example: Automatic Swap | ||
|
|
||
| ```solidity | ||
| // 1. Set a swap vault as recipient | ||
| setClaimRecipient(swapVaultAddress, aglaMerkl); | ||
|
|
||
| // 2. Claim with swap parameters encoded in data | ||
| bytes memory swapData = abi.encode( | ||
| targetToken, // Token to receive after swap | ||
| minAmountOut, // Minimum amount expected | ||
| swapRouter // Aggregator router address | ||
| ); | ||
|
|
||
| claimWithRecipient( | ||
| [userAddress], | ||
| [aglaMerkl], | ||
| [amount], | ||
| [proof], | ||
| [swapVaultAddress], | ||
| [swapData] // Data for automatic swap | ||
| ); | ||
|
|
||
| // The swap vault receives aglaMerkl, automatically swaps it, and sends the result to the user | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Support for `address(0)` as Wildcard | ||
|
|
||
| The `Distributor` contract uses `address(0)` as a wildcard parameter in several functions, representing **"all tokens"** or **"default"**. | ||
|
|
||
| ### Where `address(0)` is used: | ||
|
|
||
| 1. **`setClaimRecipient(recipient, address(0))`** | ||
| - Sets a **global recipient** that applies to all tokens without specific recipients | ||
|
|
||
| ### How it works: | ||
|
|
||
| - When `address(0)` is used as the token parameter, it acts as a fallback/default | ||
| - This simplifies operations that need to work across multiple tokens | ||
| - Token-specific recipients take precedence over the global recipient (see priority order above) | ||
|
|
||
| ### Address Remapping | ||
|
|
||
| If a smart contract you use can’t claim rewards, ask the Merkl team to remap those rewards to a claimable wallet by reaching out on [Discord](https://discord.com/channels/1209830388726243369/1210212731047776357) with the campaign ID, source address, and destination address. The full walkthrough lives in the [Reward Forwarding guide](../merkl-mechanisms/reward-forwarding.md#address-remapping). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can specify that some addresses may have in some cases admin rights to claim for everyone -> on a case by case basis (cf indirect reference to main operator)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I would say:
However, Merkl provides a flexible operator system that lets you delegate the right to execute the claim transaction on your behalf, while still receiving the rewards yourself.