Skip to content

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

Merged
Hugo0 merged 4 commits into
mainfrom
feat/offramp-migration-deposit-main
Jul 3, 2026
Merged

feat(add-money): offramp migration deposit entry#2333
Hugo0 merged 4 commits into
mainfrom
feat/offramp-migration-deposit-main

Conversation

@kushagrasarathe

Copy link
Copy Markdown
Contributor

Rebased-to-main successor of #2330 (repo blocks force-push, so a fresh branch off main was the only way to re-base cleanly without dragging dev-only commits into main). Now that Konrad's badge PRs (peanut-api-ts#1105, peanut-ui#2331) are merged to main, this sits on top of them and is testable end-to-end on preview.

Why

offramp.xyz is shutting down and migrating its users to Peanut. This gives offramp migrants a dedicated, de-cluttered Arbitrum deposit surface. Offramp users hold funds in ZeroDev smart accounts on Arbitrum, so the existing crypto deposit already works functionally (Rhino EVM SDA → funds land on the Peanut Arbitrum wallet); this is a thin, branded, tracked wrapper. FE-only, no backend changes.

What

  • Gated entry — a "Migrate from Offramp" card in the add-money method list, shown only to users with the OFFRAMP_USER badge. Routes to /add-money/crypto?network=EVM&source=offramp.
  • offramp variant of CryptoDeposit.view — same Rhino SDA / QR / address / polling / success screen, stripped to a single Arbitrum + USDC surface with offramp copy (no network drawer, no supported-networks modal, no multi-chain tooltip).
  • Analytics — completions tag method_type: 'offramp_migration'.
  • Lowercase-tag fix (InvitesPage.tsx) — an explicit ?campaign=<tag> is now resolved through the UTM map, so ?campaign=offramp grants OFFRAMP_USER instead of 400ing at /badge/award. Fixes the footgun found reviewing feat(badges): wire Offramp User badge (art + campaign maps) #2331/[TASK-13864] fix: dates in receipts #1105.

Depends on (merged to main ✅)

  • peanut-api-ts#1105 — OFFRAMP_USER link-grant badge
  • peanut-ui#2331 — badge art + campaign maps (offramp → OFFRAMP_USER)

Test plan (preview)

  • Sign up via offramp link (invite code offramp or ?utm_campaign=offramp or ?campaign=offramp) → user gets OFFRAMP_USER badge
  • Add-money shows "Migrate from Offramp" only for badge holders
  • Entry → /add-money/crypto?...&source=offramp shows stripped Arbitrum + USDC surface
  • Deposit USDC on Arbitrum → success + receipt work
  • Non-offramp crypto deposit unchanged

Closes #2330.

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.
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.
@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 3, 2026 1:31am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75365554-454a-4a02-964a-e4bae2660aee

📥 Commits

Reviewing files that changed from the base of the PR and between 456a52a and 8407e0a.

📒 Files selected for processing (7)
  • src/app/(mobile-ui)/add-money/crypto/page.tsx
  • src/components/AddMoney/components/HowToDepositModal.tsx
  • src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx
  • src/components/AddMoney/views/CryptoDeposit.view.tsx
  • src/components/Invites/InvitesPage.tsx
  • src/components/Invites/campaign-maps.test.ts
  • src/components/Invites/campaign-maps.ts

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: 5854.51 → 5859.57 (+5.06)
Findings: +3 net (+17 new, -14 resolved)

🆕 New findings (17)

  • critical complexity — src/components/Invites/InvitesPage.tsx — CC 54, MI 56.84, SLOC 126
  • high complexity — src/components/AddMoney/views/CryptoDeposit.view.tsx — CC 45, MI 62.95, SLOC 53
  • high method-complexity — src/components/AddMoney/views/CryptoDeposit.view.tsx:57 — CC 36 SLOC 33
  • medium high-mdd — src/components/AddMoney/views/CryptoDeposit.view.tsx:57 — CryptoDepositView: MDD 137.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Invites/InvitesPage.tsx:24 — InvitePageContent: MDD 56.7 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/add-money/crypto/page.tsx:24 — AddMoneyCryptoPage: MDD 42.2 (uses across many lines from declarations)
  • medium high-dlt — src/components/Invites/InvitesPage.tsx:24 — InvitePageContent: DLT 31 (calls 31 distinct functions — high context load)
  • medium high-mdd — src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx:18 — AddMoneyMethodSelection: MDD 24.0 (uses across many lines from declarations)
  • medium complexity — src/app/(mobile-ui)/add-money/crypto/page.tsx — CC 20, MI 55.81, SLOC 103
  • medium method-complexity — src/components/Invites/InvitesPage.tsx:24 — InvitePageContent CC 15 SLOC 65
  • medium complexity — src/components/Invites/campaign-maps.ts — CC 14, MI 57.49, SLOC 40
  • medium complexity — src/components/AddMoney/components/HowToDepositModal.tsx — CC 7, MI 61.96, SLOC 27
  • low high-mdd — src/app/(mobile-ui)/add-money/crypto/page.tsx:71 — : MDD 18.8 (uses across many lines from declarations)
  • low high-dlt — src/app/(mobile-ui)/add-money/crypto/page.tsx:24 — AddMoneyCryptoPage: DLT 17 (calls 17 distinct functions — high context load)
  • low structural-dup — components/AddMoney/views/CryptoDeposit.view.tsx:113 — 13 duplicate lines / 55 tokens with components/AddMoney/views/RhinoDeposit.view.tsx:76
  • low high-mdd — src/components/AddMoney/components/HowToDepositModal.tsx:29 — HowToDepositModal: MDD 13.2 (uses across many lines from declarations)
  • low missing-return-type — src/components/Invites/InvitesPage.tsx:239 — InvitesPage: exported fn missing return type annotation

✅ Resolved (14)

  • 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/Invites/InvitesPage.tsx:24 — InvitePageContent: DLT 30 (calls 30 distinct functions — high context load)
  • 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/components/Invites/campaign-maps.ts — CC 6, MI 54.93, SLOC 36
  • src/app/(mobile-ui)/add-money/crypto/page.tsx:58 — : MDD 18.4 (uses across many lines from declarations)
  • src/app/(mobile-ui)/add-money/crypto/page.tsx:24 — AddMoneyCryptoPage: DLT 16 (calls 16 distinct functions — high context load)
  • 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 9.7 +2.0
src/components/AddMoney/components/HowToDepositModal.tsx 4.6 6.3 +1.7
src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx 4.2 4.8 +0.5

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1669 ran, 0 failed, 0 skipped, 25.0s

📊 Coverage (unit)

metric %
statements 54.9%
branches 38.0%
functions 42.7%
lines 54.8%
⏱ 10 slowest test cases
time test
3.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.4s 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-extended.test.ts › should not include apiKey in updateUserById body
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
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 valid ETH address with surrounding spaces
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 invalid ETH address (too short)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle minimum length (6 digits) US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid UK IBAN with spaces
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@kushagrasarathe

Copy link
Copy Markdown
Contributor Author

@coderabbitai review in depth.

Evaluate:

  • risk of introducing new bugs and breaking existing functionality
  • performance impact and latency
  • code quality and DRY

Also, write a very concise testing guide for QA

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 2333 --repo peanutprotocol/peanut-ui --json title,body,additions,deletions,changedFiles,files -q '.'
echo "---FILES---"
gh pr diff 2333 --repo peanutprotocol/peanut-ui > /tmp/pr2333.diff
wc -l /tmp/pr2333.diff
echo "---FILE LIST---"
gh pr view 2333 --repo peanutprotocol/peanut-ui --json files -q '.files[].path'

Length of output: 3522


🏁 Script executed:

#!/bin/bash
cat /tmp/pr2333.diff

Length of output: 16825


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== rhino.consts CHAIN_LOGOS/TOKEN_LOGOS =="
rg -n "CHAIN_LOGOS|TOKEN_LOGOS|ARBITRUM" src/constants/rhino.consts.ts | head -50
echo "== BADGE_CODES / OFFRAMP_USER usages =="
rg -n "OFFRAMP_USER|BADGE_CODES" -g '!node_modules' -S
echo "== UTM_CAMPAIGN_TO_BADGE_MAP def =="
rg -n "UTM_CAMPAIGN_TO_BADGE_MAP|INVITE_CODE_TO_CAMPAIGN_MAP" -S | head -30
echo "== full page.tsx =="
cat -n "src/app/(mobile-ui)/add-money/crypto/page.tsx"
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
</br>
echo "== user.user.badges type =="
rg -n "badges" src/context/authContext.tsx src/interfaces -S | head -30

Length of output: 2086


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== BADGE_CODES / OFFRAMP_USER / badges usages =="
rg -n "OFFRAMP_USER|BADGE_CODES|badges" src -S | head -60
echo "== UTM_CAMPAIGN_TO_BADGE_MAP def =="
rg -n "UTM_CAMPAIGN_TO_BADGE_MAP|INVITE_CODE_TO_CAMPAIGN_MAP" src -S
echo "== full page.tsx =="
cat -n "src/app/(mobile-ui)/add-money/crypto/page.tsx"
echo "== ChooseNetworkDrawer usage in AddMoneyMethodSelection =="
sed -n '1,80p' src/components/AddMoney/views/AddMoneyMethodSelection.view.tsx

Length of output: 18387


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n src/components/Invites/campaign-maps.ts
echo "== users.ts badges type =="
sed -n '1,60p' src/services/users.ts
echo "== interfaces.ts badges type =="
sed -n '190,220p' src/interfaces/interfaces.ts
echo "== TOKEN_LOGOS full =="
sed -n '1,50p' src/constants/rhino.consts.ts

Length of output: 10544


@kushagrasarathe Reviewed all 4 changed files in depth (95/-31 lines). Summary below.

Risk of new bugs / breaking existing functionality — Low

  • The variant prop on CryptoDeposit.view.tsx defaults to 'default' and every offramp branch is additive (isOfframp ? ... : existingBehavior), so the non-offramp crypto flow is byte-for-byte unchanged when source is absent. Good.
  • The "Migrate from Offramp" card is purely additive and gated by hasOfframpBadge; non-badge-holders see the exact same list (just position shifts from 'first' to 'middle' for the Crypto card, cosmetically fine).
  • The InvitesPage.tsx fix is a one-line, targeted addition to the existing campaign resolution chain — low blast radius.
  • One real edge case worth a look: in page.tsx, network and source are independent query-state reads. The only production entry point hardcodes network=EVM&source=offramp, so it's unreachable in normal use — but if a user manually edits the URL to ?network=SOL&source=offramp (or TRON), CryptoDepositView will render "Arbitrum" copy/logo/labels while depositAddressData is actually created for the Solana/Tron deposit address (rhinoApi.createDepositAddress(..., network, ...) still uses the raw network param). That's a copy/address mismatch, not a fund-loss risk (Rhino still routes correctly to the real address shown), but it's confusing and easy to harden — e.g. force network to 'EVM' whenever isOfframp is true in page.tsx, rather than trusting the two params to travel together.

Performance / latency — No impact

  • hasOfframpBadge is a trivial .some() over a small badge array, computed on render, no memoization needed.
  • No new network calls; useQuery key/behavior for rhino-deposit-address unchanged.
  • useQueryState('source', ...) adds one more cheap URL-state subscription alongside the existing network one.

Code quality / DRY — Good, minor nit

  • OFFRAMP_BADGE_CODE = 'OFFRAMP_USER' as a hardcoded string with a "keep in sync with backend" comment matches the existing pattern already used elsewhere (e.g. useHomeCarouselCTAs.tsx hardcodes 'SUPPORT_SURVIVOR' the same way), so this isn't a regression in convention — just a pre-existing pattern being followed correctly.
  • CryptoDeposit.view.tsx now has ~8 isOfframp ? a : b branches inline in JSX. It's readable at this size, but if a third variant ever gets added, consider extracting a small variant-keyed config object (title, subtitle, tooltip, chain chip, token list) up top instead of scattering ternaries. Not blocking for this PR.
  • The InvitesPage.tsx resolution order comment was updated to explain the new lowercase-normalization step — good, keeps the "why" documented next to the logic.

Concise QA testing guide

  1. Badge grant (new user): Sign up via /invite?campaign=offramp (also test ?utm_campaign=offramp and invite code offramp) → confirm OFFRAMP_USER badge is awarded (no 400 from /badge/award).
  2. Gated entry: As a badge holder, open Add Money → confirm "Migrate from Offramp" card appears above "Crypto". As a non-badge user, confirm it does not appear and "Crypto" is back to position="first".
  3. Offramp surface: Tap "Migrate from Offramp" → confirm URL is /add-money/crypto?network=EVM&source=offramp and the view shows: header "Migrate from Offramp", single Arbitrum QR/address, "Your Arbitrum address" (no info tooltip), "Network: Arbitrum" (no supported-networks drawer/button), "Token: USDC" only, and the offramp-specific warning copy.
  4. Deposit + completion: Send USDC on Arbitrum to the shown address → confirm polling detects it, success screen shows, receipt/transaction details render correctly, and the PostHog deposit_completed event carries method_type: 'offramp_migration'.
  5. Regression check: Go through the normal Crypto flow (no source param, pick EVM/SOL/TRON from the network drawer) → confirm nothing changed — full multi-chain picker, tooltip, supported-networks modal, and multi-token list all still work, and completions still tag method_type: 'crypto'.
  6. Back navigation: From the offramp deposit screen, tap back → confirm it returns to Add Money (not into a broken state) and source doesn't leak into the network drawer flow.

…ted campaign resolution

Code-review + UX pass findings:
- pin network to EVM when source=offramp: a shared ?network=SOL&source=offramp
  link previously created a Solana deposit address rendered under Arbitrum-only
  labels — funds-confusion class
- migration-aware terminal states: success header 'Migration Complete', receipt
  fallbacks USDC/Arbitrum (was USDT + Ethereum icon when Rhino omits
  token/chain), failed-state copy points migrants back to their Offramp account
  instead of 'market moved / back to your wallet'
- retryable error state when deposit-address creation fails (was: screen
  instructs 'send USDC' with no address rendered at all — applies to the
  default crypto flow too)
- offramp-specific How to Migrate steps (generic modal said 'select one of the
  supported networks' — contradicting the single-network migration screen)
- 'Migration deposit address' + reuse-warning tooltip (was 'Your Arbitrum
  address', teaching users to save a constrained bridge endpoint as their
  wallet), per-transfer limit labels + split-into-parts hint
- post-claim routing: logged-in offramp claimants land on the migration screen,
  not /home
- campaign resolution extracted to resolveCampaign() in campaign-maps.ts with
  unit tests — encodes ?campaign= ≡ ?utm_campaign= deliberately; shared
  OFFRAMP_BADGE_CODE so the entry gate can't drift from the maps
@Hugo0

Hugo0 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Pushed review fixes in 8407e0a (code-review + UX pass, full context in Hugo's session):

Correctness

  • Pinned network to EVM when source=offramp — a shared/edited ?network=SOL&source=offramp link created a real Solana deposit address rendered under Arbitrum-only labels ("Migration deposit address", Arbitrum QR logo, "Send USDC on Arbitrum"). Funds-confusion class.
  • Retryable error state for deposit-address creation failure — previously the screen instructed "Send USDC…" while rendering no address at all. Benefits the default crypto flow too.

Migration-aware UX

  • Success screen: "Migration Complete" header; receipt fallbacks now USDC/Arbitrum for offramp (was USDT + Ethereum icon when Rhino's status omits token/chain).
  • Failed state: offramp copy points migrants back to their Offramp account ("market moved… back to your wallet" reads as funds-vanished to someone evacuating a shutting-down app).
  • "How to Migrate" modal steps written for offramp.xyz's withdraw flow (generic steps said "select one of the supported networks" — contradicting the single-network screen).
  • "Migration deposit address" + tooltip warning against saving it as a wallet address (was "Your Arbitrum address").
  • Per-transfer limit labels + "send in multiple transfers" hint for balances above max.
  • Post-claim routing: logged-in offramp claimants land directly on the migration screen instead of /home.

Code health

  • resolveCampaign() extracted to campaign-maps.ts with unit tests — encodes that ?campaign=<tag>?utm_campaign=<tag> deliberately (side effect worth knowing: ?campaign=alumni etc. now resolve like their utm twins; the utm forms already granted the same badges, so no new capability).
  • Shared OFFRAMP_BADGE_CODE const — entry gate can't drift from the campaign maps.

Local gate: typecheck ✅, jest 106/106 suites (1649 tests) ✅. cc @kushagrasarathe

@Hugo0 Hugo0 merged commit 357728d into main Jul 3, 2026
22 of 24 checks passed
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