feat: integrate Mantle (chainId 5000) as second-class EVM chain#11905
feat: integrate Mantle (chainId 5000) as second-class EVM chain#11905gomesalexandre merged 15 commits intodevelopfrom
Conversation
…Relay bridge support Add support for Mantle (MNT native gas) including CAIP constants, chain adapter, plugin, feature flag, Relay swapper mapping, HDWallet support flags, CSP headers, asset generation script, and all required shared-file entries. Part of #11902
📝 WalkthroughWalkthroughAdds Mantle (eip155:5000) as a second-class EVM chain across the codebase: env/config, CAIP constants, viem/ethers clients, chain adapters, asset data and generators, swapper mappings, HDWallet capability flags/guards, plugins, transaction-status utilities, CSP, feature-flag gating, and project metadata. Changes
Sequence Diagram(s)sequenceDiagram
participant Subscriber as ActionCenterSubscriber
participant MantleUtil as MantleUtil
participant Node as MantleNode (VITE_MANTLE_NODE_URL)
participant Store as AppStore
Subscriber->>MantleUtil: getMantleTransactionStatus(txHash)
MantleUtil->>Node: JSON-RPC eth_getTransactionReceipt (txHash)
Node-->>MantleUtil: HTTP 200 + receipt / HTTP error
MantleUtil-->>Subscriber: TxStatus (Confirmed|Failed|Pending|Unknown)
Subscriber->>Store: dispatch status update
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Add src/lib/utils/mantle.ts with getMantleTransactionStatus using eth_getTransactionReceipt via the Mantle RPC. Add KnownChainIds.MantleMainnet case to useSendActionSubscriber.tsx so Mantle transactions resolve in the action center.
Address PR review feedback: - Add mantleChainId to getCoingeckoSupportedChainIds (feature-flagged) - Add mantle to ZERION_CHAINS array and ZERION_CHAINS_MAP - Across does not support Mantle, skipped
…tle-relay # Conflicts: # .env # .env.development # packages/caip/src/adapters/coingecko/generated/index.ts # packages/caip/src/adapters/coingecko/index.ts # packages/caip/src/adapters/coingecko/utils.test.ts # packages/caip/src/adapters/coingecko/utils.ts # packages/caip/src/constants.ts # packages/chain-adapters/src/evm/EvmBaseAdapter.ts # packages/chain-adapters/src/types.ts # packages/contracts/src/ethersProviderSingleton.ts # packages/contracts/src/viemClient.ts # packages/hdwallet-coinbase/src/coinbase.ts # packages/hdwallet-core/src/ethereum.ts # packages/hdwallet-core/src/wallet.ts # packages/hdwallet-gridplus/src/gridplus.ts # packages/hdwallet-keepkey/src/keepkey.ts # packages/hdwallet-ledger/src/ledger.ts # packages/hdwallet-metamask-multichain/src/shapeshift-multichain.ts # packages/hdwallet-native/src/ethereum.ts # packages/hdwallet-phantom/src/phantom.ts # packages/hdwallet-trezor/src/trezor.ts # packages/hdwallet-vultisig/src/vultisig.ts # packages/hdwallet-walletconnectv2/src/walletconnectV2.ts # packages/swapper/src/swappers/RelaySwapper/constant.ts # packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts # packages/types/src/base.ts # packages/types/src/zerion.ts # packages/utils/src/assetData/baseAssets.ts # packages/utils/src/assetData/getBaseAsset.ts # packages/utils/src/chainIdToFeeAssetId.ts # packages/utils/src/getAssetNamespaceFromChainId.ts # packages/utils/src/getChainShortName.ts # packages/utils/src/getNativeFeeAssetReference.ts # scripts/generateAssetData/coingecko.ts # scripts/generateAssetData/generateAssetData.ts # src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx # src/config.ts # src/constants/chains.ts # src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx # src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts # src/lib/account/evm.ts # src/lib/asset-service/service/AssetService.ts # src/lib/coingecko/utils.ts # src/pages/Markets/components/MarketsRow.tsx # src/state/slices/portfolioSlice/utils/index.ts # src/state/slices/preferencesSlice/preferencesSlice.ts # src/test/mocks/store.ts # src/vite-env.d.ts
Missing closing braces in 7 files where auto-resolve stripped them at mantle/next-entry boundaries. Also made generateChainRelatedAssetIndex self-contained (no import from generateRelatedAssetIndex to avoid module-scope ZERION_API_KEY check). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 Mantle assets added with relatedAssetKey cross-chain linking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gomesalexandre
left a comment
There was a problem hiding this comment.
- Swaps from/to MNT/tokens happy
- Popular assets gucci
- Same like Berachain previously, cross-chain swaps to MNT don't have an execution price nor MNT entry - probably the same lack of trace debugging in RPCs and
parseTx()kind of fix re:un/wrapping
https://jam.dev/c/7f3d3fa9-100e-469d-8c85-417b255ae6de
Prompts for AI agents:
Look into the Berachain commit. Notice how we circumvented the lack of trace debugging to be able to upsert second-class Txs for BERA by manually detecting un/wrapping without a need for trace debug.
The fix is most likely similar here, look at the Txx below, figure out the ABI/patterns
Initiating Relay Tx on Ethereum: https://etherscan.io/tx/0xee22f39981ca5199eb76b57507b23458d0278b3df4259c2876801778dd3d658a
Receive Tx on Mantle: https://mantlescan.xyz/tx/0x90d278e7c30e6d15902e50b6940c780bf27dc2251fef8d0a1dcd04771ce4fffe
Notice WMNT which is a similar pattern to WBERA
Make no mistakes you degen, commit, let user fix, push after they confirmed worky or iterate with them
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
.beads/ss-dx5.3.json (1)
20-36: Consider trimming the inlined parent epic notes in child task dependencies.The dependency entry for
ss-dx5embeds the entire epic's procedural documentation (~3KB) verbatim. If multiple child task beads reference this same epic, this content is duplicated in each.beads/*.jsonfile and must be kept in sync manually. Consider keeping only the reference fields (id,title,dependency_type) here and relying onss-dx5's own bead file as the single source of truth for its notes/description.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.beads/ss-dx5.3.json around lines 20 - 36, The dependency entry for "ss-dx5" currently in the child bead embeds the full epic notes; replace the verbose fields with a minimal reference: keep "id": "ss-dx5", "title": "Add support for missing Relay.link EVM chains", and "dependency_type": "parent-child" (optionally include "status" or "external_ref" if needed), and remove/trim "description", "notes", and large freeform fields so the epic's full documentation remains only in the ss-dx5 bead file; update any other child beads that reference this epic to follow the same minimal pattern so the parent bead is the single source of truth..env.development (1)
65-65: Fix key ordering reported bydotenv-linterTwo
UnorderedKeywarnings from the static analysis tool:
VITE_MANTLE_NODE_URL(line 65) should be placed beforeVITE_MONAD_NODE_URL(alphabetical ordering within the nodes block).VITE_FEATURE_MANTLE(line 102) should be placed beforeVITE_FEATURE_WC_DIRECT_CONNECTION(alphabetical ordering within the feature flags block).🔧 Proposed ordering fix
-VITE_MONAD_NODE_URL=https://rpc.monad.xyz -VITE_PLASMA_NODE_URL=https://rpc.plasma.to -VITE_KATANA_NODE_URL=https://rpc.katana.network -VITE_HYPEREVM_NODE_URL=https://rpc.hyperliquid.xyz/evm -VITE_MANTLE_NODE_URL=https://rpc.mantle.xyz +VITE_MANTLE_NODE_URL=https://rpc.mantle.xyz +VITE_MONAD_NODE_URL=https://rpc.monad.xyz +VITE_PLASMA_NODE_URL=https://rpc.plasma.to +VITE_KATANA_NODE_URL=https://rpc.katana.network +VITE_HYPEREVM_NODE_URL=https://rpc.hyperliquid.xyz/evm-VITE_FEATURE_WC_DIRECT_CONNECTION=true -VITE_FEATURE_CETUS_SWAP=true -VITE_FEATURE_MANTLE=true +VITE_FEATURE_MANTLE=true +VITE_FEATURE_WC_DIRECT_CONNECTION=true +VITE_FEATURE_CETUS_SWAP=trueAlso applies to: 102-102
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.development at line 65, Reorder environment keys to satisfy dotenv-linter alphabetical rules: move VITE_MANTLE_NODE_URL so it appears alphabetically before VITE_MONAD_NODE_URL within the node URLs block, and move VITE_FEATURE_MANTLE so it appears alphabetically before VITE_FEATURE_WC_DIRECT_CONNECTION within the feature flags block; ensure no other keys or comments are changed and run dotenv-linter to confirm UnorderedKey warnings are resolved.src/plugins/mantle/index.tsx (1)
22-36: Minor:fromAssetIdis called twice per matching asset.The filter at line 26 and map at line 31 each call
fromAssetIdon the sameasset.assetId. Consider consolidating into a single pass (e.g.,flatMaporreduce) to avoid the redundant parse. That said, this matches other plugin files so it's fine to defer.♻️ Optional: single-pass approach
- const getKnownTokens = () => { - const assetService = getAssetService() - return assetService.assets - .filter(asset => { - const { chainId, assetNamespace } = fromAssetId(asset.assetId) - return chainId === mantleChainId && assetNamespace === 'erc20' - }) - .map(asset => ({ - assetId: asset.assetId, - contractAddress: fromAssetId(asset.assetId).assetReference, - symbol: asset.symbol, - name: asset.name, - precision: asset.precision, - })) - } + const getKnownTokens = () => { + const assetService = getAssetService() + return assetService.assets.reduce< + { + assetId: string + contractAddress: string + symbol: string + name: string + precision: number + }[] + >((acc, asset) => { + const { chainId, assetNamespace, assetReference } = fromAssetId(asset.assetId) + if (chainId === mantleChainId && assetNamespace === 'erc20') { + acc.push({ + assetId: asset.assetId, + contractAddress: assetReference, + symbol: asset.symbol, + name: asset.name, + precision: asset.precision, + }) + } + return acc + }, []) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/plugins/mantle/index.tsx` around lines 22 - 36, getKnownTokens currently calls fromAssetId twice per asset (once in the filter and again in the map), causing redundant parsing; fix by parsing each assetId once (e.g., transform the asset list into [{ asset, parsed }] via map or use reduce/flatMap) so you can check parsed.chainId and parsed.assetNamespace against mantleChainId in a single pass and then build the returned object using parsed.assetReference for contractAddress — change getKnownTokens to use a single parse call to fromAssetId per asset obtained from getAssetService().assets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chain-adapters/src/evm/mantle/MantleChainAdapter.ts`:
- Around line 18-20: The isMantleChainAdapter type guard must defensively handle
null/undefined and objects missing getType; update isMantleChainAdapter to first
ensure adapter is non-null and that (adapter as any).getType is a function
before calling it, then compare the returned type to KnownChainIds.MantleMainnet
(i.e., add checks like adapter != null && typeof (adapter as any).getType ===
'function' prior to (adapter as ChainAdapter).getType()).
In
`@scripts/generateAssetData/generateRelatedAssetIndex/generateChainRelatedAssetIndex.ts`:
- Around line 65-68: Add an inline Gitleaks suppression for the Solana mint
literal on the array value for [starknetAssetId]; locate the array entry in
generateChainRelatedAssetIndex where [starknetAssetId] maps to the two CAIP-19
strings (the Ethereum ERC-20
'eip155:1/erc20:0xca14007eff0db1f8135f4c25b34de49ab0d42766' and the Solana token
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:HsRpHQn6VbyMs5b5j5SV6xQ2VvpvvCCzu19GjytVSCoz')
and add a single-line suppression comment (// gitleaks:allow) adjacent to the
Solana token literal to prevent Gitleaks from flagging the known CAIP-19 mint as
a secret.
In `@src/lib/utils/mantle.ts`:
- Around line 7-14: There are two conflicting isMantleChainAdapter definitions
(one typing as EvmChainAdapter using getChainId, and another typing as
ChainAdapter using getType) — remove the duplicate and consolidate to a single
exported utility: keep the defensive utility version named isMantleChainAdapter
(the one that narrows to EvmChainAdapter and calls getChainId), delete the
duplicate definition in the adapter file, and update exports/imports so only the
single consolidated isMantleChainAdapter is exported/used across the codebase;
if you prefer the adapter-file implementation, instead replace the utility
implementation with the adapter one and ensure its signature and usage (getType
vs getChainId) are consistent everywhere.
---
Nitpick comments:
In @.beads/ss-dx5.3.json:
- Around line 20-36: The dependency entry for "ss-dx5" currently in the child
bead embeds the full epic notes; replace the verbose fields with a minimal
reference: keep "id": "ss-dx5", "title": "Add support for missing Relay.link EVM
chains", and "dependency_type": "parent-child" (optionally include "status" or
"external_ref" if needed), and remove/trim "description", "notes", and large
freeform fields so the epic's full documentation remains only in the ss-dx5 bead
file; update any other child beads that reference this epic to follow the same
minimal pattern so the parent bead is the single source of truth.
In @.env.development:
- Line 65: Reorder environment keys to satisfy dotenv-linter alphabetical rules:
move VITE_MANTLE_NODE_URL so it appears alphabetically before
VITE_MONAD_NODE_URL within the node URLs block, and move VITE_FEATURE_MANTLE so
it appears alphabetically before VITE_FEATURE_WC_DIRECT_CONNECTION within the
feature flags block; ensure no other keys or comments are changed and run
dotenv-linter to confirm UnorderedKey warnings are resolved.
In `@src/plugins/mantle/index.tsx`:
- Around line 22-36: getKnownTokens currently calls fromAssetId twice per asset
(once in the filter and again in the map), causing redundant parsing; fix by
parsing each assetId once (e.g., transform the asset list into [{ asset, parsed
}] via map or use reduce/flatMap) so you can check parsed.chainId and
parsed.assetNamespace against mantleChainId in a single pass and then build the
returned object using parsed.assetReference for contractAddress — change
getKnownTokens to use a single parse call to fromAssetId per asset obtained from
getAssetService().assets.
scripts/generateAssetData/generateRelatedAssetIndex/generateChainRelatedAssetIndex.ts
Show resolved
Hide resolved
|
re: gitleaks false positive on the Solana SPL token mint - ser you're drunk @coderabbitai, that's a CAIP-19 asset ID not an API key. No suppression comment needed, we don't run gitleaks in strict mode. |
…ract - Replace Berachain-only WBERA burn detection with generalized WRAPPED_NATIVE_CONTRACT_BY_CHAIN_ID mapping in SecondClassEvmAdapter - Add WMNT address for Mantle cross-chain swap native receives - Fix Linea networkIcon URL (CoinGecko 403 -> relay.link CDN) - Update second-class-evm-chain contract with wrapped native, icon validation, and append-only convention sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts (1)
420-438:⚠️ Potential issue | 🟡 MinorSynthesized internal tx unconditionally sets
to: pubkey— may produce a spurious Receive in one edge case.When
internalTxs.length === 0and aWMNT → zeroAddressTransfer event is found, the synthesized entry hardcodesto: getAddress(pubkey). The downstreamparse()method then checksisAddressEqual(address, internalTo), whereinternalTowill always equaladdress(they are both derived frompubkey), so the user always sees aReceivefor that value.The intended scenario — user receives native MNT via a Relay cross-chain swap — is handled correctly. However, if the user is the sender of a transaction that incidentally triggers a WMNT burn for a different final recipient (e.g., a contract they called that unwraps WMNT and sends MNT to a third party), parseTx would generate a spurious
Receiveentry for the user.This is the same limitation already accepted for Berachain, and the feature is gated behind
VITE_FEATURE_MANTLE. Worth keeping in mind for future hardening (e.g., cross-checkinglog.args.fromagainst the transaction participants).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts` around lines 420 - 438, The synthesized internalTx currently hardcodes to: getAddress(pubkey) for any WMNT -> zeroAddress burn, causing spurious Receive entries; change the logic in SecondClassEvmAdapter where wrappedNativeBurnLogs are processed (use symbols WRAPPED_NATIVE_CONTRACT_BY_CHAIN_ID, wrappedNativeBurnLogs, internalTxs, parseEventLogs, erc20Abi, getAddress, pubkey, log.args.from) so you only push a synthesized internalTx with to: getAddress(pubkey) when the burn's sender/participant matches the transaction participant (e.g., require isAddressEqual(getAddress(log.args.from), getAddress(pubkey)) or the tx.from/other transaction participants include that address); otherwise skip adding the synthesized internalTx (or populate a more accurate recipient if available) so parse() won't produce a false Receive for the user.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/chain-adapters/src/evm/SecondClassEvmAdapter.ts`:
- Around line 420-438: The synthesized internalTx currently hardcodes to:
getAddress(pubkey) for any WMNT -> zeroAddress burn, causing spurious Receive
entries; change the logic in SecondClassEvmAdapter where wrappedNativeBurnLogs
are processed (use symbols WRAPPED_NATIVE_CONTRACT_BY_CHAIN_ID,
wrappedNativeBurnLogs, internalTxs, parseEventLogs, erc20Abi, getAddress,
pubkey, log.args.from) so you only push a synthesized internalTx with to:
getAddress(pubkey) when the burn's sender/participant matches the transaction
participant (e.g., require isAddressEqual(getAddress(log.args.from),
getAddress(pubkey)) or the tx.from/other transaction participants include that
address); otherwise skip adding the synthesized internalTx (or populate a more
accurate recipient if available) so parse() won't produce a false Receive for
the user.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/contracts/second-class-evm-chain.md:
- Around line 171-188: The numbered checklist in
.claude/contracts/second-class-evm-chain.md is out of order (items labeled 42,
then 44, then 43); update the item numbers so they increment sequentially in
document order—specifically, rename the "44. **Blacklist native ERC20 wrapper**"
entry to "43." and rename the subsequent "43." entry (Phase 4.5) to "44."
(ensure any internal references or cross-links to
WRAPPED_NATIVE_CONTRACT_BY_CHAIN_ID, the blacklist.json guidance, or Phase
numbers are updated accordingly).
- fix early-return bug in processRelatedAssetIds: assets with relatedAssetKey set but not in the group array now get added - fix existing group merge: when CoinGecko/Zerion fail but relatedAssetKey points to an existing group, merge the asset into that group instead of setting null - add manual stablecoin mappings for Linea + Mantle native USDC/USDT/DAI to manualRelatedAssetIndex - update second-class-evm-chain contract with stablecoin mapping step - regen Linea + Mantle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Canonical defensive version lives in src/lib/utils/mantle.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gomesalexandre
left a comment
There was a problem hiding this comment.
both linea and mantle are happy campers now UwU
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@scripts/generateAssetData/generateRelatedAssetIndex/generateChainRelatedAssetIndex.ts`:
- Around line 260-274: The code silently returns when existingRelatedAssetKey is
set but relatedAssetIndex[existingRelatedAssetKey] is undefined, leaving an
orphaned key; change the logic inside the existingRelatedAssetKey branch in
generateChainRelatedAssetIndex.ts so that if group is falsy you create and
assign a new group containing assetId (e.g.,
relatedAssetIndex[existingRelatedAssetKey] = [assetId]) and optionally log the
recovery, while keeping the existing checks that avoid duplicate inserts when
the asset is already present.
---
Duplicate comments:
In
`@scripts/generateAssetData/generateRelatedAssetIndex/generateChainRelatedAssetIndex.ts`:
- Around line 65-68: The suppressing Gitleaks comment is unnecessary for the
Solana CAIP-19 mint entry inside the mapping keyed by starknetAssetId in
generateChainRelatedAssetIndex.ts; remove any gitleaks suppression
annotation/comment around the Solana asset string (the array value for
[starknetAssetId]) so the file contains only the CAIP-19 IDs without a
suppression note.
scripts/generateAssetData/generateRelatedAssetIndex/generateChainRelatedAssetIndex.ts
Show resolved
Hide resolved
Emulates squash merge of #11905 into develop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Description
Add support for Mantle (EVM chain, chainId 5000) as a second-class citizen. Mantle is an Optimistic Rollup L2 with MNT as the native gas token.
Implements: CAIP constants, chain adapter, plugin, feature flag, Relay swapper mapping, HDWallet support flags, CSP headers, asset generation script, and all required shared-file entries.
This is PR 1 of 17 in a sequential chain integration series. These PRs must be reviewed and merged in order, as each builds on the previous one (stacked branches).
PR merge order:
Issue (if applicable)
Part of #11902
Risk
Low - All changes are behind the
Mantlefeature flag (VITE_FEATURE_MANTLE), disabled by default in production.No new on-chain transactions or contract interactions. Standard EVM chain support using existing SecondClassEvmAdapter pattern.
Testing
Engineering
VITE_FEATURE_MANTLE=truein.env.developmentyarn devOperations
Summary by CodeRabbit
New Features
Assets
Configuration
Chores