Skip to content

feat(add-money): offramp migration deposit entry#2330

Closed
kushagrasarathe wants to merge 3 commits into
devfrom
feat/offramp-migration-deposit
Closed

feat(add-money): offramp migration deposit entry#2330
kushagrasarathe wants to merge 3 commits into
devfrom
feat/offramp-migration-deposit

Conversation

@kushagrasarathe

Copy link
Copy Markdown
Contributor

Why

offramp.xyz is shutting down and migrating its users to Peanut (~50k accounts, ~5k active). To improve conversion of migrants, Konrad asked for a dedicated, de-cluttered deposit entry that hands offramp users an Arbitrum address without the full multi-chain crypto picker.

Offramp users hold funds in ZeroDev smart accounts on Arbitrum, so the existing crypto deposit already does the job functionally (Rhino EVM SDA → funds land on the Peanut Arbitrum wallet). This PR is a thin, branded, tracked wrapper over that same infra — no backend changes.

What

  • Gated entry — a "Migrate from Offramp" card in the add-money method list, shown only to users with the offramp badge. Routes to /add-money/crypto?network=EVM&source=offramp.
  • offramp variant of CryptoDeposit.view — same Rhino SDA, QR, address, copy, polling and success screen, but strips the chain fluff: single Arbitrum network chip + single USDC token chip, offramp copy, no network drawer / no supported-networks modal / no multi-chain tooltip.
  • Analytics — completions tag method_type: 'offramp_migration' for the growth dashboard.

Draft — pending badge wiring ⚠️

The offramp badge doesn't exist yet (Konrad's badges PR is WIP). This PR uses a placeholder badge code:

// AddMoneyMethodSelection.view.tsx
const OFFRAMP_BADGE_CODE = 'OFFRAMP'

Before merge:

  1. Swap OFFRAMP_BADGE_CODE for the real code from the badges PR.
  2. Confirm the badge is link-granted at signup (via the offramp signup invite code/campaign), not auto on an action — otherwise it's circular (need a deposit to earn the badge that unlocks the deposit entry).

Reuse / scope

Reuses useCryptoDepositPolling, the SDA creation query, and PaymentSuccessView unchanged. Default (non-offramp) crypto deposit is untouched. FE-only.

Test plan

  • Load /add-money/crypto?network=EVM&source=offramp → see stripped Arbitrum + USDC surface
  • Add-money method list shows "Migrate from Offramp" only when the user has the (placeholder) offramp badge
  • Deposit USDC on Arbitrum → success screen + receipt work as before
  • Non-offramp crypto deposit unchanged

offramp.xyz is shutting down and migrating users to peanut. give
offramp migrants a de-cluttered arbitrum deposit surface instead of the
full multi-chain crypto picker: same rhino EVM SDA (funds land on the
peanut arbitrum wallet), but stripped to a single arbitrum + usdc view
with offramp copy.

- gate a 'migrate from offramp' entry on the offramp badge (placeholder
  code, wired to the real badge once that PR lands)
- deep-link /add-money/crypto?network=EVM&source=offramp drives a new
  'offramp' variant of the crypto deposit view
- tag completions as offramp_migration for the growth dashboard

reuses the existing SDA creation, polling and success screen unchanged.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 2, 2026 2:38pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR updates Add Money method selection to derive an Offramp badge flag from authenticated user data and conditionally render an extra migration option. It also changes invite campaign resolution to map explicit campaign parameters through the badge map before the existing fallback order.

Changes

Add Money method selection

Layer / File(s) Summary
Auth badge state
src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx
useAuth is added and hasOfframpBadge is derived from the authenticated user's badges using OFFRAMP_BADGE_CODE.
Conditional card rendering
src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx
An extra “Migrate from Offramp” ActionListCard is shown when the badge is present, and the “Crypto” card position changes to fit the new layout.

Invite campaign resolution

Layer / File(s) Summary
Explicit campaign mapping
src/components/Invites/InvitesPage.tsx
Explicit campaign/campaignTag values are mapped through UTM_CAMPAIGN_TO_BADGE_MAP before the existing invite-code and utm_campaign fallback order.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: jjramirezn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change by calling out an Offramp migration deposit entry in add-money.
Description check ✅ Passed The description aligns with the change set, describing the Offramp migration entry and invite badge resolution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5858.65 → 5860.51 (+1.86)
Findings: +1 net (+12 new, -11 resolved)

🆕 New findings (12)

  • critical complexity — src/components/Invites/InvitesPage.tsx — CC 59, MI 56.79, SLOC 126
  • high complexity — src/components/AddMoney/views/CryptoDeposit.view.tsx — CC 35, MI 63.72, SLOC 52
  • medium high-mdd — src/components/AddMoney/views/CryptoDeposit.view.tsx:50 — CryptoDepositView: MDD 100.3 (uses across many lines from declarations)
  • medium high-mdd — src/components/Invites/InvitesPage.tsx:24 — InvitePageContent: MDD 55.8 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/add-money/crypto/page.tsx:24 — AddMoneyCryptoPage: MDD 34.5 (uses across many lines from declarations)
  • medium method-complexity — src/components/AddMoney/views/CryptoDeposit.view.tsx:50 — CC 26 SLOC 35
  • medium high-mdd — src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx:20 — AddMoneyMethodSelection: MDD 24.0 (uses across many lines from declarations)
  • medium method-complexity — src/components/Invites/InvitesPage.tsx:24 — InvitePageContent CC 21 SLOC 65
  • medium complexity — src/app/(mobile-ui)/add-money/crypto/page.tsx — CC 15, MI 54.89, SLOC 97
  • low high-mdd — src/app/(mobile-ui)/add-money/crypto/page.tsx:61 — : MDD 18.4 (uses across many lines from declarations)
  • low structural-dup — components/AddMoney/views/CryptoDeposit.view.tsx:86 — 15 duplicate lines / 66 tokens with components/AddMoney/views/RhinoDeposit.view.tsx:74
  • low missing-return-type — src/components/Invites/InvitesPage.tsx:244 — InvitesPage: exported fn missing return type annotation

✅ Resolved (11)

  • src/components/Invites/InvitesPage.tsx — CC 57, MI 56.95, SLOC 125
  • src/components/AddMoney/views/CryptoDeposit.view.tsx:46 — CryptoDepositView: MDD 94.7 (uses across many lines from declarations)
  • src/components/Invites/InvitesPage.tsx:24 — InvitePageContent: MDD 55.5 (uses across many lines from declarations)
  • src/app/(mobile-ui)/add-money/crypto/page.tsx:24 — AddMoneyCryptoPage: MDD 32.6 (uses across many lines from declarations)
  • src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx:14 — AddMoneyMethodSelection: MDD 21.0 (uses across many lines from declarations)
  • src/components/AddMoney/views/CryptoDeposit.view.tsx — CC 21, MI 65.33, SLOC 48
  • src/components/Invites/InvitesPage.tsx:24 — InvitePageContent CC 19 SLOC 64
  • src/app/(mobile-ui)/add-money/crypto/page.tsx — CC 13, MI 55.65, SLOC 93
  • src/app/(mobile-ui)/add-money/crypto/page.tsx:58 — : MDD 18.4 (uses across many lines from declarations)
  • components/AddMoney/views/CryptoDeposit.view.tsx:73 — 15 duplicate lines / 66 tokens with components/AddMoney/views/RhinoDeposit.view.tsx:74
  • src/components/Invites/InvitesPage.tsx:240 — InvitesPage: exported fn missing return type annotation

📈 Painscore deltas (top movers)

File Before After Δ
src/components/AddMoney/views/CryptoDeposit.view.tsx 7.8 8.6 +0.8

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1643 ran, 0 failed, 0 skipped, 25.5s

📊 Coverage (unit)

metric %
statements 54.7%
branches 37.5%
functions 42.3%
lines 54.6%
⏱ 10 slowest test cases
time test
3.9s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle unresolvable ENS name
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (too short)
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Manteca PIX form ready shows merchant card + amount input + pay button
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

badge PRs (peanut-api-ts#1105, peanut-ui#2331) landed the link-granted
OFFRAMP_USER badge; wire the deposit-entry gate to it.
?campaign=offramp (and any UTM-mapped vanity tag passed via the explicit
campaign/campaignTag param) reached /badge/award raw and 400'd, since the
backend matches the badge code. Resolve it through the UTM map first so the
offramp migration link grants OFFRAMP_USER regardless of link shape.

depends on peanut-ui#2331 (adds the offramp UTM map entry) reaching this base.
@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Invites/InvitesPage.tsx`:
- Around line 40-41: The campaign mapping in InvitesPage.tsx falls through for
?campaign=offramp, so add an offramp entry to UTM_CAMPAIGN_TO_BADGE_MAP and
ensure the InvitesPage campaign normalization uses that mapped badge code
instead of passing the raw value through. Use the UTM_CAMPAIGN_TO_BADGE_MAP
constant and the campaign selection logic in InvitesPage to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d9ced66-ab00-4467-8a91-873f4fe0c4db

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3b664 and 3300404.

📒 Files selected for processing (1)
  • src/components/Invites/InvitesPage.tsx

Comment on lines 40 to +41
const campaign =
(campaignParam && UTM_CAMPAIGN_TO_BADGE_MAP[campaignParam.toLowerCase()]) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm an 'offramp' entry exists in UTM_CAMPAIGN_TO_BADGE_MAP.
rg -n -A2 "offramp" src/components/Invites/campaign-maps.ts

Repository: peanutprotocol/peanut-ui

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and inspect the campaign map plus invite page usage.
git ls-files | rg 'src/components/Invites/(InvitesPage\.tsx|campaign-maps\.ts)$'

echo '--- campaign-maps outline ---'
ast-grep outline src/components/Invites/campaign-maps.ts --view expanded || true

echo '--- InvitesPage outline ---'
ast-grep outline src/components/Invites/InvitesPage.tsx --view expanded || true

echo '--- campaign-maps excerpt ---'
nl -ba src/components/Invites/campaign-maps.ts | sed -n '1,220p'

echo '--- InvitesPage excerpt ---'
nl -ba src/components/Invites/InvitesPage.tsx | sed -n '1,220p'

Repository: peanutprotocol/peanut-ui

Length of output: 615


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any offramp-related mappings or alternate invite campaign maps.
rg -n "offramp|UTM_CAMPAIGN_TO_BADGE_MAP|campaignParam" src/components src | sed -n '1,220p'

Repository: peanutprotocol/peanut-ui

Length of output: 25046


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- campaign-maps.ts ---'
sed -n '1,220p' src/components/Invites/campaign-maps.ts

echo '--- InvitesPage.tsx ---'
sed -n '1,120p' src/components/Invites/InvitesPage.tsx

Repository: peanutprotocol/peanut-ui

Length of output: 10014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for path in [
    Path("src/components/Invites/campaign-maps.ts"),
    Path("src/components/Invites/InvitesPage.tsx"),
]:
    print(f"=== {path} ===")
    text = path.read_text()
    for i, line in enumerate(text.splitlines(), 1):
        if "offramp" in line.lower() or "UTM_CAMPAIGN_TO_BADGE_MAP" in line or "campaignParam" in line:
            start = max(1, i-4)
            end = min(len(text.splitlines()), i+6)
            for j in range(start, end+1):
                print(f"{j:4d}: {text.splitlines()[j-1]}")
            print()
PY

Repository: peanutprotocol/peanut-ui

Length of output: 5461


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for path in [
    Path("src/components/Invites/campaign-maps.ts"),
    Path("src/components/Invites/InvitesPage.tsx"),
]:
    print(f"=== {path} ===")
    lines = path.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if "offramp" in line.lower() or "UTM_CAMPAIGN_TO_BADGE_MAP" in line or "campaignParam" in line:
            start = max(1, i-4)
            end = min(len(lines), i+6)
            for j in range(start, end+1):
                print(f"{j:4d}: {lines[j-1]}")
            print()
PY

Repository: peanutprotocol/peanut-ui

Length of output: 5461


Add an offramp entry to UTM_CAMPAIGN_TO_BADGE_MAP
?campaign=offramp still falls through to the raw value, so the invite request hits the backend as an invalid badge code and returns 400.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Invites/InvitesPage.tsx` around lines 40 - 41, The campaign
mapping in InvitesPage.tsx falls through for ?campaign=offramp, so add an
offramp entry to UTM_CAMPAIGN_TO_BADGE_MAP and ensure the InvitesPage campaign
normalization uses that mapped badge code instead of passing the raw value
through. Use the UTM_CAMPAIGN_TO_BADGE_MAP constant and the campaign selection
logic in InvitesPage to locate the fix.

@kushagrasarathe

Copy link
Copy Markdown
Contributor Author

Superseded by #2333 — rebased onto main (which now has Konrad's badge PRs #1105/#2331) so it's testable end-to-end on preview. Repo blocks force-push, so a fresh branch off main was the clean way to re-base without dragging dev-only commits into main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant