Skip to content

Add Merkl yield boost claiming to savings screen - #2592

Merged
MusabShakeel576 merged 1 commit into
qafrom
claude/merkl-campaign-implementation-nogeis
Sep 27, 2026
Merged

MusabShakeel576 merged 1 commit into
qafrom
claude/merkl-campaign-implementation-nogeis

Conversation

@MusabShakeel576

@MusabShakeel576 MusabShakeel576 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements claiming of Merkl yield-boost rewards (WFUSE on Fuse) directly from the savings screen. Users can now view their accumulated boost earnings and claim them in a single transaction that unwraps WFUSE to native FUSE.

Key Changes

  • New Merkl yield-boost module (lib/merklYieldBoost.ts):

    • Fetches reward data from Merkl's REST API (/v4/users/{address}/rewards)
    • Parses cumulative amounts, claimed balances, and Merkle proofs
    • Scopes "Total Earned" to boost campaigns while allowing claims of the full WFUSE balance
    • Builds transactions to claim via the Merkl Distributor and unwrap to native FUSE
    • Handles cache-bypassing reads before/after claims to avoid stale state
  • React hook (hooks/useYieldBoostRewards.ts):

    • useYieldBoostRewards: Queries boost rewards with 60s stale time to balance freshness and rate limits
    • useClaimYieldBoost: Mutation that reads fresh proofs and onchain claimed state, executes the claim batch, and updates cache
  • UI updates (components/Savings/NewSavings/SavingsYieldBoostCard.tsx):

    • Displays boost percentage, total earned in USD, and a claim button
    • Shows loading skeleton while fetching rewards
    • Claim button disabled when nothing is claimable
    • Renders nothing for users with no active boost and no claimable rewards
    • Allows users who lost their tier to still collect previously earned rewards
    • Uses Merkl's price quote or falls back to native FUSE price for USD conversion
  • Configuration (lib/config.ts, .env.example):

    • Added EXPO_PUBLIC_MERKL_YIELD_BOOST_CAMPAIGN_IDS to scope earned rewards to specific campaigns
  • Type updates (lib/types.ts, YieldBoostSheet.types.ts):

    • Added yieldBoostBalanceCap to track the savings balance cap per tier (Prime $10K, Ultra $25K)
  • Comprehensive tests (lib/__tests__/merklYieldBoost.test.ts, components/Savings/NewSavings/__tests__/SavingsYieldBoostCard.test.tsx):

    • Parser handles malformed payloads, case-insensitive addresses, and campaign scoping
    • Transaction builder validates proofs and onchain state
    • UI tests verify rendering, claim flow, and disabled states

Implementation Details

  • Merkl claims are per token and cumulative, so three separate campaigns (soUSD, soETH, soFUSE) collapse into one WFUSE leaf and one claim transaction
  • Fresh cache-bypassing reads before signing prevent reverts from concurrent claims or new roots
  • The claim batch atomically claims WFUSE and unwraps it to FUSE, using the Distributor's own claimed record to calculate exact payout
  • Rewards query uses 60s stale time to keep the savings screen responsive while respecting Merkl's rate limits
  • Price data comes from Merkl's API or falls back to the native FUSE price for USD display

https://claude.ai/code/session_01EupadZ9qYL5nDLM6dDtu5n


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Implements the Yield boost card from Figma 26134:23293 with its Claim
button, and puts the card back on the funded savings vault screen (the
redesign had dropped it), under the balance card.

- The boost is paid by Merkl campaigns (one per vault, all WFUSE on
  Fuse). lib/merklYieldBoost.ts reads the WFUSE leaf from
  /v4/users/{address}/rewards, scopes "Total Earned" to the campaign ids
  in EXPO_PUBLIC_MERKL_YIELD_BOOST_CAMPAIGN_IDS, and builds the claim.
  It doesn't import @merkl/api, so it can be tested without it.
- Claim is one user operation: Distributor.claim with the cumulative
  amount, then WFUSE.withdraw of exactly what the claim pays, measured
  against the Distributor's own claimed record so a stale index can't
  revert the batch. Proofs are re-read (bypassing Merkl's cache) right
  before signing, and the query is refreshed the same way afterwards.
- Total Earned is the boost campaigns' credited amount plus pending, in
  USD at the app's FUSE price (Merkl's quote as a fallback).
- The card stays visible with a disabled "—" boost for a user who lost
  their tier but still has something to claim.
- The rewards yield-boost sheet now describes the FUSE boost and its
  balance cap ("on your first $10,000") when the backend sends
  yieldBoostBalanceCap, and keeps the old copy when it doesn't.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EupadZ9qYL5nDLM6dDtu5n
@vercel

vercel Bot commented Sep 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
solid-app Ignored Ignored Sep 27, 2026 11:38am UTC
solid-app-staging Ignored Ignored Sep 27, 2026 11:38am UTC

Request Review

@claude

claude Bot commented Sep 27, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@MusabShakeel576
MusabShakeel576 merged commit 5f6d14d into qa Sep 27, 2026
7 checks passed
@MusabShakeel576
MusabShakeel576 deleted the claude/merkl-campaign-implementation-nogeis branch September 27, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants