← Back to articles

Writing

5 min read

Building a Merkl Rewards Integration in a Modern Web3 Application

A deep dive into integrating the Merkl protocol into a modern Web3 application using Next.js. Explore API architecture, reward parsing, wallet interactions, Merkle proof claims, and scalable frontend engineering practices.

Merkl integrationMerkl APIMerkl rewardsNext.js Web3wagmi tutorialviem smart contract

Blockchain incentive programs have become a core part of the DeFi ecosystem, but integrating reward distribution into an existing application isn't always straightforward. A production-ready implementation needs to display rewards accurately, provide a smooth claiming experience, and fit naturally into the application's existing architecture without introducing unnecessary complexity.

In one of my recent Web3 frontend projects, I implemented a complete integration with the **Merkl** rewards protocol. The goal was to allow users to view and claim their on-chain rewards while keeping the implementation modular, scalable, and easy to maintain.

Rather than replacing an existing rewards system, the integration was designed as an independent module that could coexist with other claim mechanisms and be extended in the future.

---

# What is Merkl?

Merkl is a decentralized rewards distribution protocol used by many DeFi protocols to incentivize on-chain activity.

Instead of storing claimable balances directly inside smart contracts, Merkl periodically computes user rewards and publishes them as a **Merkle Tree**. Users then submit a Merkle proof to the Distributor contract in order to claim their rewards.

Each reward contains several important values:

* **amount** – Total cumulative rewards in the current Merkle tree * **claimed** – Rewards already claimed on-chain * **pending** – Rewards earned but not yet included in a Merkle root * **proofs** – Merkle proofs required for claiming

One of the most important implementation details is understanding the reward calculation.

The actual claimable amount is:

claimable = amount - claimed

The `pending` value represents rewards that have not yet been committed into a Merkle root and therefore cannot be claimed.

This distinction is essential for displaying accurate balances and preventing incorrect claim attempts.

---

# Technology Stack

The integration was built using technologies already present in the application:

* Next.js (App Router) * TypeScript * React * wagmi * viem * RainbowKit * Tailwind CSS

One design goal was to avoid introducing additional dependencies. Everything was implemented using the existing frontend architecture.

---

# High-Level Architecture

The overall flow looks like this:

User Wallet
      │
      ▼
React UI
      │
      ▼
Custom React Hook
      │
      ▼
Next.js API Route
      │
      ▼
Merkl REST API
      │
      ▼
Reward Parsing
      │
      ▼
Claim Transaction
      │
      ▼
Distributor Smart Contract

The architecture intentionally separates responsibilities.

* UI components only display data. * Custom hooks manage state and wallet interactions. * Service functions communicate with external APIs. * Server routes handle communication with external services. * Smart contract interactions remain isolated from presentation logic.

This separation keeps the codebase easier to maintain as the application grows.

---

# Why Use a Server Proxy?

One interesting challenge was that the browser could not directly communicate with the Merkl API because of the application's Content Security Policy (CSP).

Instead of relaxing browser security policies, I implemented a lightweight Next.js API route that acts as a proxy.

The browser communicates only with the application's backend, while the backend securely fetches data from the Merkl API.

This approach provides several advantages:

* Maintains a strict Content Security Policy * Allows future authentication if required * Centralizes API handling * Simplifies frontend logic * Makes rate limiting easier to implement later

---

# Building a Modular Rewards System

One of the primary goals was modularity.

Instead of scattering Merkl-related logic throughout the application, everything was isolated into its own feature module.

A typical structure looks like this:

merkl/
├── constants
├── types
├── service
├── utilities
├── hooks
└── components

This organization makes the integration easy to locate, test, extend, or even remove without affecting unrelated reward systems.

---

# Fetching Rewards

The frontend requests rewards through an internal API route.

The server then retrieves reward information from Merkl's public API and filters the response for the appropriate blockchain network and supported reward token.

After parsing the response, the frontend calculates:

* Total rewards * Already claimed rewards * Currently claimable rewards * Pending rewards * Available Merkle proofs

The UI only enables claiming when:

* Claimable balance is greater than zero * Valid Merkle proofs are available

This prevents unnecessary failed transactions and improves the overall user experience.

---

# The On-Chain Claim Flow

The claiming process follows a straightforward sequence:

1. User connects their wallet. 2. Reward data is loaded. 3. User initiates a claim. 4. The application switches to the correct blockchain network if necessary. 5. Merkle proof data is prepared. 6. The Distributor contract is called. 7. The transaction is confirmed. 8. Reward data is refreshed.

An important implementation detail is that the smart contract expects the **cumulative reward amount**, not only the currently claimable difference.

The contract itself calculates how much has already been claimed and transfers only the remaining rewards.

Using the cumulative amount ensures compatibility with the Distributor contract's accounting model.

---

# User Experience Considerations

Good blockchain UX is often more important than the transaction itself.

The interface was designed to clearly communicate every possible reward state, including:

* Wallet not connected * No rewards available * Rewards fully claimed * Pending rewards awaiting the next Merkle root * Rewards available but missing proofs * Transaction pending * Successful claim * User-rejected transaction * Contract execution failure

Rather than displaying generic errors, each state provides context so users understand exactly what is happening.

---

# Challenges During Development

1. Browser Security Restrictions

The browser could not communicate directly with the external rewards API because of Content Security Policy restrictions.

**Solution**

A Next.js API proxy handled all external communication while keeping browser security intact.

---

2. Correct Reward Calculations

Initially, it was tempting to treat all reported rewards as immediately claimable.

However, Merkl distinguishes between cumulative rewards, claimed rewards, and pending rewards.

**Solution**

The application explicitly computes:

claimable = amount - claimed

Pending rewards are displayed separately and never treated as claimable.

---

3. Cache Refresh After Claim

Immediately after claiming, cached API responses may still show the previous reward state because indexing can take a short time.

**Solution**

After a successful transaction, the application performs a fresh reward fetch using cache bypass logic so users receive updated information as soon as possible.

---

4. Defensive Error Handling

Blockchain applications encounter many edge cases that traditional web applications do not.

Examples include:

* Wallet disconnected * Wrong network * User rejects signature * Missing Merkle proofs * API unavailable * Smart contract reverts

Handling each scenario individually creates a much smoother user experience than relying on generic error messages.

---

# Key Engineering Decisions

Several architectural decisions made the integration easier to maintain:

* Modular feature-based folder structure * Custom React hooks instead of global state management * Server-side API proxy * Minimal smart contract abstraction * Existing Web3 libraries reused instead of adding new dependencies * Strong TypeScript typing across API responses * Clear separation between fetching, parsing, UI rendering, and contract execution

These decisions reduce long-term maintenance costs and make future enhancements significantly easier.

---

# Future Improvements

The current architecture provides a solid foundation while leaving room for additional features, including:

* Multi-token reward support * Campaign-level reward breakdowns * Historical reward analytics * Additional blockchain networks * Enhanced production caching and rate limiting * Expanded reward dashboards and filtering

Because the implementation is modular, these features can be introduced without major architectural changes.

---

# Final Thoughts

Integrating a Web3 rewards protocol involves much more than simply calling a smart contract. A production-ready solution requires careful handling of API responses, blockchain transactions, caching, wallet interactions, security policies, and user experience.

This project reinforced the importance of building integrations that are modular, maintainable, and resilient to real-world edge cases. By keeping responsibilities clearly separated and designing for future extensibility, the resulting implementation is both easier to maintain and ready to evolve alongside the broader Web3 ecosystem.

---

References

* Merkl Developer Documentation: https://developers.merkl.xyz/integrate-merkl/user-rewards * Merkl Chains & Contracts: https://developers.merkl.xyz/resources/chains-and-contracts * Merkl API Documentation: https://api.merkl.xyz/v4

Get practical Web3 & frontend tips

Short articles on Next.js, Web3 dashboards, and DeFi interfaces. No spam, unsubscribe anytime.

Want help implementing this on your product?

I work with crypto startups and product teams on Web3 dashboards, DeFi interfaces, and Next.js frontends — from architecture through launch. Hire me for web3 dashboard development.