feat: integrate Sei (eip155:1329) as second-class citizen via Relay#11928
feat: integrate Sei (eip155:1329) as second-class citizen via Relay#11928
Conversation
Add Sei EVM chain support including: - CAIP identifiers (seiChainId, seiAssetId) - HDWallet support flags across all wallet implementations - Chain adapter (SeiChainAdapter extending EvmBaseAdapter) - Viem client and ethers provider configuration - Feature flag (VITE_FEATURE_SEI) with dev-only enablement - Plugin registration and wallet support hooks - CoinGecko adapter with sei-v2 platform mapping - Zerion chain mapping - Relay swapper chain mapping - CSP headers for RPC endpoint - Asset generation scripts - Market/portfolio/opportunities integration - Popular assets discoverability Part of #11902
📝 WalkthroughWalkthroughThis PR introduces comprehensive Sei mainnet support across the application by adding environment variables, CSP headers, CAIP constants, chain adapters, HDWallet capability flags, Coingecko mappings, asset definitions, and plugin registration with feature flag gating to control availability. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
packages/utils/src/assetData/baseAssets.ts (1)
392-407: Nit:seiChainnaming is inconsistent with peer exports.Every other export in this file uses a plain lowercase chain name (
katana,monad,hyperevm,plasma,megaeth,solana, etc.). Renaming toseiwould align with the pattern, but there's no functional impact.♻️ Proposed rename
-export const seiChain: Readonly<Asset> = Object.freeze({ +export const sei: Readonly<Asset> = Object.freeze({And the corresponding update in
getBaseAsset.ts:-import { ..., seiChain, ... } from './baseAssets' +import { ..., sei, ... } from './baseAssets' ... - case KnownChainIds.SeiMainnet: return seiChain + case KnownChainIds.SeiMainnet: return sei🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/utils/src/assetData/baseAssets.ts` around lines 392 - 407, Rename the exported constant seiChain to match the file's lowercase chain-name pattern (rename export const seiChain: Readonly<Asset> to export const sei: Readonly<Asset>) and update all internal references/imports accordingly (e.g., any usage in getBaseAsset.ts and any maps or lookups that reference seiChain). Ensure the exported identifier is updated everywhere in the repo so type annotations and imports still resolve to the same Readonly<Asset> value and run type checks to catch missed references..env.development (1)
99-99: Optional: key ordering nit flagged by dotenv-linter.
VITE_FEATURE_SEIshould precedeVITE_FEATURE_WC_DIRECT_CONNECTIONalphabetically. No functional impact.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.development at line 99, Reorder the environment keys so VITE_FEATURE_SEI appears alphabetically before VITE_FEATURE_WC_DIRECT_CONNECTION in the .env.development file; specifically, move the VITE_FEATURE_SEI line so it is placed above the VITE_FEATURE_WC_DIRECT_CONNECTION entry to satisfy dotenv-linter's alphabetical key ordering check..env (1)
308-308: Optional: key ordering nit flagged by dotenv-linter.
VITE_FEATURE_SEIshould precedeVITE_FEATURE_SUNIO_SWAPalphabetically. No functional impact.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env at line 308, Reorder the environment keys so they are alphabetically sorted: move the VITE_FEATURE_SEI entry to come before VITE_FEATURE_SUNIO_SWAP; update the .env ordering only (no value changes) to satisfy dotenv-linter's alphabetical check.src/plugins/sei/index.tsx (1)
22-36:fromAssetIdis called twice per asset — once in thefilterand again in themap. A singlereducepass (orflatMapwith a combined predicate) would be more efficient.♻️ Suggested refactor
- const getKnownTokens = () => { - const assetService = getAssetService() - return assetService.assets - .filter(asset => { - const { chainId, assetNamespace } = fromAssetId(asset.assetId) - return chainId === seiChainId && 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<ReturnType<typeof sei.ChainAdapter.prototype.getKnownTokens extends () => infer R ? () => R : never>>((acc, asset) => { + // simpler: inline reduce + }, []) + }Or more simply:
const getKnownTokens = () => { const assetService = getAssetService() - return assetService.assets - .filter(asset => { - const { chainId, assetNamespace } = fromAssetId(asset.assetId) - return chainId === seiChainId && assetNamespace === 'erc20' - }) - .map(asset => ({ - assetId: asset.assetId, - contractAddress: fromAssetId(asset.assetId).assetReference, - symbol: asset.symbol, - name: asset.name, - precision: asset.precision, - })) + 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 === seiChainId && 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/sei/index.tsx` around lines 22 - 36, getKnownTokens currently calls fromAssetId twice per asset (once in the filter and again in the map), wasting CPU; update getKnownTokens to perform a single pass and call fromAssetId only once per asset—either replace the filter+map pair with a single reduce over assetService.assets or use map that first computes const info = fromAssetId(asset.assetId) and returns the token only when info.chainId === seiChainId && info.assetNamespace === 'erc20'; keep the same returned shape (assetId, contractAddress from info.assetReference, symbol, name, precision) and retain the use of getAssetService()/assetService.assets and the seiChainId constant.src/lib/utils/sei.ts (1)
10-17:isSeiChainAdapterduplicates the one already exported fromSeiChainAdapter.ts— the two differ in the checked method (getChainId()here vsgetType()there), null-handling, and return type. Consider importing and wrapping the package-level guard instead of re-implementing it.♻️ Suggested refactor
+import { isSeiChainAdapter as _isSeiChainAdapter } from '@shapeshiftoss/chain-adapters' export const isSeiChainAdapter = (chainAdapter: unknown): chainAdapter is EvmChainAdapter => { if (!chainAdapter) return false - - const maybeAdapter = chainAdapter as EvmChainAdapter - if (typeof maybeAdapter.getChainId !== 'function') return false - - return maybeAdapter.getChainId() === seiChainId + return _isSeiChainAdapter(chainAdapter) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/sei.ts` around lines 10 - 17, Replace this duplicate type-guard with a thin wrapper around the package-level guard exported from SeiChainAdapter.ts: import the existing isSeiChainAdapter from SeiChainAdapter.ts and have this module re-export or call that function instead of re-implementing it; ensure your wrapper handles falsy inputs identically (return false for null/undefined), and keep the same type predicate signature exported here (chainAdapter is EvmChainAdapter) so callers' types still narrow correctly while relying on the canonical implementation (which uses getType()) rather than the duplicated getChainId() check.
🤖 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/sei/SeiChainAdapter.ts`:
- Around line 18-20: The type-guard isSeiChainAdapter currently calls (adapter
as ChainAdapter).getType() without null/undefined protection and will throw for
nullish inputs; update isSeiChainAdapter to first check adapter is not
null/undefined and that (adapter as any).getType is a function before calling
it, then return whether getType() === KnownChainIds.SeiMainnet (referencing
isSeiChainAdapter, getType, and KnownChainIds.SeiMainnet in your change).
---
Nitpick comments:
In @.env:
- Line 308: Reorder the environment keys so they are alphabetically sorted: move
the VITE_FEATURE_SEI entry to come before VITE_FEATURE_SUNIO_SWAP; update the
.env ordering only (no value changes) to satisfy dotenv-linter's alphabetical
check.
In @.env.development:
- Line 99: Reorder the environment keys so VITE_FEATURE_SEI appears
alphabetically before VITE_FEATURE_WC_DIRECT_CONNECTION in the .env.development
file; specifically, move the VITE_FEATURE_SEI line so it is placed above the
VITE_FEATURE_WC_DIRECT_CONNECTION entry to satisfy dotenv-linter's alphabetical
key ordering check.
In `@packages/utils/src/assetData/baseAssets.ts`:
- Around line 392-407: Rename the exported constant seiChain to match the file's
lowercase chain-name pattern (rename export const seiChain: Readonly<Asset> to
export const sei: Readonly<Asset>) and update all internal references/imports
accordingly (e.g., any usage in getBaseAsset.ts and any maps or lookups that
reference seiChain). Ensure the exported identifier is updated everywhere in the
repo so type annotations and imports still resolve to the same Readonly<Asset>
value and run type checks to catch missed references.
In `@src/lib/utils/sei.ts`:
- Around line 10-17: Replace this duplicate type-guard with a thin wrapper
around the package-level guard exported from SeiChainAdapter.ts: import the
existing isSeiChainAdapter from SeiChainAdapter.ts and have this module
re-export or call that function instead of re-implementing it; ensure your
wrapper handles falsy inputs identically (return false for null/undefined), and
keep the same type predicate signature exported here (chainAdapter is
EvmChainAdapter) so callers' types still narrow correctly while relying on the
canonical implementation (which uses getType()) rather than the duplicated
getChainId() check.
In `@src/plugins/sei/index.tsx`:
- Around line 22-36: getKnownTokens currently calls fromAssetId twice per asset
(once in the filter and again in the map), wasting CPU; update getKnownTokens to
perform a single pass and call fromAssetId only once per asset—either replace
the filter+map pair with a single reduce over assetService.assets or use map
that first computes const info = fromAssetId(asset.assetId) and returns the
token only when info.chainId === seiChainId && info.assetNamespace === 'erc20';
keep the same returned shape (assetId, contractAddress from info.assetReference,
symbol, name, precision) and retain the use of
getAssetService()/assetService.assets and the seiChainId constant.
| export const isSeiChainAdapter = (adapter: unknown): adapter is ChainAdapter => { | ||
| return (adapter as ChainAdapter).getType() === KnownChainIds.SeiMainnet | ||
| } |
There was a problem hiding this comment.
isSeiChainAdapter will throw on null/undefined input — the adapter: unknown type does not preclude null, and (adapter as ChainAdapter).getType() will produce a TypeError without a null guard.
🛡️ Proposed fix
export const isSeiChainAdapter = (adapter: unknown): adapter is ChainAdapter => {
+ if (!adapter) return false
return (adapter as ChainAdapter).getType() === KnownChainIds.SeiMainnet
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chain-adapters/src/evm/sei/SeiChainAdapter.ts` around lines 18 - 20,
The type-guard isSeiChainAdapter currently calls (adapter as
ChainAdapter).getType() without null/undefined protection and will throw for
nullish inputs; update isSeiChainAdapter to first check adapter is not
null/undefined and that (adapter as any).getType is a function before calling
it, then return whether getType() === KnownChainIds.SeiMainnet (referencing
isSeiChainAdapter, getType, and KnownChainIds.SeiMainnet in your change).
Description
Integrate Sei EVM chain (eip155:1329) as a second-class citizen in ShapeShift Web via Relay.link bridge support.
Changes include:
seiChainId,seiAssetId,CHAIN_REFERENCE.SeiMainnet)SeiChainAdapterextendingEvmBaseAdapter)VITE_FEATURE_SEI) with dev-only enablementsei-v2platform mappingIssue (if applicable)
Part of #11902
Risk
Low risk — all changes are behind the
VITE_FEATURE_SEIfeature flag (disabled in production). No existing chains or transaction flows are affected.Testing
Engineering
.env.development(VITE_FEATURE_SEI=true)yarn lintandyarn type-check— both should passOperations
Screenshots (if applicable)
N/A — chain integration behind feature flag
Summary by CodeRabbit
Release Notes