Skip to content

feat: integrate Citrea (4114) as second-class EVM chain#12368

Draft
swdiscordia wants to merge 3 commits into
shapeshift:developfrom
swdiscordia:feat/citrea-chain-integration
Draft

feat: integrate Citrea (4114) as second-class EVM chain#12368
swdiscordia wants to merge 3 commits into
shapeshift:developfrom
swdiscordia:feat/citrea-chain-integration

Conversation

@swdiscordia
Copy link
Copy Markdown
Contributor

@swdiscordia swdiscordia commented May 21, 2026

Description

Integrate Citrea — the first Bitcoin L2 ZK-rollup (EVM-equivalent Type-2) — as a second-class EVM chain in ShapeShift. After this PR, users can hold and view their cBTC and Citrea-bridged tokens (USDC.e, USDT.e, WBTC.e, etc.) directly in the wallet behind the VITE_FEATURE_CITREA feature flag.

Chain facts

  • Chain ID: 4114 (CAIP eip155:4114) — verified live via eth_chainId0x1012
  • Native gas token: cBTC (18 decimals, BTC-pegged via Clementine BitVM, slip44:60)
  • Block explorer: https://explorer.mainnet.citrea.xyz (HTTP 200)
  • Public RPC: https://rpc.mainnet.citrea.xyz
  • Wrapped cBTC (WcBTC ERC-20): 0x3100000000000000000000000000000000000006 (system contract, deployed — verified via eth_getCode)

Scope
All Phase 1 / Phase 2 / Phase 3.5 / Phase 4 / Phase 4.5 points of .claude/contracts/second-class-evm-chain.md are covered:

  • CAIP constants, KnownChainIds, chain adapter (CitreaChainAdapter extends SecondClassEvmAdapter), discriminated unions
  • Plugin (src/plugins/citrea/index.tsx), feature flag, .env / .env.development
  • 10 HDWallet packages expose supportsCitrea() + _supportsCitrea (13 files touched in total: 9 wallet packages + hdwallet-metamask-multichain's 2 sub-impls + hdwallet-core's 2 shared abstraction files)
  • Viem client (inline defineChain — viem 2.43.5 ships citreaTestnet only, no mainnet definition), ethers provider returning PUBLIC_RPC_URLS.citrea[0] (second-class pattern), public RPC URL
  • Base asset, utility functions, CSP headers
  • Coingecko adapter (incl. generated eip155_4114/adapter.json), Citrea asset-data script
  • Persistor migration (335: clearAssets), vite env type declarations
  • State & UI: portfolio utils, wallet-supports hook, EVM account derivation, SECOND_CLASS_CHAINS, plugin provider, markets row, popular assets, asset service, opportunities mappings
  • Wrapped native contract registered in SecondClassEvmAdapter.WRAPPED_NATIVE_CONTRACT_BY_CHAIN_ID
  • Generated artifacts: asset-manifest.json, generatedAssetData.json, relatedAssetIndex.json (+ .br / .gz)
  • All additions verified append-only (Phase 4.5 audit — spot-checked on baseAssets.ts:861, caip/constants.ts:415, migrations/index.ts:384)

Related-asset mapping

  • cBTC is treated as its own primary asset (relatedAssetKey: null, no group) — matches BTC/AVAX/MNT/BERA convention. Aligns with Citrea team guidance ("cBTC is the main token of Citrea — like SOL for Solana").
  • WBTC.e (0xdf240dc...) had to be manually overridden in manualRelatedAssetIndex because Zerion's algo was mis-mapping it to ETH USDT ($1) instead of ETH WBTC ($77k) — material pricing bug if left uncorrected.

Phase 3 swapper / data-provider research (verified 2026-05-21):

  • Garden Finance — chain metadata + HTLC contract 0xE413743B51f3cC8b3ac24addf50D18fa138cB0Bb are pre-integrated in Garden's /v2/chains (evm:4114, native citrea:cbtc, is_active: true, min_amount: 0.0001 cBTC, max_amount: 5 cBTC), but no order_pair is activated server-side. Verified via /v2/quote on 8 representative pairs (bitcoin:btccitrea:cbtc, citrea:cbtcarbitrum:wbtc, starknet:strkbtccitrea:cbtc, base:cbbtccitrea:cbtc, ethereum:wbtccitrea:cbtc) — all return No order pair found. Routes can activate at any time via Garden backend config without requiring a code change in shapeshift/web. Note: the ShapeShift GardenSwapper integration is tracked in feat(swapper): integrate Garden Finance for BTC and wrapped BTC swaps #12355 (open, not yet merged).
  • Relay/chains returns 73 chains; chainId 4114 is not in the list. ❌ Not currently supported.
  • Portals/v2/networks does not list Citrea. ❌ Not currently supported.
  • Across — public /api/available-routes endpoint returned a parse error; inconclusive without further investigation. 🔍 Follow-up.
  • Zerion/v1/chains requires API auth for full enumeration; inconclusive from public endpoint. 🔍 Follow-up.
  • WalletConnect V2, Yield.xyz, Treasury (DAO) — not researched yet. 🔍 Follow-up.

No swap routes are usable today.

Out of scope (tracked separately)

Issue (if applicable)

closes #12367

Risk

High Risk PRs Require 2 approvals

HIGH-RISK — new chain integration touches:

  • All 10 HDWallet packages (new chain derivation surface; _supportsCitrea boolean + supportsCitrea() predicate)
  • New persistor migration (335: clearAssets)
  • Chain adapter (SecondClassEvmAdapter registered for chainId 4114)
  • Wrapped-native unwrap detection logic (cross-chain Tx parsing on incoming swaps to cBTC — relevant if/when a swapper enables Citrea)

Affected paths: HDWallet, CAIP, chain-adapters, contracts, asset-service, portfolio, plugin layer, persistor migrations.

Unaffected: existing swappers (no Citrea routes wired), existing chains' adapters, existing HDWallet derivations for other chains.

Please apply the high risk label (external contributor cannot add it directly — 403 on REST).

Testing

Engineering

Setup

  1. pnpm install
  2. Ensure VITE_FEATURE_CITREA=true in .env.development (default) and VITE_CITREA_NODE_URL=https://rpc.mainnet.citrea.xyz
  3. pnpm run dev

Verification checklist

  • Open chain selector → Citrea appears at the bottom (Phase 4.5 append-only)
  • Connect a hardware/software wallet → "My Assets" lists cBTC and any held Citrea tokens with correct icons (no perma-loading spinner)
  • Asset detail for cBTC → "Actifs connexes" section is empty / absent (cBTC standalone)
  • Asset detail for Citrea Bridged WBTC (WBTC.e) → "Actifs connexes" shows Wrapped Bitcoin on Ethereum (NOT USDT)
  • Trade modal "Popular Assets" filtered by Citrea → shows native + popular tokens without searching
  • Send small amount of cBTC to self → tx history shows confirmed (uses generic SecondClassEvmAdapter.getTransactionStatus)
  • Markets page filtered by Citrea → assets render
  • Type-check + lint pass: pnpm run type-check && pnpm run lint
  • pnpm run generate:chain eip155:4114 (with ZERION_API_KEY set) re-generates without diff in generatedAssetData.json / relatedAssetIndex.json (idempotent)

Math walkthrough — Zerion override correctness
Before fix: WBTC.e.relatedAssetKey = ETH USDT → UI used USDT price ($1) for a Bitcoin-pegged asset ($77k) → 77000× pricing error in portfolio display.
After fix: WBTC.e.relatedAssetKey = ETH WBTC → UI uses WBTC price (
$77k). Verified by inspecting public/generated/generatedAssetData.json after regen.

Operations

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

The chain is gated behind VITE_FEATURE_CITREA (default false in .env, default true in .env.development). Production rollout would be a separate config flip with QA on a preview environment.

Screenshots (if applicable)

N/A for this draft. UI is identical to other second-class chains (Mantle, Berachain, etc.) — the chain selector adds Citrea at the bottom; asset detail pages render the standard layout. Screenshots will be added before marking ready-for-review.

swdiscordia and others added 3 commits May 20, 2026 06:56
Adds Citrea Bitcoin L2 mainnet (chainId 4114) as a second-class EVM chain
behind the VITE_FEATURE_CITREA flag. Native gas token cBTC (18 decimals,
BTC-pegged via Clementine BitVM bridge). Standard contract Phase 1-2 items
implemented (CAIP, KnownChainIds, ChainAdapter, EvmBaseAdapter, Plugin,
FeatureFlag, 10x HDWallet, viem/ethers, baseAsset, utils, CSP, asset
generation, coingecko adapter, migration 335).

cBTC pricing falls back to WBTC mainnet via manualRelatedAssetIndex
(cBTC has no direct CoinGecko price as of mainnet launch).

Incidental fix: market-service-manager treats price=0 as not-found in
findByAssetId, so providers returning partial data with zero price don't
block the related-asset fallback. Symmetric with the existing
findPriceHistoryByAssetId behavior. Discovered during cBTC smoke testing.
…nd-class pattern

Returning `process.env.VITE_CITREA_NODE_URL` directly diverged from the
second-class chain convention (Mantle, Berachain, Sonic, etc. all return
`PUBLIC_RPC_URLS.<chain>[0]`). Switched to `PUBLIC_RPC_URLS.citrea[0]` to
align with the existing pattern at `ethersProviderSingleton.ts:30-84`.
The citrea entry was already present at `publicRpcUrls.ts:98`.

Refs shapeshift#12367

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two corrections to manualRelatedAssetIndex in both generateRelatedAssetIndex.ts
and generateChainRelatedAssetIndex.ts:

1. WBTC.e (Citrea Bridged WBTC at `0xdf240dc...`) was mis-mapped by Zerion's
   algo to ETH USDT — a ~$77k vs ~$1 pricing divergence in the UI. Added a
   manual override `'eip155:1/erc20:0x2260...': ['eip155:4114/erc20:0xdf240dc...']`
   so WBTC.e correctly resolves to the Ethereum Wrapped Bitcoin canonical.

2. Removed the prior `[citreaAssetId]: ['eip155:1/erc20:0x2260...']` entry
   that grouped cBTC under ETH WBTC. Per Citrea team input (cBTC is the main
   token of Citrea — analogous to SOL for Solana), cBTC is its own primary
   asset rather than a wrapped variant. This aligns with the convention
   already followed for BTC/AVAX/MNT/BERA natives (`relatedAssetKey: null`,
   no group). Adding `[citreaAssetId]: [btcAssetId]` was considered but would
   mutate BTC native's `relatedAssetKey` to cBTC at the line 422-427 group-
   update step, breaking BTC's primary status — unsafe.

Regenerated `public/generated/*` artifacts via
`pnpm run generate:chain eip155:4114`.

Final state verified:
- cBTC.relatedAssetKey = null (primary, standalone)
- WBTC.e.relatedAssetKey = ETH WBTC (in ETH WBTC group, +1 member)
- BTC native.relatedAssetKey = null (untouched, still primary)
- ETH WBTC.relatedAssetKey = self (canonical, unchanged)

Refs shapeshift#12367

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6a25aa7-304f-42f2-98c8-ffb144d3b029

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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.

Integrate Citrea chain (Bitcoin L2 zk-rollup, chain ID 4114)

1 participant