chain-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Chain Integration Skill

链集成技能

You are helping integrate a new blockchain as a second-class citizen into ShapeShift Web and HDWallet. This means basic support (native asset send/receive, account derivation, swaps to/from the chain) using the "poor man's" approach similar to Monad, Tron, and Sui - public RPC, no microservices, minimal features.
你正在协助将新区块链作为二等公民集成到ShapeShift Web和HDWallet中。这意味着采用“简易方案”提供基础支持(原生资产收发、账户派生、与该链的跨链兑换),类似Monad、Tron和Sui的集成方式——使用公共RPC,无微服务,仅提供最小功能集。

When This Skill Activates

何时激活此技能

Use this skill when the user wants to:
  • "Add support for [ChainName]"
  • "Integrate [ChainName] as second-class citizen"
  • "Implement basic [ChainName] support"
  • "Add [ChainName] with native wallet only"
当用户需要以下操作时使用此技能:
  • "添加[ChainName]支持"
  • "将[ChainName]集成为二等公民链"
  • "实现[ChainName]基础支持"
  • "仅添加原生钱包支持的[ChainName]"

Critical Understanding

核心理解

Second-Class Citizen Pattern

二等公民链模式

Recent examples: Monad (EVM), Tron (UTXO-like), Sui (non-EVM)
What it includes:
  • ✅ Native asset sends/receives
  • ✅ Account derivation (Native wallet required, Ledger optional)
  • ✅ Swap to/from the chain
  • ✅ Poor man's balance updates (public RPC polling)
  • ✅ Poor man's tx status (RPC polling with eth_getTransactionReceipt or equivalent)
  • ✅ Feature flag gating
What it DOESN'T include:
  • ❌ Full transaction history (no microservices)
  • ❌ First-class Unchained API support
  • ❌ Advanced features (staking, DeFi, etc.)
  • ❌ All wallet support (usually just Native initially)
近期示例:Monad(EVM兼容)、Tron(类UTXO)、Sui(非EVM)
包含功能
  • ✅ 原生资产收发
  • ✅ 账户派生(需原生钱包,Ledger支持可选)
  • ✅ 与该链的跨链兑换
  • ✅ 简易余额更新(公共RPC轮询)
  • ✅ 简易交易状态查询(使用eth_getTransactionReceipt或等效方法的RPC轮询)
  • ✅ 功能标志管控
不包含功能
  • ❌ 完整交易历史(无微服务)
  • ❌ 一等公民Unchained API支持
  • ❌ 高级功能(质押、DeFi等)
  • ❌ 全钱包支持(通常初始仅支持原生钱包)

Development Flow

开发流程

ALWAYS follow this order:
  1. HDWallet Native Support (workspace packages under
    packages/hdwallet-*
    )
  2. Web Basic Support (poor man's chain adapter)
  3. Web Plugin & Integration (wire everything up)
  4. Ledger Support (
    packages/hdwallet-ledger
    - if chain is supported by Ledger)
必须遵循以下顺序
  1. HDWallet原生支持(工作区包位于
    packages/hdwallet-*
  2. Web基础支持(简易链适配器)
  3. Web插件与集成(连接所有组件)
  4. Ledger支持
    packages/hdwallet-ledger
    - 若该链受Ledger支持)

Phase 0: Deep Research & Information Gathering

阶段0:深度调研与信息收集

CRITICAL: This phase determines the entire integration strategy. Take time to research thoroughly.
关键:此阶段决定整个集成策略,请花时间彻底调研。

Step 0.1: Initial Chain Discovery

步骤0.1:初始链发现

First, search for basic chain information:
  1. Search for official chain website and docs
    • Use WebSearch to find: "[ChainName] blockchain official website"
    • Look for: developer docs, whitepaper, GitHub repos
  2. Determine chain architecture
    • CRITICAL QUESTION: Is this an EVM-compatible chain?
    • Search: "[ChainName] EVM compatible"
    • Look for keywords: "Ethereum Virtual Machine", "Solidity", "EVM-compatible", "Ethereum fork"
    • Check if they mention Metamask compatibility
  3. Find RPC endpoints
Why this matters:
  • EVM chains (like Monad): 90% less code! Just add to EVM chains list. Auto-supported by all EVM wallets.
  • Non-EVM chains (like Tron, Sui): Need full custom implementation with crypto adapters.
首先,搜索基础链信息
  1. 搜索官方链网站和文档
    • 使用WebSearch查找:"[ChainName] blockchain official website"
    • 查找:开发者文档、白皮书、GitHub仓库
  2. 确定链架构
    • 核心问题:该链是否兼容EVM?
    • 搜索:"[ChainName] EVM compatible"
    • 查找关键词:"Ethereum Virtual Machine"、"Solidity"、"EVM-compatible"、"Ethereum fork"
    • 检查是否提到Metamask兼容性
  3. 查找RPC端点
重要性
  • EVM兼容链(如Monad):代码量减少90%!只需添加到EVM链列表中,所有EVM钱包自动支持。
  • 非EVM链(如Tron、Sui):需要完整的自定义实现,包括加密适配器。

Step 0.2: Interactive Information Gathering

步骤0.2:交互式信息收集

Use the
AskUserQuestion
tool with the Claude inquiry UI to gather information.
Question 1 - Chain Architecture (MOST IMPORTANT):
Does the user know if this is an EVM-compatible chain?

Options:
1. "Yes, it's EVM-compatible" → Proceed with EVM integration path (much simpler!)
2. "No, it's a custom blockchain" → Proceed with non-EVM integration path
3. "Not sure - can you research it?" → Perform web research (search for EVM compatibility indicators)

Context: EVM-compatible chains like Monad require minimal code changes (just add to supported chains list). Non-EVM chains like Tron/Sui require full custom crypto adapters.
Question 2 - RPC Endpoint:
Do you have a public RPC endpoint URL?

Options:
1. "Yes, here's the URL: [input]" → Use provided URL
2. "No, can you find one?" → Search ChainList.org, official docs, and GitHub for public RPC
3. "Need both HTTP and WebSocket" → Search for both endpoint types

Context: We need a reliable public RPC for the poor man's chain adapter. WebSocket is optional but nice for real-time updates.
Question 3 - SLIP44 Coin Type:
Do you know the SLIP44 coin type (BIP44 derivation path)?

Options:
1. "Yes, it's [number]" → Use provided coin type
2. "No, can you look it up?" → Search SLIP44 registry: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
3. "Use the same as Ethereum (60)" → Common for EVM chains

Context: This determines the BIP44 derivation path: m/44'/[TYPE]'/0'/0/0
使用
AskUserQuestion
工具和Claude查询UI收集信息
问题1 - 链架构(最重要):
用户是否知道该链是否兼容EVM?

选项:
1. "是,它兼容EVM" → 采用EVM集成路径(简单得多!)
2. "否,它是自定义区块链" → 采用非EVM集成路径
3. "不确定 - 你能调研一下吗?" → 进行网络调研(查找EVM兼容性指标)

背景:像Monad这样的EVM兼容链只需极少代码变更(仅添加到支持链列表)。像Tron/Sui这样的非EVM链需要完整的自定义加密适配器。
问题2 - RPC端点
你有公共RPC端点URL吗?

选项:
1. "有,URL是:[输入内容]" → 使用提供的URL
2. "没有,你能找一个吗?" → 搜索ChainList.org、官方文档和GitHub获取公共RPC
3. "需要HTTP和WebSocket两种" → 搜索两种类型的端点

背景:我们需要可靠的公共RPC来实现简易链适配器。WebSocket是可选的,但有助于实时更新。
问题3 - SLIP44币种类型
你知道SLIP44币种类型(BIP44派生路径)吗?

选项:
1. "知道,是[数字]" → 使用提供的币种类型
2. "不知道,你能查一下吗?" → 搜索SLIP44注册表:https://github.com/satoshilabs/slips/blob/master/slip-0044.md
3. "使用与以太坊相同的(60)" → EVM链常用此设置

背景:这决定了BIP44派生路径:m/44'/[TYPE]'/0'/0/0

Step 0.3: Structured Information Collection

步骤0.3:结构化信息收集

After determining chain type (EVM or non-EVM), collect remaining details:
Use
AskUserQuestion
to ask:
For ALL chains:
  1. Chain Basic Info
    • Chain name (exact capitalization, e.g., "Monad", "Tron", "Sui")
    • SLIP44 coin type (from Step 0.2 above)
    • Chain ID (numeric or string, e.g., "1" for Ethereum, "monad-1", etc.)
  2. Documentation Links
    • Official website URL
    • Developer documentation URL
    • Block explorer URL
    • GitHub repository (if available)
  3. Asset Information
    • Native asset symbol (e.g., MON, TRX, SUI)
    • Native asset name (e.g., "Monad", "Tron", "Sui")
    • Decimals/precision (usually 18 for EVM, varies for others)
    • CoinGecko ID (search: "coingecko [chainname]" or ask user)
For EVM chains only:
  1. EVM-Specific Info
    • Network/Chain ID (numeric, e.g., 41454 for Monad)
    • Token standard: ERC20 (always)
    • Block explorer API (etherscan-like)?
    • Any non-standard behavior vs Ethereum?
For non-EVM chains only:
  1. Chain Architecture Details
    • Transaction structure/format (link to docs)
    • Signing algorithm (secp256k1, ed25519, etc.)
    • Address format (base58, bech32, hex, etc.)
    • Official SDK (npm package name if available)
    • Token standard name (e.g., "TRC20", "SUI Coin", "SPL")
  2. Ledger Hardware Wallet Support
Action: Don't proceed until you have:
  • ✅ Confirmed EVM vs non-EVM architecture
  • ✅ At least one working RPC endpoint
  • ✅ SLIP44 coin type
  • ✅ Official documentation links
  • ✅ Basic asset information (symbol, name, decimals)
Pro Tips:
  • For EVM chains: Integration is 10x easier. You mostly just add constants.
  • For non-EVM: Budget extra time for crypto adapter implementation.
  • Missing RPC? Check ChainList.org, official Discord, or GitHub repos.
  • Missing SLIP44? Check if it's in SLIP-0044 registry or propose one.
  • Can't find CoinGecko ID? Search their API or website directly.

确定链类型(EVM或非EVM)后,收集剩余细节:
使用
AskUserQuestion
询问
所有链通用
  1. 链基础信息
    • 链名称(准确大小写,例如"Monad"、"Tron"、"Sui")
    • SLIP44币种类型(来自步骤0.2)
    • 链ID(数字或字符串,例如以太坊的"1"、"monad-1"等)
  2. 文档链接
    • 官方网站URL
    • 开发者文档URL
    • 区块浏览器URL
    • GitHub仓库(若有)
  3. 资产信息
    • 原生资产符号(如MON、TRX、SUI)
    • 原生资产名称(如"Monad"、"Tron"、"Sui")
    • 小数位数/精度(EVM链通常为18,其他链可能不同)
    • CoinGecko ID(搜索:"coingecko [chainname]"或询问用户)
仅EVM链
  1. EVM特定信息
    • 网络/链ID(数字,例如Monad的41454)
    • 代币标准:ERC20(固定)
    • 区块浏览器API(类似etherscan?)
    • 与以太坊相比有任何非标准行为吗?
仅非EVM链
  1. 链架构细节
    • 交易结构/格式(文档链接)
    • 签名算法(secp256k1、ed25519等)
    • 地址格式(base58、bech32、hex等)
    • 官方SDK(npm包名称,若有)
    • 代币标准名称(如"TRC20"、"SUI Coin"、"SPL")
  2. Ledger硬件钱包支持
行动要求:在获取以下信息前不要继续:
  • ✅ 确认EVM vs 非EVM架构
  • ✅ 至少一个可用的RPC端点
  • ✅ SLIP44币种类型
  • ✅ 官方文档链接
  • ✅ 基础资产信息(符号、名称、小数位数)
提示
  • 对于EVM链:集成难度低10倍,主要只需添加常量。
  • 对于非EVM链:为加密适配器实现预留额外时间。
  • 缺少RPC?检查ChainList.org、官方Discord或GitHub仓库。
  • 缺少SLIP44?检查是否在SLIP-0044注册表中,或提议一个。
  • 找不到CoinGecko ID?直接搜索其API或网站。

Integration Path Decision

集成路径决策

Based on Phase 0 research, choose your path:
基于阶段0的调研结果,选择路径

Path A: EVM Chain Integration (SIMPLE)

路径A:EVM链集成(简单)

Examples: Monad, Base, Arbitrum, Optimism
Characteristics:
  • ✅ Uses Ethereum Virtual Machine
  • ✅ Solidity smart contracts
  • ✅ ERC20 token standard
  • ✅ Web3/ethers.js compatible
  • ✅ Auto-supported by MetaMask, Ledger Ethereum app
What you'll do:
  1. HDWallet: Just add chain ID to EVM chains list (~10 lines of code)
  2. Web: Extend EvmBaseAdapter (~100 lines)
  3. Everything else: Add constants and config
Time estimate: 2-4 hours for basic integration
示例:Monad、Base、Arbitrum、Optimism
特点
  • ✅ 使用以太坊虚拟机
  • ✅ Solidity智能合约
  • ✅ ERC20代币标准
  • ✅ 兼容Web3/ethers.js
  • ✅ 自动受MetaMask、Ledger以太坊应用支持
需执行操作
  1. HDWallet:只需将链ID添加到EVM链列表(约10行代码)
  2. Web:扩展EvmBaseAdapter(约100行)
  3. 其他:添加常量和配置
时间估算:基础集成需2-4小时

Path B: Non-EVM Chain Integration (COMPLEX)

路径B:非EVM链集成(复杂)

Examples: Tron, Sui, Cosmos, Solana
Characteristics:
  • ❌ Custom virtual machine (not EVM)
  • ❌ Custom smart contract language
  • ❌ Custom token standard
  • ❌ Custom transaction format
  • ❌ Requires chain-specific crypto implementation
What you'll do:
  1. HDWallet: Implement full chain module with crypto adapters (~500-1000 lines)
  2. Web: Implement full IChainAdapter interface (~500-1000 lines)
  3. Everything else: Add constants and config
Time estimate: 1-2 days for basic integration

示例:Tron、Sui、Cosmos、Solana
特点
  • ❌ 自定义虚拟机(非EVM)
  • ❌ 自定义智能合约语言
  • ❌ 自定义代币标准
  • ❌ 自定义交易格式
  • ❌ 需要链特定的加密实现
需执行操作
  1. HDWallet:实现完整的链模块,包含加密适配器(约500-1000行)
  2. Web:实现完整的IChainAdapter接口(约500-1000行)
  3. 其他:添加常量和配置
时间估算:基础集成需1-2天

Phase 1: HDWallet Native Support

阶段1:HDWallet原生支持

Working Directory: Same monorepo — hdwallet packages are at
packages/hdwallet-*
工作目录:同一monorepo — hdwallet包位于
packages/hdwallet-*

Step 1.0: Choose Implementation Strategy

步骤1.0:选择实现策略

If EVM chain: Continue with Step 1.2-EVM below (MINIMAL hdwallet work - ~30 minutes) If non-EVM chain: Continue with Step 1.1 below (COMPLEX - 1-2 days)
若为EVM链:继续执行下方步骤1.2-EVM(HDWallet工作极少 - 约30分钟) 若为非EVM链:继续执行步骤1.1(复杂 - 1-2天)

⚡ EVM Chains: Minimal HDWallet Work Required

⚡ EVM链:HDWallet工作极少

For EVM-compatible chains (like Monad, HyperEVM, Base), you need MINIMAL changes to hdwallet:
What EVM chains DON'T need:
  • ❌ No new core interfaces (TronWallet, SuiWallet, etc.)
  • ❌ No crypto adapters (address derivation, signing)
  • ❌ No wallet mixins
  • ✅ Use existing Ethereum crypto (secp256k1, Keccak256)
What EVM chains DO need:
  • ✅ Wallet support flags (
    _supportsChainName: boolean
    )
  • ✅ Support function (
    supportsChainName()
    )
  • ✅ Set flags on all wallet implementations (~14 files)
  • ✅ Build and verify with
    yarn hdwallet:build
Why? Each wallet type (Native, Ledger, MetaMask, etc.) needs to explicitly declare support for the chain, even though the crypto is identical. This enables wallet-specific gating in the UI.
Reference PRs:
Time estimate: 30 minutes for hdwallet changes (vs 1-2 days for non-EVM)
对于EVM兼容链(如Monad、HyperEVM、Base),只需极少变更
EVM链不需要
  • ❌ 新的核心接口(TronWallet、SuiWallet等)
  • ❌ 加密适配器(地址派生、签名)
  • ❌ 钱包混入
  • ✅ 使用现有以太坊加密(secp256k1、Keccak256)
EVM链需要
  • ✅ 钱包支持标志(
    _supportsChainName: boolean
  • ✅ 支持函数(
    supportsChainName()
  • ✅ 在所有钱包实现中设置标志(约14个文件)
  • ✅ 构建并使用
    yarn hdwallet:build
    验证
原因:每个钱包类型(原生、Ledger、MetaMask等)都需要显式声明对该链的支持,即使加密逻辑相同。这允许UI中进行钱包特定的管控。
参考PR
时间估算:HDWallet变更需30分钟(而非非EVM链的1-2天)

Step 1.1: Research HDWallet Patterns (Non-EVM Only)

步骤1.1:调研HDWallet模式(仅非EVM链)

Examine existing implementations to understand patterns:
For non-EVM chains (like Tron, Sui):
bash
undefined
查看现有实现以理解模式:
对于非EVM链(如Tron、Sui):
bash
undefined

In the monorepo

在monorepo中

cat packages/hdwallet-core/src/tron.ts cat packages/hdwallet-native/src/tron.ts cat packages/hdwallet-native/src/crypto/isolation/adapters/tron.ts

Key pattern: Need new core interfaces, native implementation, and crypto adapters for signing.
cat packages/hdwallet-core/src/tron.ts cat packages/hdwallet-native/src/tron.ts cat packages/hdwallet-native/src/crypto/isolation/adapters/tron.ts

核心模式:需要新的核心接口、原生实现和签名用的加密适配器。

Step 1.2-EVM: EVM Chain Implementation (SIMPLE PATH)

步骤1.2-EVM:EVM链实现(简单路径)

For EVM chains only (like Monad):
File:
packages/hdwallet-core/src/ethereum.ts
Add your chain to supported EVM chains:
typescript
// Find the list of supported chain IDs and add yours
export const SUPPORTED_EVM_CHAINS = [
  1,      // Ethereum
  10,     // Optimism
  // ... other chains
  41454,  // Add your chain ID here (example: Monad)
]
File:
packages/hdwallet-core/src/utils.ts
Register SLIP44 if not using Ethereum's (60):
typescript
// If your chain uses a different SLIP44 than Ethereum
{ slip44: YOUR_SLIP44, symbol: 'SYMBOL', name: 'ChainName' }
That's it for hdwallet! EVM chains don't need crypto adapters. Skip to Step 1.6 (Version Bump).
仅EVM链(如Monad):
文件
packages/hdwallet-core/src/ethereum.ts
将你的链添加到支持的EVM链列表:
typescript
// 找到支持的链ID列表并添加你的链
export const SUPPORTED_EVM_CHAINS = [
  1,      // Ethereum
  10,     // Optimism
  // ... 其他链
  41454,  // 在此添加你的链ID(示例:Monad)
]
文件
packages/hdwallet-core/src/utils.ts
若不使用以太坊的SLIP44(60),则注册你的SLIP44:
typescript
// 若你的链使用与以太坊不同的SLIP44
{ slip44: YOUR_SLIP44, symbol: 'SYMBOL', name: 'ChainName' }
HDWallet部分完成! EVM链不需要加密适配器,跳至步骤1.6(版本升级)。

Step 1.2-EVM: EVM Chain HDWallet Support (MINIMAL WORK - ~30 minutes)

步骤1.2-EVM:EVM链HDWallet支持(工作极少 - 约30分钟)

For EVM chains only (like Monad, HyperEVM). Follow these PRs as reference:
File:
packages/hdwallet-core/src/ethereum.ts
Add your chain's support flag to the ETHWalletInfo interface:
typescript
export interface ETHWalletInfo extends HDWalletInfo {
  // ... existing flags
  readonly _supportsMonad: boolean;
  readonly _supportsHyperEvm: boolean;  // ADD THIS
  // ...
}
File:
packages/hdwallet-core/src/wallet.ts
Add support function after
supportsMonad
:
typescript
export function supportsMonad(wallet: HDWallet): wallet is ETHWallet {
  return isObject(wallet) && (wallet as any)._supportsMonad;
}

export function supports[ChainName](wallet: HDWallet): wallet is ETHWallet {
  return isObject(wallet) && (wallet as any)._supports[ChainName];
}
Set flags on ALL wallet implementations (~12 files):
For second-class EVM chains (HyperEVM, Monad, Plasma):
Set
readonly _supports[ChainName] = true
on:
  • packages/hdwallet-native/src/ethereum.ts
  • packages/hdwallet-metamask-multichain/src/shapeshift-multichain.ts (uses standard EVM cryptography)
  • packages/hdwallet-ledger/src/ledger.ts (uses Ethereum app, supports all EVM chains)
  • packages/hdwallet-trezor/src/trezor.ts (uses Ethereum app, supports all EVM chains)
  • packages/hdwallet-walletconnectv2/src/walletconnectv2.ts (chain-agnostic, supports all EVM chains)
Set
readonly _supports[ChainName] = false
on:
  • packages/hdwallet-coinbase/src/coinbase.ts
  • packages/hdwallet-gridplus/src/gridplus.ts
  • packages/hdwallet-keepkey/src/keepkey.ts
  • packages/hdwallet-keplr/src/keplr.ts
  • packages/hdwallet-phantom/src/phantom.ts
  • packages/hdwallet-vultisig/src/vultisig.ts
For non-EVM chains:
Set
readonly _supports[ChainName] = true
for Native only:
  • packages/hdwallet-native/src/ethereum.ts (or appropriate chain file)
Set
readonly _supports[ChainName] = false
on all other wallet types listed above.
Then: Skip to Step 1.6 (Build & Verify)

仅EVM链(如Monad、HyperEVM),参考以下PR:
文件
packages/hdwallet-core/src/ethereum.ts
将你的链的支持标志添加到ETHWalletInfo接口:
typescript
export interface ETHWalletInfo extends HDWalletInfo {
  // ... 现有标志
  readonly _supportsMonad: boolean;
  readonly _supportsHyperEvm: boolean;  // 添加此行
  // ...
}
文件
packages/hdwallet-core/src/wallet.ts
supportsMonad
后添加支持函数:
typescript
export function supportsMonad(wallet: HDWallet): wallet is ETHWallet {
  return isObject(wallet) && (wallet as any)._supportsMonad;
}

export function supports[ChainName](wallet: HDWallet): wallet is ETHWallet {
  return isObject(wallet) && (wallet as any)._supports[ChainName];
}
在所有钱包实现中设置标志(约12个文件):
对于二等EVM链(HyperEVM、Monad、Plasma)
readonly _supports[ChainName] = true
设置到:
  • packages/hdwallet-native/src/ethereum.ts
  • packages/hdwallet-metamask-multichain/src/shapeshift-multichain.ts(使用标准EVM加密)
  • packages/hdwallet-ledger/src/ledger.ts(使用以太坊应用,支持所有EVM链)
  • packages/hdwallet-trezor/src/trezor.ts(使用以太坊应用,支持所有EVM链)
  • packages/hdwallet-walletconnectv2/src/walletconnectv2.ts(链无关,支持所有EVM链)
readonly _supports[ChainName] = false
设置到:
  • packages/hdwallet-coinbase/src/coinbase.ts
  • packages/hdwallet-gridplus/src/gridplus.ts
  • packages/hdwallet-keepkey/src/keepkey.ts
  • packages/hdwallet-keplr/src/keplr.ts
  • packages/hdwallet-phantom/src/phantom.ts
  • packages/hdwallet-vultisig/src/vultisig.ts
对于非EVM链
仅在原生钱包中设置
readonly _supports[ChainName] = true
  • packages/hdwallet-native/src/ethereum.ts(或对应的链文件)
在上述所有其他钱包类型中设置
readonly _supports[ChainName] = false
然后:跳至步骤1.6(构建与验证)

Step 1.2-NonEVM: Non-EVM Core Interfaces (COMPLEX PATH)

步骤1.2-NonEVM:非EVM核心接口(复杂路径)

File:
packages/hdwallet-core/src/[chainname].ts
Create core TypeScript interfaces following the pattern:
typescript
import { addressNListToBIP32, slip44ByCoin } from "./utils";
import { BIP32Path, HDWallet, HDWalletInfo, PathDescription } from "./wallet";

export interface [Chain]GetAddress {
  addressNList: BIP32Path;
  showDisplay?: boolean;
  pubKey?: string;
}

export interface [Chain]SignTx {
  addressNList: BIP32Path;
  // Chain-specific tx data fields
  rawDataHex?: string; // or other fields
}

export interface [Chain]SignedTx {
  serialized: string;
  signature: string;
}

export interface [Chain]TxSignature {
  signature: string;
}

export interface [Chain]GetAccountPaths {
  accountIdx: number;
}

export interface [Chain]AccountPath {
  addressNList: BIP32Path;
}

export interface [Chain]WalletInfo extends HDWalletInfo {
  readonly _supports[Chain]Info: boolean;
  [chainLower]GetAccountPaths(msg: [Chain]GetAccountPaths): Array<[Chain]AccountPath>;
  [chainLower]NextAccountPath(msg: [Chain]AccountPath): [Chain]AccountPath | undefined;
}

export interface [Chain]Wallet extends [Chain]WalletInfo, HDWallet {
  readonly _supports[Chain]: boolean;
  [chainLower]GetAddress(msg: [Chain]GetAddress): Promise<string | null>;
  [chainLower]SignTx(msg: [Chain]SignTx): Promise<[Chain]SignedTx | null>;
}

export function [chainLower]DescribePath(path: BIP32Path): PathDescription {
  const pathStr = addressNListToBIP32(path);
  const unknown: PathDescription = {
    verbose: pathStr,
    coin: "[ChainName]",
    isKnown: false,
  };

  if (path.length != 5) return unknown;
  if (path[0] != 0x80000000 + 44) return unknown;
  if (path[1] != 0x80000000 + slip44ByCoin("[ChainName]")) return unknown;
  if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;
  if (path[3] !== 0) return unknown;
  if (path[4] !== 0) return unknown;

  const index = path[2] & 0x7fffffff;
  return {
    verbose: `[ChainName] Account #${index}`,
    accountIdx: index,
    wholeAccount: true,
    coin: "[ChainName]",
    isKnown: true,
  };
}

// Standard BIP44 derivation: m/44'/SLIP44'/<account>'/0/0
export function [chainLower]GetAccountPaths(msg: [Chain]GetAccountPaths): Array<[Chain]AccountPath> {
  const slip44 = slip44ByCoin("[ChainName]");
  return [{ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0] }];
}
Export from core:
typescript
// In packages/hdwallet-core/src/index.ts
export * from './[chainname]'
Register SLIP44:
typescript
// In packages/hdwallet-core/src/utils.ts
// Add to slip44Table
{ slip44: SLIP44, symbol: '[SYMBOL]', name: '[ChainName]' }
文件
packages/hdwallet-core/src/[chainname].ts
遵循以下模式创建核心TypeScript接口:
typescript
import { addressNListToBIP32, slip44ByCoin } from "./utils";
import { BIP32Path, HDWallet, HDWalletInfo, PathDescription } from "./wallet";

export interface [Chain]GetAddress {
  addressNList: BIP32Path;
  showDisplay?: boolean;
  pubKey?: string;
}

export interface [Chain]SignTx {
  addressNList: BIP32Path;
  // 链特定的交易数据字段
  rawDataHex?: string; // 或其他字段
}

export interface [Chain]SignedTx {
  serialized: string;
  signature: string;
}

export interface [Chain]TxSignature {
  signature: string;
}

export interface [Chain]GetAccountPaths {
  accountIdx: number;
}

export interface [Chain]AccountPath {
  addressNList: BIP32Path;
}

export interface [Chain]WalletInfo extends HDWalletInfo {
  readonly _supports[Chain]Info: boolean;
  [chainLower]GetAccountPaths(msg: [Chain]GetAccountPaths): Array<[Chain]AccountPath>;
  [chainLower]NextAccountPath(msg: [Chain]AccountPath): [Chain]AccountPath | undefined;
}

export interface [Chain]Wallet extends [Chain]WalletInfo, HDWallet {
  readonly _supports[Chain]: boolean;
  [chainLower]GetAddress(msg: [Chain]GetAddress): Promise<string | null>;
  [chainLower]SignTx(msg: [Chain]SignTx): Promise<[Chain]SignedTx | null>;
}

export function [chainLower]DescribePath(path: BIP32Path): PathDescription {
  const pathStr = addressNListToBIP32(path);
  const unknown: PathDescription = {
    verbose: pathStr,
    coin: "[ChainName]",
    isKnown: false,
  };

  if (path.length != 5) return unknown;
  if (path[0] != 0x80000000 + 44) return unknown;
  if (path[1] != 0x80000000 + slip44ByCoin("[ChainName]")) return unknown;
  if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;
  if (path[3] !== 0) return unknown;
  if (path[4] !== 0) return unknown;

  const index = path[2] & 0x7fffffff;
  return {
    verbose: `[ChainName] 账户 #${index}`,
    accountIdx: index,
    wholeAccount: true,
    coin: "[ChainName]",
    isKnown: true,
  };
}

// 标准BIP44派生:m/44'/SLIP44'/<account>'/0/0
export function [chainLower]GetAccountPaths(msg: [Chain]GetAccountPaths): Array<[Chain]AccountPath> {
  const slip44 = slip44ByCoin("[ChainName]");
  return [{ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0] }];
}
从核心导出
typescript
// 在packages/hdwallet-core/src/index.ts中
export * from './[chainname]'
注册SLIP44
typescript
// 在packages/hdwallet-core/src/utils.ts中
// 添加到slip44Table
{ slip44: SLIP44, symbol: '[SYMBOL]', name: '[ChainName]' }

Step 1.3: Implement Native Wallet Support

步骤1.3:实现原生钱包支持

File:
packages/hdwallet-native/src/[chainname].ts
typescript
import * as core from "@shapeshiftoss/hdwallet-core";
import { Isolation } from "./crypto";
import { [Chain]Adapter } from "./crypto/isolation/adapters/[chainname]";
import { NativeHDWalletBase } from "./native";

export function MixinNative[Chain]WalletInfo<TBase extends core.Constructor<core.HDWalletInfo>>(Base: TBase) {
  return class MixinNative[Chain]WalletInfo extends Base implements core.[Chain]WalletInfo {
    readonly _supports[Chain]Info = true;

    [chainLower]GetAccountPaths(msg: core.[Chain]GetAccountPaths): Array<core.[Chain]AccountPath> {
      return core.[chainLower]GetAccountPaths(msg);
    }

    [chainLower]NextAccountPath(msg: core.[Chain]AccountPath): core.[Chain]AccountPath | undefined {
      throw new Error("Method not implemented");
    }
  };
}

export function MixinNative[Chain]Wallet<TBase extends core.Constructor<NativeHDWalletBase>>(Base: TBase) {
  return class MixinNative[Chain]Wallet extends Base {
    readonly _supports[Chain] = true;

    [chainLower]Adapter: [Chain]Adapter | undefined;

    async [chainLower]InitializeWallet(masterKey: Isolation.Core.BIP32.Node): Promise<void> {
      const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey);
      this.[chainLower]Adapter = new [Chain]Adapter(nodeAdapter);
    }

    [chainLower]Wipe() {
      this.[chainLower]Adapter = undefined;
    }

    async [chainLower]GetAddress(msg: core.[Chain]GetAddress): Promise<string | null> {
      return this.needsMnemonic(!!this.[chainLower]Adapter, () => {
        return this.[chainLower]Adapter!.getAddress(msg.addressNList);
      });
    }

    async [chainLower]SignTx(msg: core.[Chain]SignTx): Promise<core.[Chain]SignedTx | null> {
      return this.needsMnemonic(!!this.[chainLower]Adapter, async () => {
        const address = await this.[chainLower]GetAddress({
          addressNList: msg.addressNList,
          showDisplay: false,
        });

        if (!address) throw new Error("Failed to get [ChainName] address");

        const signature = await this.[chainLower]Adapter!.signTransaction(
          msg.rawDataHex,
          msg.addressNList
        );

        return {
          serialized: msg.rawDataHex + signature,
          signature,
        };
      });
    }
  };
}
Integrate into NativeHDWallet:
typescript
// In packages/hdwallet-native/src/native.ts
// Add mixin to class hierarchy
// Add initialization in initialize() method
// Add wipe in wipe() method
文件
packages/hdwallet-native/src/[chainname].ts
typescript
import * as core from "@shapeshiftoss/hdwallet-core";
import { Isolation } from "./crypto";
import { [Chain]Adapter } from "./crypto/isolation/adapters/[chainname]";
import { NativeHDWalletBase } from "./native";

export function MixinNative[Chain]WalletInfo<TBase extends core.Constructor<core.HDWalletInfo>>(Base: TBase) {
  return class MixinNative[Chain]WalletInfo extends Base implements core.[Chain]WalletInfo {
    readonly _supports[Chain]Info = true;

    [chainLower]GetAccountPaths(msg: core.[Chain]GetAccountPaths): Array<core.[Chain]AccountPath> {
      return core.[chainLower]GetAccountPaths(msg);
    }

    [chainLower]NextAccountPath(msg: core.[Chain]AccountPath): core.[Chain]AccountPath | undefined {
      throw new Error("方法未实现");
    }
  };
}

export function MixinNative[Chain]Wallet<TBase extends core.Constructor<NativeHDWalletBase>>(Base: TBase) {
  return class MixinNative[Chain]Wallet extends Base {
    readonly _supports[Chain] = true;

    [chainLower]Adapter: [Chain]Adapter | undefined;

    async [chainLower]InitializeWallet(masterKey: Isolation.Core.BIP32.Node): Promise<void> {
      const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey);
      this.[chainLower]Adapter = new [Chain]Adapter(nodeAdapter);
    }

    [chainLower]Wipe() {
      this.[chainLower]Adapter = undefined;
    }

    async [chainLower]GetAddress(msg: core.[Chain]GetAddress): Promise<string | null> {
      return this.needsMnemonic(!!this.[chainLower]Adapter, () => {
        return this.[chainLower]Adapter!.getAddress(msg.addressNList);
      });
    }

    async [chainLower]SignTx(msg: core.[Chain]SignTx): Promise<core.[Chain]SignedTx | null> {
      return this.needsMnemonic(!!this.[chainLower]Adapter, async () => {
        const address = await this.[chainLower]GetAddress({
          addressNList: msg.addressNList,
          showDisplay: false,
        });

        if (!address) throw new Error("获取[ChainName]地址失败");

        const signature = await this.[chainLower]Adapter!.signTransaction(
          msg.rawDataHex,
          msg.addressNList
        );

        return {
          serialized: msg.rawDataHex + signature,
          signature,
        };
      });
    }
  };
}
集成到NativeHDWallet
typescript
// 在packages/hdwallet-native/src/native.ts中
// 将混入添加到类继承结构
// 在initialize()方法中添加初始化逻辑
// 在wipe()方法中添加清理逻辑

Step 1.4: Create Crypto Adapter (Non-EVM only)

步骤1.4:创建加密适配器(仅非EVM链)

File:
packages/hdwallet-native/src/crypto/isolation/adapters/[chainname].ts
Implement chain-specific cryptography:
  • Address generation algorithm
  • Transaction signing
  • Any chain-specific encoding
Reference: See
tron.ts
adapter for a complete example
Export adapter:
typescript
// In packages/hdwallet-native/src/crypto/isolation/adapters/index.ts
export * from './[chainname]'
文件
packages/hdwallet-native/src/crypto/isolation/adapters/[chainname].ts
实现链特定的加密逻辑:
  • 地址生成算法
  • 交易签名
  • 任何链特定的编码
参考:查看
tron.ts
适配器作为完整示例
导出适配器
typescript
// 在packages/hdwallet-native/src/crypto/isolation/adapters/index.ts中
export * from './[chainname]'

Step 1.5: Update Core Wallet Interface

步骤1.5:更新核心钱包接口

File:
packages/hdwallet-core/src/wallet.ts
Add support check function:
typescript
export function supports[Chain](wallet: HDWallet): wallet is [Chain]Wallet {
  return !!(wallet as any)._supports[Chain]
}
文件
packages/hdwallet-core/src/wallet.ts
添加支持检查函数:
typescript
export function supports[Chain](wallet: HDWallet): wallet is [Chain]Wallet {
  return !!(wallet as any)._supports[Chain]
}

Step 1.6: Build & Verify

步骤1.6:构建与验证

bash
undefined
bash
undefined

Build hdwallet packages to verify

构建hdwallet包以验证

yarn hdwallet:build
yarn hdwallet:build

Run hdwallet tests

运行hdwallet测试

yarn hdwallet:test

No version bumps or publishing needed — hdwallet packages are workspace packages (`workspace:^`).

---
yarn hdwallet:test

无需版本升级或发布 — hdwallet包是工作区包(`workspace:^`)。

---

Phase 2: Web Chain Adapter (Poor Man's Approach)

阶段2:Web链适配器(简易方案)

Step 3.1: Add Chain Constants

步骤3.1:添加链常量

File:
packages/caip/src/constants.ts
typescript
// Add chain ID constant
export const [chainLower]ChainId = '[caip19-format]' as const // e.g., 'eip155:1', 'tron:0x2b6653dc', etc.

// Add asset ID constant
export const [chainLower]AssetId = '[caip19-format]' as AssetId

// Add asset reference constant
export const ASSET_REFERENCE = {
  // ...
  [ChainName]: 'slip44:COINTYPE',
}

// Add to KnownChainIds enum
export enum KnownChainIds {
  // ...
  [ChainName]Mainnet = '[caip2-chain-id]',
}

// Add to asset namespace if needed (non-EVM chains)
export const ASSET_NAMESPACE = {
  // ...
  [tokenStandard]: '[namespace]', // e.g., trc20, suiCoin
}
File:
packages/types/src/base.ts
typescript
// Add to KnownChainIds enum (duplicate but required)
export enum KnownChainIds {
  // ...
  [ChainName]Mainnet = '[caip2-chain-id]',
}
File:
src/constants/chains.ts
typescript
// Add to second-class chains array
export const SECOND_CLASS_CHAINS = [
  // ...
  KnownChainIds.[ChainName]Mainnet,
]

// Add to feature-flag gated chains
文件
packages/caip/src/constants.ts
typescript
// 添加链ID常量
export const [chainLower]ChainId = '[caip19-format]' as const // 例如:'eip155:1'、'tron:0x2b6653dc'等

// 添加资产ID常量
export const [chainLower]AssetId = '[caip19-format]' as AssetId

// 添加资产引用常量
export const ASSET_REFERENCE = {
  // ...
  [ChainName]: 'slip44:COINTYPE',
}

// 添加到KnownChainIds枚举
export enum KnownChainIds {
  // ...
  [ChainName]Mainnet = '[caip2-chain-id]',
}

// 若需要,添加到资产命名空间(非EVM链)
export const ASSET_NAMESPACE = {
  // ...
  [tokenStandard]: '[namespace]', // 例如:trc20、suiCoin
}
文件
packages/types/src/base.ts
typescript
// 添加到KnownChainIds枚举(重复但必需)
export enum KnownChainIds {
  // ...
  [ChainName]Mainnet = '[caip2-chain-id]',
}
文件
src/constants/chains.ts
typescript
// 添加到二等公民链数组
export const SECOND_CLASS_CHAINS = [
  // ...
  KnownChainIds.[ChainName]Mainnet,
]

// 添加到功能标志管控的链

Step 3.2: Create Chain Adapter

步骤3.2:创建链适配器

Directory:
packages/chain-adapters/src/[adaptertype]/[chainname]/
目录
packages/chain-adapters/src/[adaptertype]/[chainname]/

For EVM Chains (SIMPLE!)

对于EVM链(简单!)

Extend
SecondClassEvmAdapter
- you only need ~50 lines!
File:
packages/chain-adapters/src/evm/[chainname]/[ChainName]ChainAdapter.ts
typescript
import { ASSET_REFERENCE, [chainLower]AssetId } from '@shapeshiftoss/caip'
import type { AssetId } from '@shapeshiftoss/caip'
import type { RootBip44Params } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'

import { ChainAdapterDisplayName } from '../../types'
import { SecondClassEvmAdapter } from '../SecondClassEvmAdapter'
import type { TokenInfo } from '../SecondClassEvmAdapter'

const SUPPORTED_CHAIN_IDS = [KnownChainIds.[ChainName]Mainnet]
const DEFAULT_CHAIN_ID = KnownChainIds.[ChainName]Mainnet

export type ChainAdapterArgs = {
  rpcUrl: string
  knownTokens?: TokenInfo[]
}

export const is[ChainName]ChainAdapter = (adapter: unknown): adapter is ChainAdapter => {
  return (adapter as ChainAdapter).getType() === KnownChainIds.[ChainName]Mainnet
}

export class ChainAdapter extends SecondClassEvmAdapter<KnownChainIds.[ChainName]Mainnet> {
  public static readonly rootBip44Params: RootBip44Params = {
    purpose: 44,
    coinType: Number(ASSET_REFERENCE.[ChainName]),
    accountNumber: 0,
  }

  constructor(args: ChainAdapterArgs) {
    super({
      assetId: [chainLower]AssetId,
      chainId: DEFAULT_CHAIN_ID,
      rootBip44Params: ChainAdapter.rootBip44Params,
      supportedChainIds: SUPPORTED_CHAIN_IDS,
      rpcUrl: args.rpcUrl,
      knownTokens: args.knownTokens ?? [],
    })
  }

  getDisplayName() {
    return ChainAdapterDisplayName.[ChainName]
  }

  getName() {
    return '[ChainName]'
  }

  getType(): KnownChainIds.[ChainName]Mainnet {
    return KnownChainIds.[ChainName]Mainnet
  }

  getFeeAssetId(): AssetId {
    return this.assetId
  }
}

export type { TokenInfo }
That's it! SecondClassEvmAdapter automatically provides:
  • ✅ Account balance fetching (native + ERC-20 tokens via multicall)
  • ✅ Fee estimation
  • ✅ Transaction broadcasting
  • ✅ Transaction parsing with ERC-20 event decoding (for execution price)
  • ✅ Rate limiting via PQueue
  • ✅ Multicall batching for token balances
Just follow the pattern from HyperEVM, Monad, or Plasma adapters.
扩展
SecondClassEvmAdapter
- 只需约50行代码!
文件
packages/chain-adapters/src/evm/[chainname]/[ChainName]ChainAdapter.ts
typescript
import { ASSET_REFERENCE, [chainLower]AssetId } from '@shapeshiftoss/caip'
import type { AssetId } from '@shapeshiftoss/caip'
import type { RootBip44Params } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'

import { ChainAdapterDisplayName } from '../../types'
import { SecondClassEvmAdapter } from '../SecondClassEvmAdapter'
import type { TokenInfo } from '../SecondClassEvmAdapter'

const SUPPORTED_CHAIN_IDS = [KnownChainIds.[ChainName]Mainnet]
const DEFAULT_CHAIN_ID = KnownChainIds.[ChainName]Mainnet

export type ChainAdapterArgs = {
  rpcUrl: string
  knownTokens?: TokenInfo[]
}

export const is[ChainName]ChainAdapter = (adapter: unknown): adapter is ChainAdapter => {
  return (adapter as ChainAdapter).getType() === KnownChainIds.[ChainName]Mainnet
}

export class ChainAdapter extends SecondClassEvmAdapter<KnownChainIds.[ChainName]Mainnet> {
  public static readonly rootBip44Params: RootBip44Params = {
    purpose: 44,
    coinType: Number(ASSET_REFERENCE.[ChainName]),
    accountNumber: 0,
  }

  constructor(args: ChainAdapterArgs) {
    super({
      assetId: [chainLower]AssetId,
      chainId: DEFAULT_CHAIN_ID,
      rootBip44Params: ChainAdapter.rootBip44Params,
      supportedChainIds: SUPPORTED_CHAIN_IDS,
      rpcUrl: args.rpcUrl,
      knownTokens: args.knownTokens ?? [],
    })
  }

  getDisplayName() {
    return ChainAdapterDisplayName.[ChainName]
  }

  getName() {
    return '[ChainName]'
  }

  getType(): KnownChainIds.[ChainName]Mainnet {
    return KnownChainIds.[ChainName]Mainnet
  }

  getFeeAssetId(): AssetId {
    return this.assetId
  }
}

export type { TokenInfo }
完成! SecondClassEvmAdapter自动提供:
  • ✅ 账户余额查询(原生 + ERC-20代币,通过多调用)
  • ✅ 费用估算
  • ✅ 交易广播
  • ✅ 交易解析(含ERC-20事件解码,用于执行价格计算)
  • ✅ 通过PQueue进行速率限制
  • ✅ 代币余额的多调用批处理
只需遵循HyperEVM、Monad或Plasma适配器的模式。

For Non-EVM Chains (COMPLEX)

对于非EVM链(复杂)

Implement
IChainAdapter
interface - requires custom crypto adapters and ~500-1000 lines.
Key Methods to Implement:
  • getAccount()
    - Get balances (native + tokens)
  • getAddress()
    - Derive address from wallet
  • getFeeData()
    - Estimate transaction fees
  • broadcastTransaction()
    - Submit signed tx to network
  • buildSendApiTransaction()
    - Build unsigned tx
  • signTransaction()
    - Sign with wallet
  • parseTx()
    - Parse transaction (can stub out)
  • getTxHistory()
    - Get tx history (stub out - return empty)
Poor Man's Patterns:
  1. No Unchained: Use public RPC directly (@mysten/sui, tronweb, etc.)
  2. No TX History: Stub out
    getTxHistory()
    to return empty array
  3. Direct RPC Polling: Use chain-specific RPC for tx status
File:
packages/chain-adapters/src/[chainname]/[ChainName]ChainAdapter.ts
See
SuiChainAdapter.ts
or
TronChainAdapter.ts
for complete examples.
Export:
typescript
// In packages/chain-adapters/src/[adaptertype]/[chainname]/index.ts
export * from './[ChainName]ChainAdapter'
export * from './types'

// In packages/chain-adapters/src/[adaptertype]/index.ts
export * as [chainLower] from './[chainname]'

// In packages/chain-adapters/src/index.ts
export * from './[adaptertype]'
实现
IChainAdapter
接口 - 需要自定义加密适配器和约500-1000行代码。
需实现的核心方法
  • getAccount()
    - 获取余额(原生 + 代币)
  • getAddress()
    - 从钱包派生地址
  • getFeeData()
    - 估算交易费用
  • broadcastTransaction()
    - 提交签名交易到网络
  • buildSendApiTransaction()
    - 构建未签名交易
  • signTransaction()
    - 使用钱包签名
  • parseTx()
    - 解析交易(可暂存)
  • getTxHistory()
    - 获取交易历史(暂存 - 返回空数组)
简易方案模式
  1. 无Unchained:直接使用公共RPC(@mysten/sui、tronweb等)
  2. 无交易历史:暂存
    getTxHistory()
    以返回空数组
  3. 直接RPC轮询:使用链特定的RPC查询交易状态
文件
packages/chain-adapters/src/[chainname]/[ChainName]ChainAdapter.ts
查看
SuiChainAdapter.ts
TronChainAdapter.ts
作为完整示例。
导出
typescript
// 在packages/chain-adapters/src/[adaptertype]/[chainname]/index.ts中
export * from './[ChainName]ChainAdapter'
export * from './types'

// 在packages/chain-adapters/src/[adaptertype]/index.ts中
export * as [chainLower] from './[chainname]'

// 在packages/chain-adapters/src/index.ts中
export * from './[adaptertype]'

Step 3.2a: Implement parseTx (Iterative Approach)

步骤3.2a:实现parseTx(迭代方法)

CRITICAL: The
parseTx()
method parses transaction data after broadcast. This determines:
  • Whether the transaction shows in history (if applicable)
  • Execution price calculation for swaps
  • Transfer display (from/to/value)
Reference Implementations (use these as patterns):
  • EVM chains:
    SecondClassEvmAdapter.parseTx()
    - handles ERC-20 Transfer events automatically
  • Sui:
    SuiChainAdapter.parseTx()
    - parses SUI native and coin transfers
  • Tron:
    TronChainAdapter.parseTx()
    - parses TRC-20 transfers
  • NEAR:
    NearChainAdapter.parseTx()
    +
    parseNep141Transfers()
    - parses NEP-141 token logs
Iterative Development Flow:
  1. Start with naive implementation - Parse native asset transfers only:
typescript
async parseTx(txHash: string, pubkey: string): Promise<Transaction> {
  const result = await this.rpcCall('getTransaction', [txHash])

  // Basic structure
  const status = result.success ? TxStatus.Confirmed : TxStatus.Failed
  const fee = { assetId: this.assetId, value: result.fee.toString() }

  const transfers: Transfer[] = []

  // Parse native transfers (naive - just native asset)
  if (result.value) {
    transfers.push({
      assetId: this.assetId,
      from: [result.from],
      to: [result.to],
      type: result.from === pubkey ? TransferType.Send : TransferType.Receive,
      value: result.value.toString(),
    })
  }

  return { txid: txHash, status, fee, transfers, /* ... */ }
}
  1. User testing reveals issues - User tests sends/swaps and reports:
    • "Native send works but tokens don't show"
    • "Swap execution price is wrong"
    • Provides RPC response from debugger
  2. Refine with actual RPC response - User provides debugger scope:
typescript
// Example: User provides RPC response showing token events in logs
// You then add token parsing logic based on actual data structure

private parseTokenTransfers(result: RpcResult, pubkey: string): Transfer[] {
  const transfers: Transfer[] = []

  // Parse token events from logs/events
  for (const event of result.events || []) {
    if (event.type === 'token_transfer') {
      // Token-specific parsing based on actual RPC structure
    }
  }

  return transfers
}
Key considerations for parseTx:
AspectNative AssetTokensInternal Transfers
Where to findTransaction value fieldEvent logs / receiptsNested calls / traces
Asset ID
this.assetId
chainId/namespace:contractAddress
Varies
pubkey comparisonUsually sender fieldEvent old_owner/new_ownerMay be nested
Common patterns by chain type:
EVM Chains (SecondClassEvmAdapter handles automatically):
  • Native:
    tx.value
  • Tokens: ERC-20 Transfer events in logs
  • Uses
    ethers.Interface.parseLog()
    to decode events
Non-EVM Chains (must implement manually):
typescript
// NEAR pattern - EVENT_JSON logs
for (const log of receipt.outcome.logs) {
  if (!log.startsWith('EVENT_JSON:')) continue
  const event = JSON.parse(log.slice('EVENT_JSON:'.length))
  if (event.standard === 'nep141' && event.event === 'ft_transfer') {
    // Parse transfer from event.data
  }
}

// Sui pattern - coin type from object changes
for (const change of result.objectChanges) {
  if (change.type === 'mutated' && change.objectType.includes('::coin::Coin<')) {
    // Extract coin type and amount
  }
}

// Tron pattern - TRC20 logs
for (const log of result.log || []) {
  if (log.topics[0] === TRC20_TRANSFER_TOPIC) {
    // Decode TRC20 transfer
  }
}
pubkey vs account ID gotcha:
  • Some chains pass
    pubkey
    as hex public key
  • But logs/events use account addresses (e.g.,
    alice.near
    , base58, etc.)
  • May need to convert:
    const accountId = pubKeyToAddress(pubkey)
When to ask user for debugger scope:
  • Initial naive implementation doesn't catch tokens
  • Swap execution prices are wrong
  • Internal transfers missing
Example request to user:
"The parseTx implementation needs refinement for token transfers. Can you:
  1. Make a token send/swap
  2. Set a breakpoint in parseTx()
  3. Share the
    result
    variable from the RPC response This will help me see the actual data structure for token events."
关键
parseTx()
方法在广播后解析交易数据,决定:
  • 交易是否显示在历史中(若适用)
  • 兑换的执行价格计算
  • 转账显示(从/到/金额)
参考实现(作为模式使用):
  • EVM链
    SecondClassEvmAdapter.parseTx()
    - 自动处理ERC-20 Transfer事件
  • Sui
    SuiChainAdapter.parseTx()
    - 解析SUI原生和代币转账
  • Tron
    TronChainAdapter.parseTx()
    - 解析TRC-20转账
  • NEAR
    NearChainAdapter.parseTx()
    +
    parseNep141Transfers()
    - 解析NEP-141代币日志
迭代开发流程
  1. 从简单实现开始 - 仅解析原生资产转账:
typescript
async parseTx(txHash: string, pubkey: string): Promise<Transaction> {
  const result = await this.rpcCall('getTransaction', [txHash])

  // 基础结构
  const status = result.success ? TxStatus.Confirmed : TxStatus.Failed
  const fee = { assetId: this.assetId, value: result.fee.toString() }

  const transfers: Transfer[] = []

  // 解析原生转账(简单 - 仅原生资产)
  if (result.value) {
    transfers.push({
      assetId: this.assetId,
      from: [result.from],
      to: [result.to],
      type: result.from === pubkey ? TransferType.Send : TransferType.Receive,
      value: result.value.toString(),
    })
  }

  return { txid: txHash, status, fee, transfers, /* ... */ }
}
  1. 用户测试发现问题 - 用户测试转账/兑换并报告:
    • "原生转账正常,但代币不显示"
    • "兑换执行价格错误"
    • 提供调试器中的RPC响应
  2. 根据实际RPC响应优化 - 用户提供调试器作用域:
typescript
// 示例:用户提供RPC响应,显示日志中的代币事件
// 然后根据实际数据结构添加代币解析逻辑

private parseTokenTransfers(result: RpcResult, pubkey: string): Transfer[] {
  const transfers: Transfer[] = []

  // 从日志/事件中解析代币事件
  for (const event of result.events || []) {
    if (event.type === 'token_transfer') {
      // 根据实际RPC结构进行代币特定解析
    }
  }

  return transfers
}
parseTx的关键考虑因素
方面原生资产代币内部转账
查找位置交易金额字段事件日志/收据嵌套调用/追踪
资产ID
this.assetId
chainId/namespace:contractAddress
可变
pubkey比较通常为发送方字段事件中的old_owner/new_owner可能嵌套
按链类型的常见模式
EVM链(SecondClassEvmAdapter自动处理):
  • 原生:
    tx.value
  • 代币:日志中的ERC-20 Transfer事件
  • 使用
    ethers.Interface.parseLog()
    解码事件
非EVM链(必须手动实现):
typescript
// NEAR模式 - EVENT_JSON日志
for (const log of receipt.outcome.logs) {
  if (!log.startsWith('EVENT_JSON:')) continue
  const event = JSON.parse(log.slice('EVENT_JSON:'.length))
  if (event.standard === 'nep141' && event.event === 'ft_transfer') {
    // 从event.data中解析转账
  }
}

// Sui模式 - 从对象变更中解析币种
for (const change of result.objectChanges) {
  if (change.type === 'mutated' && change.objectType.includes('::coin::Coin<')) {
    // 提取币种类型和金额
  }
}

// Tron模式 - TRC20日志
for (const log of result.log || []) {
  if (log.topics[0] === TRC20_TRANSFER_TOPIC) {
    // 解码TRC20转账
   }
}
pubkey与账户ID的陷阱
  • 某些链传递的
    pubkey
    是十六进制公钥
  • 但日志/事件中使用的是账户地址(如
    alice.near
    、base58等)
  • 可能需要转换:
    const accountId = pubKeyToAddress(pubkey)
何时向用户请求调试器作用域
  • 初始简单实现无法捕获代币
  • 兑换执行价格错误
  • 缺少内部转账
示例请求
"parseTx实现需要针对代币转账进行优化。能否:
  1. 进行一次代币转账/兑换
  2. 在parseTx()中设置断点
  3. 分享RPC响应中的
    result
    变量 这将帮助我了解代币事件的实际数据结构。"

Step 3.3: Add Utility Functions

步骤3.3:添加工具函数

File:
packages/utils/src/getAssetNamespaceFromChainId.ts
typescript
case [chainLower]ChainId:
  return ASSET_NAMESPACE.[tokenStandard]
File:
packages/utils/src/getChainShortName.ts
typescript
case KnownChainIds.[ChainName]Mainnet:
  return '[SHORT]'
File:
packages/utils/src/getNativeFeeAssetReference.ts
typescript
case KnownChainIds.[ChainName]Mainnet:
  return ASSET_REFERENCE.[ChainName]
File:
packages/utils/src/chainIdToFeeAssetId.ts
typescript
[chainLower]ChainId: [chainLower]AssetId,
File:
packages/utils/src/assetData/baseAssets.ts
typescript
// Add base asset
export const [chainLower]BaseAsset: Asset = {
  assetId: [chainLower]AssetId,
  chainId: [chainLower]ChainId,
  name: '[ChainName]',
  symbol: '[SYMBOL]',
  precision: [DECIMALS],
  icon: '[iconUrl]',
  explorer: '[explorerUrl]',
  // ... other fields
}
File:
packages/utils/src/assetData/getBaseAsset.ts
typescript
case [chainLower]ChainId:
  return [chainLower]BaseAsset
文件
packages/utils/src/getAssetNamespaceFromChainId.ts
typescript
case [chainLower]ChainId:
  return ASSET_NAMESPACE.[tokenStandard]
文件
packages/utils/src/getChainShortName.ts
typescript
case KnownChainIds.[ChainName]Mainnet:
  return '[SHORT]'
文件
packages/utils/src/getNativeFeeAssetReference.ts
typescript
case KnownChainIds.[ChainName]Mainnet:
  return ASSET_REFERENCE.[ChainName]
文件
packages/utils/src/chainIdToFeeAssetId.ts
typescript
[chainLower]ChainId: [chainLower]AssetId,
文件
packages/utils/src/assetData/baseAssets.ts
typescript
// 添加基础资产
export const [chainLower]BaseAsset: Asset = {
  assetId: [chainLower]AssetId,
  chainId: [chainLower]ChainId,
  name: '[ChainName]',
  symbol: '[SYMBOL]',
  precision: [DECIMALS],
  icon: '[iconUrl]',
  explorer: '[explorerUrl]',
  // ... 其他字段
}
文件
packages/utils/src/assetData/getBaseAsset.ts
typescript
case [chainLower]ChainId:
  return [chainLower]BaseAsset

Step 3.4: Create Chain Utils (Transaction Status)

步骤3.4:创建链工具(交易状态)

File:
src/lib/utils/[chainname].ts
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import type { ChainAdapter } from '@shapeshiftoss/chain-adapters'
import { assertUnreachable } from '@/lib/utils'
import type { TxStatus } from '@/state/slices/txHistorySlice/txHistorySlice'

export const is[Chain]ChainAdapter = (adapter: unknown): adapter is [ChainAdapter] => {
  return (adapter as ChainAdapter).getChainId() === [chainLower]ChainId
}

export const assertGet[Chain]ChainAdapter = (
  adapter: ChainAdapter,
): asserts adapter is [ChainAdapter] => {
  if (!is[Chain]ChainAdapter(adapter)) {
    throw new Error('[ChainName] adapter required')
  }
}

// Implement getTxStatus using chain-specific RPC calls
export const get[Chain]TransactionStatus = async (
  txHash: string,
  adapter: [ChainAdapter],
): Promise<TxStatus> => {
  // Use chain client to check transaction status
  // Return 'confirmed', 'failed', or 'unknown'
  // See monad.ts / sui.ts for examples
}
文件
src/lib/utils/[chainname].ts
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import type { ChainAdapter } from '@shapeshiftoss/chain-adapters'
import { assertUnreachable } from '@/lib/utils'
import type { TxStatus } from '@/state/slices/txHistorySlice/txHistorySlice'

export const is[Chain]ChainAdapter = (adapter: unknown): adapter is [ChainAdapter] => {
  return (adapter as ChainAdapter).getChainId() === [chainLower]ChainId
}

export const assertGet[Chain]ChainAdapter = (
  adapter: ChainAdapter,
): asserts adapter is [ChainAdapter] => {
  if (!is[Chain]ChainAdapter(adapter)) {
    throw new Error('需要[ChainName]适配器')
  }
}

// 使用链特定的RPC调用实现getTxStatus
export const get[Chain]TransactionStatus = async (
  txHash: string,
  adapter: [ChainAdapter],
): Promise<TxStatus> => {
  // 使用链客户端检查交易状态
  // 返回'confirmed'、'failed'或'unknown'
  // 参考monad.ts / sui.ts示例
}

Step 3.5: Wire Up Transaction Status Polling

步骤3.5:连接交易状态轮询

File:
src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx
Add case for your chain:
typescript
case KnownChainIds.[ChainName]Mainnet:
  txStatus = await get[Chain]TransactionStatus(txHash, adapter)
  break
文件
src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx
添加你的链的case:
typescript
case KnownChainIds.[ChainName]Mainnet:
  txStatus = await get[Chain]TransactionStatus(txHash, adapter)
  break

Step 3.6: Add Account Derivation

步骤3.6:添加账户派生

File:
src/lib/account/[chainname].ts
typescript
import { [chainLower]ChainId, toAccountId } from '@shapeshiftoss/caip'
import type { AccountMetadata, AccountMetadataByAccountId } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'
import { assertGet[Chain]ChainAdapter, is[Chain]ChainAdapter } from '@/lib/utils/[chainname]'

export const derive[Chain]AccountIdsAndMetadata = async (
  args: // standard args
): Promise<AccountMetadataByAccountId> => {
  const { accountNumber, chainIds, wallet } = args

  const adapter = adapterManager.get([chainLower]ChainId)
  if (!adapter) throw new Error('[ChainName] adapter not available')
  assertGet[Chain]ChainAdapter(adapter)

  const address = await adapter.getAddress({
    wallet,
    accountNumber,
  })

  const accountId = toAccountId({ chainId: [chainLower]ChainId, account: address })
  const account = await adapter.getAccount(address)

  const metadata: AccountMetadata = {
    accountType: 'native',
    bip44Params: adapter.getBip44Params({ accountNumber }),
  }

  return {
    [accountId]: metadata,
  }
}
Wire into account dispatcher:
typescript
// In src/lib/account/account.ts
case KnownChainIds.[ChainName]Mainnet:
  return derive[Chain]AccountIdsAndMetadata(args)
文件
src/lib/account/[chainname].ts
typescript
import { [chainLower]ChainId, toAccountId } from '@shapeshiftoss/caip'
import type { AccountMetadata, AccountMetadataByAccountId } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'
import { assertGet[Chain]ChainAdapter, is[Chain]ChainAdapter } from '@/lib/utils/[chainname]'

export const derive[Chain]AccountIdsAndMetadata = async (
  args: // 标准参数
): Promise<AccountMetadataByAccountId> => {
  const { accountNumber, chainIds, wallet } = args

  const adapter = adapterManager.get([chainLower]ChainId)
  if (!adapter) throw new Error('[ChainName]适配器不可用')
  assertGet[Chain]ChainAdapter(adapter)

  const address = await adapter.getAddress({
    wallet,
    accountNumber,
  })

  const accountId = toAccountId({ chainId: [chainLower]ChainId, account: address })
  const account = await adapter.getAccount(address)

  const metadata: AccountMetadata = {
    accountType: 'native',
    bip44Params: adapter.getBip44Params({ accountNumber }),
  }

  return {
    [accountId]: metadata,
  }
}
连接到账户调度器
typescript
// 在src/lib/account/account.ts中
case KnownChainIds.[ChainName]Mainnet:
  return derive[Chain]AccountIdsAndMetadata(args)

Step 3.7: Add Wallet Support Detection

步骤3.7:添加钱包支持检测(关键!)

File:
src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts
typescript
// Add to switch statement
case KnownChainIds.[ChainName]Mainnet:
  return supports[Chain](wallet) // from hdwallet-core
重要:最近的链(Tron、SUI、Monad、HyperEVM、Plasma)都遗漏了这一步,导致资产无法正确显示!
文件
src/state/slices/portfolioSlice/utils/index.ts
isAssetSupportedByWallet
函数中添加你的链(约367行):
typescript
// 1. 在顶部导入你的链ID
import {
  // ... 现有导入
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

// 2. 从hdwallet-core导入支持函数
import {
  // ... 现有导入
  supports[ChainName],
} from '@shapeshiftoss/hdwallet-core'

// 3. 在isAssetSupportedByWallet的switch语句中添加case
export const isAssetSupportedByWallet = (assetId: AssetId, wallet: HDWallet): boolean => {
  if (!assetId) return false
  const { chainId } = fromAssetId(assetId)
  switch (chainId) {
    // ... 现有case
    case [chainLower]ChainId:
      return supports[ChainName](wallet)
    // ... 其他case
    default:
      return false
  }
}
重要性:此函数决定钱包是否可以使用特定资产。如果没有这一步,即使其他配置都正确,你的链的资产也不会出现在钱包UI中!
示例:对于HyperEVM,添加:
typescript
case hyperEvmChainId:
  return supportsHyperEvm(wallet)

Step 3.8: Add Asset Support Detection (CRITICAL!)

阶段4:Web插件与功能标志

步骤4.1:创建插件

IMPORTANT: This was missing for recent chains (Tron, SUI, Monad, HyperEVM, Plasma) and caused assets not to show up properly!
File:
src/state/slices/portfolioSlice/utils/index.ts
Add your chain to the
isAssetSupportedByWallet
function around line 367:
typescript
// 1. Import your chain ID at the top
import {
  // ... existing imports
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

// 2. Import the support function from hdwallet-core
import {
  // ... existing imports
  supports[ChainName],
} from '@shapeshiftoss/hdwallet-core'

// 3. Add case to the switch statement in isAssetSupportedByWallet
export const isAssetSupportedByWallet = (assetId: AssetId, wallet: HDWallet): boolean => {
  if (!assetId) return false
  const { chainId } = fromAssetId(assetId)
  switch (chainId) {
    // ... existing cases
    case [chainLower]ChainId:
      return supports[ChainName](wallet)
    // ... rest of cases
    default:
      return false
  }
}
Why this matters: This function determines if a wallet can use a particular asset. Without it, assets for your chain won't appear in wallet UIs even if everything else is configured correctly!
Example: For HyperEVM, add:
typescript
case hyperEvmChainId:
  return supportsHyperEvm(wallet)

文件
src/plugins/[chainname]/index.tsx
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import { [chainLower] } from '@shapeshiftoss/chain-adapters'
import { KnownChainIds } from '@shapeshiftoss/types'
import { getConfig } from '@/config'
import type { Plugins } from '@/plugins/types'

export default function register(): Plugins {
  return [
    [
      '[chainLower]ChainAdapter',
      {
        name: '[chainLower]ChainAdapter',
        featureFlag: ['[ChainName]'],
        providers: {
          chainAdapters: [
            [
              KnownChainIds.[ChainName]Mainnet,
              () => {
                return new [chainLower].ChainAdapter({
                  rpcUrl: getConfig().VITE_[CHAIN]_NODE_URL,
                  // 添加其他所需配置
                })
              },
            ],
          ],
        },
      },
    ],
  ]
}
注册插件
typescript
// 在src/plugins/activePlugins.ts中
import [chainLower] from './[chainname]'

export const activePlugins = [
  // ...
  [chainLower],
]
在提供程序中管控
typescript
// 在src/context/PluginProvider/PluginProvider.tsx中
// 添加你的链的功能标志检查

Phase 4: Web Plugin & Feature Flags

步骤4.2:环境变量

Step 4.1: Create Plugin

File:
src/plugins/[chainname]/index.tsx
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import { [chainLower] } from '@shapeshiftoss/chain-adapters'
import { KnownChainIds } from '@shapeshiftoss/types'
import { getConfig } from '@/config'
import type { Plugins } from '@/plugins/types'

export default function register(): Plugins {
  return [
    [
      '[chainLower]ChainAdapter',
      {
        name: '[chainLower]ChainAdapter',
        featureFlag: ['[ChainName]'],
        providers: {
          chainAdapters: [
            [
              KnownChainIds.[ChainName]Mainnet,
              () => {
                return new [chainLower].ChainAdapter({
                  rpcUrl: getConfig().VITE_[CHAIN]_NODE_URL,
                  // Add other config as needed
                })
              },
            ],
          ],
        },
      },
    ],
  ]
}
Register plugin:
typescript
// In src/plugins/activePlugins.ts
import [chainLower] from './[chainname]'

export const activePlugins = [
  // ...
  [chainLower],
]
Gate in provider:
typescript
// In src/context/PluginProvider/PluginProvider.tsx
// Add feature flag check for your chain
文件
.env
bash
VITE_[CHAIN]_NODE_URL=[默认公共RPC]
VITE_FEATURE_[CHAIN]=false
文件
.env.development
bash
VITE_[CHAIN]_NODE_URL=[开发RPC]
VITE_FEATURE_[CHAIN]=true
文件
.env.production
bash
VITE_[CHAIN]_NODE_URL=[生产RPC]
VITE_FEATURE_[CHAIN]=false

Step 4.2: Environment Variables

步骤4.3:配置验证

File:
.env
bash
VITE_[CHAIN]_NODE_URL=[default-public-rpc]
VITE_FEATURE_[CHAIN]=false
File:
.env.development
bash
VITE_[CHAIN]_NODE_URL=[dev-rpc]
VITE_FEATURE_[CHAIN]=true
File:
.env.production
bash
VITE_[CHAIN]_NODE_URL=[prod-rpc]
VITE_FEATURE_[CHAIN]=false
文件
src/config.ts
typescript
const validators = {
  // ...
  VITE_[CHAIN]_NODE_URL: url(),
  VITE_FEATURE_[CHAIN]: bool({ default: false }),
}

Step 4.3: Config Validation

步骤4.4:功能标志状态

File:
src/config.ts
typescript
const validators = {
  // ...
  VITE_[CHAIN]_NODE_URL: url(),
  VITE_FEATURE_[CHAIN]: bool({ default: false }),
}
文件
src/state/slices/preferencesSlice/preferencesSlice.ts
typescript
export type FeatureFlags = {
  // ...
  [ChainName]: boolean
}

const initialState: PreferencesState = {
  featureFlags: {
    // ...
    [ChainName]: getConfig().VITE_FEATURE_[CHAIN],
  },
}
添加到测试模拟
typescript
// 在src/test/mocks/store.ts中
featureFlags: {
  // ...
  [ChainName]: false,
}

Step 4.4: Feature Flag State

步骤4.5:CSP头

File:
src/state/slices/preferencesSlice/preferencesSlice.ts
typescript
export type FeatureFlags = {
  // ...
  [ChainName]: boolean
}

const initialState: PreferencesState = {
  featureFlags: {
    // ...
    [ChainName]: getConfig().VITE_FEATURE_[CHAIN],
  },
}
Add to test mocks:
typescript
// In src/test/mocks/store.ts
featureFlags: {
  // ...
  [ChainName]: false,
}
文件
headers/csps/chains/[chainname].ts
typescript
const [chainLower]: Csp = {
  'connect-src': [env.VITE_[CHAIN]_NODE_URL],
}

export default [chainLower]
注册CSP
typescript
// 在headers/csps/index.ts中
import [chainLower] from './chains/[chainname]'

export default [
  // ...
  [chainLower],
]

Step 4.5: CSP Headers

阶段5:资产生成

步骤5.1:CoinGecko适配器集成

File:
headers/csps/chains/[chainname].ts
typescript
const [chainLower]: Csp = {
  'connect-src': [env.VITE_[CHAIN]_NODE_URL],
}

export default [chainLower]
Register CSP:
typescript
// In headers/csps/index.ts
import [chainLower] from './chains/[chainname]'

export default [
  // ...
  [chainLower],
]

关键:此步骤是资产发现和定价所必需的!参考Monad示例PR #11257。
文件
packages/caip/src/adapters/coingecko/index.ts
添加你的链到CoingeckoAssetPlatform枚举,并导入链ID:
typescript
// 在顶部添加导入
import {
  // ... 现有导入
  [chainLower]ChainId,
} from '../../constants'

// 添加平台常量
export enum CoingeckoAssetPlatform {
  // ... 现有平台
  [ChainName] = '[coingecko-platform-id]', // 例如:'hyperevm'对应HyperEVM
}
文件
packages/caip/src/adapters/coingecko/utils.ts
chainIdToCoingeckoAssetPlatform
函数中添加链ID到平台的映射:
typescript
// 对于EVM链,添加到EVM switch语句
case CHAIN_REFERENCE.[ChainName]Mainnet:
  return CoingeckoAssetPlatform.[ChainName]

// 对于非EVM链,在外部switch中添加单独的case
文件
packages/caip/src/adapters/coingecko/utils.test.ts
为你的链添加测试用例:
typescript
it('为[ChainName]返回正确的平台', () => {
  expect(chainIdToCoingeckoAssetPlatform([chainLower]ChainId)).toEqual(
    CoingeckoAssetPlatform.[ChainName]
  )
})
文件
packages/caip/src/adapters/coingecko/index.test.ts
为你的链添加测试资产:
typescript
// 在测试夹具中添加你的链的示例资产
const [chainLower]UsdcAssetId: AssetId = 'eip155:[CHAIN_ID]/erc20:[USDC_ADDRESS]'

// 更新测试预期以包含你的链的资产

Phase 5: Asset Generation

步骤5.2:创建资产生成器

Step 5.1: CoinGecko Adapter Integration

CRITICAL: This step is required for asset discovery and pricing! See PR #11257 for Monad example.
File:
packages/caip/src/adapters/coingecko/index.ts
Add your chain to the CoingeckoAssetPlatform enum and import the chain ID:
typescript
// Add import at top
import {
  // ... existing imports
  [chainLower]ChainId,
} from '../../constants'

// Add platform constant
export enum CoingeckoAssetPlatform {
  // ... existing platforms
  [ChainName] = '[coingecko-platform-id]', // e.g., 'hyperevm' for HyperEVM
}
File:
packages/caip/src/adapters/coingecko/utils.ts
Add chain ID to platform mapping in the
chainIdToCoingeckoAssetPlatform
function:
typescript
// For EVM chains, add to the EVM switch statement
case CHAIN_REFERENCE.[ChainName]Mainnet:
  return CoingeckoAssetPlatform.[ChainName]

// For non-EVM chains, add separate case in outer switch
File:
packages/caip/src/adapters/coingecko/utils.test.ts
Add test case for your chain:
typescript
it('returns correct platform for [ChainName]', () => {
  expect(chainIdToCoingeckoAssetPlatform([chainLower]ChainId)).toEqual(
    CoingeckoAssetPlatform.[ChainName]
  )
})
File:
packages/caip/src/adapters/coingecko/index.test.ts
Add test asset for your chain:
typescript
// Add example asset from your chain to test fixtures
const [chainLower]UsdcAssetId: AssetId = 'eip155:[CHAIN_ID]/erc20:[USDC_ADDRESS]'

// Update test expectations to include your chain's asset
文件
scripts/generateAssetData/[chainname]/index.ts
遵循monad/tron/sui的模式:
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import type { Asset } from '@shapeshiftoss/types'
import { [chainLower], unfreeze } from '@shapeshiftoss/utils'

import * as coingecko from '../coingecko'

export const getAssets = async (): Promise<Asset[]> => {
  const assets = await coingecko.getAssets([chainLower]ChainId)

  return [...assets, unfreeze([chainLower])]
}
连接到生成器
  1. scripts/generateAssetData/generateAssetData.ts
    中导入
typescript
import * as [chainLower] from './[chainname]'
  1. generateAssetData()
    函数中获取资产
typescript
const [chainLower]Assets = await [chainLower].getAssets()
  1. 添加到unfilteredAssetData数组
typescript
  ...[chainLower]Assets,
将链添加到CoinGecko脚本
文件
scripts/generateAssetData/coingecko.ts
导入你的链:
typescript
import {
  // ... 现有导入
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

import {
  // ... 现有导入
  [chainLower],
} from '@shapeshiftoss/utils'
在switch语句中添加case(约133行+):
typescript
case [chainLower]ChainId:
  return {
    assetNamespace: ASSET_NAMESPACE.erc20, // 或trc20、suiCoin等
    category: adapters.chainIdToCoingeckoAssetPlatform(chainId),
    explorer: [chainLower].explorer,
    explorerAddressLink: [chainLower].explorerAddressLink,
    explorerTxLink: [chainLower].explorerTxLink,
  }

Step 5.2: Create Asset Generator

步骤5.3:Swapper支持发现与集成

File:
scripts/generateAssetData/[chainname]/index.ts
Follow the pattern from monad/tron/sui:
typescript
import { [chainLower]ChainId } from '@shapeshiftoss/caip'
import type { Asset } from '@shapeshiftoss/types'
import { [chainLower], unfreeze } from '@shapeshiftoss/utils'

import * as coingecko from '../coingecko'

export const getAssets = async (): Promise<Asset[]> => {
  const assets = await coingecko.getAssets([chainLower]ChainId)

  return [...assets, unfreeze([chainLower])]
}
Wire into generator:
  1. Import in
    scripts/generateAssetData/generateAssetData.ts
    :
typescript
import * as [chainLower] from './[chainname]'
  1. Fetch assets in the
    generateAssetData()
    function:
typescript
const [chainLower]Assets = await [chainLower].getAssets()
  1. Add to unfilteredAssetData array:
typescript
  ...[chainLower]Assets,
Add chain to CoinGecko script:
File:
scripts/generateAssetData/coingecko.ts
Import your chain:
typescript
import {
  // ... existing imports
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

import {
  // ... existing imports
  [chainLower],
} from '@shapeshiftoss/utils'
Add case in the switch statement (around line 133+):
typescript
case [chainLower]ChainId:
  return {
    assetNamespace: ASSET_NAMESPACE.erc20, // or trc20, suiCoin, etc.
    category: adapters.chainIdToCoingeckoAssetPlatform(chainId),
    explorer: [chainLower].explorer,
    explorerAddressLink: [chainLower].explorerAddressLink,
    explorerTxLink: [chainLower].explorerTxLink,
  }
关键:将你的链添加到支持的swapper中,以便用户可以实际交易!

Step 5.3: Swapper Support Discovery & Integration

步骤5.3a:调研哪些Swapper支持你的链

CRITICAL: Add your chain to supported swappers so users can actually trade!
使用
AskUserQuestion
询问
问题:哪些Swapper支持[ChainName]?
选项
  1. "我知道哪些Swapper支持" → 用户提供列表
  2. "你能调研一下吗?" → 搜索Swapper文档和支持链列表
  3. "先添加Relay" → 先从Relay开始,之后添加其他
背景:不同的Swapper支持不同的链。我们需要将你的链添加到每个支持它的Swapper中。
搜索Swapper支持
  1. Relay:检查https://docs.relay.link/resources/supported-chains
  2. 0x/Matcha:检查https://0x.org/docs/introduction/0x-cheat-sheet
  3. OneInch:检查https://docs.1inch.io/docs/aggregation-protocol/introduction
  4. CowSwap:检查https://docs.cow.fi/cow-protocol/reference/contracts/deployments
  5. Jupiter:仅支持Solana
  6. THORChain:检查https://docs.thorchain.org/chain-clients/overview
常见模式
  • 大多数EVM链:Relay、0x,可能还有OneInch
  • Ethereum L2:Relay、0x、CowSwap、OneInch
  • 非EVM链:Relay(若支持)、链特定DEX

Step 5.3a: Research Which Swappers Support Your Chain

步骤5.3b:Relay Swapper集成(最常见)

Use
AskUserQuestion
to ask:
Question: Which swappers support [ChainName]?
Options:
  1. "I know which swappers" → User provides list
  2. "Can you research it?" → Search swapper docs and supported chains lists
  3. "Just add Relay for now" → Start with Relay, add others later
Context: Different swappers support different chains. We need to add your chain to each swapper that supports it.
Search for swapper support:
  1. Relay: Check https://docs.relay.link/resources/supported-chains
  2. 0x/Matcha: Check https://0x.org/docs/introduction/0x-cheat-sheet
  3. OneInch: Check https://docs.1inch.io/docs/aggregation-protocol/introduction
  4. CowSwap: Check https://docs.cow.fi/cow-protocol/reference/contracts/deployments
  5. Jupiter: Solana-only
  6. THORChain: Check https://docs.thorchain.org/chain-clients/overview
Common patterns:
  • Most EVM chains: Relay, 0x, possibly OneInch
  • Ethereum L2s: Relay, 0x, CowSwap, OneInch
  • Non-EVM: Relay (if supported), chain-specific DEXes
对于Relay Swapper(支持大多数链):
重要 - 先检查viem链定义
  1. 搜索viem的链定义:检查你的链是否存在于viem中:
  2. 若需要,更新viem:对于新/近期的链,先将viem更新到最新版本:
    bash
    # 检查当前版本
    yarn why viem
    
    # 更新到最新固定版本
    yarn up viem@latest
    
    # 重建包
    yarn build:packages
  3. 仅当不可用时手动定义:若链在viem中不存在,使用
    defineChain()
    模式(参考viem文档)
文件
packages/swapper/src/swappers/RelaySwapper/constant.ts
将你的链添加到Relay链ID映射:
typescript
import {
  // ... 现有导入
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

// 若为EVM链,添加到viem链导入(检查viem/chains的确切导出名称!)
import {
  // ... 现有链
  [chainLower], // 例如:从viem/chains导入hyperEvm(注意:是hyperEvm而非hyperliquid!)
} from 'viem/chains'

export const chainIdToRelayChainId = {
  // ... 现有映射
  [[chainLower]ChainId]: [chainLower].id, // 对于使用viem的EVM链
  // 或
  [[chainLower]ChainId]: [RELAY_CHAIN_ID], // 对于非EVM链(从Relay文档获取)
}
文件
packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
relayTokenToAssetId
函数中添加原生资产case:
typescript
// 在原生资产的switch语句中添加case(约100行+)
case CHAIN_REFERENCE.[ChainName]Mainnet:
  return {
    assetReference: ASSET_REFERENCE.[ChainName],
    assetNamespace: ASSET_NAMESPACE.slip44,
  }
重要:确保
constant.ts
chainIdToRelayChainId
映射中的所有链,在
relayTokenToAssetId.ts
的switch语句中都有对应的case。缺少case会导致运行时错误,如
chainId 'XX' not supported
检查Relay文档

Step 5.3b: Relay Swapper Integration (Most Common)

步骤5.4:生成资产(分步方法)

For Relay Swapper (supports most chains):
IMPORTANT - Check viem chain definitions first:
  1. Search viem's chain definitions: Check if your chain exists in viem:
  2. Update viem if needed: For new/recent chains, update viem to latest version first:
    bash
    # Check current version
    yarn why viem
    
    # Update to latest pinned version
    yarn up viem@latest
    
    # Rebuild packages
    yarn build:packages
  3. Only define manually if unavailable: If the chain doesn't exist in viem, use
    defineChain()
    pattern (see viem docs)
File:
packages/swapper/src/swappers/RelaySwapper/constant.ts
Add your chain to the Relay chain ID mapping:
typescript
import {
  // ... existing imports
  [chainLower]ChainId,
} from '@shapeshiftoss/caip'

// Add to viem chain imports if EVM (check viem/chains for exact export name!)
import {
  // ... existing chains
  [chainLower], // e.g., hyperEvm from viem/chains (note: hyperEvm not hyperliquid!)
} from 'viem/chains'

export const chainIdToRelayChainId = {
  // ... existing mappings
  [[chainLower]ChainId]: [chainLower].id, // For EVM chains using viem
  // OR
  [[chainLower]ChainId]: [RELAY_CHAIN_ID], // For non-EVM (get from Relay docs)
}
File:
packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
Add native asset case in the
relayTokenToAssetId
function:
typescript
// Add to the switch statement for native assets (around line 100+)
case CHAIN_REFERENCE.[ChainName]Mainnet:
  return {
    assetReference: ASSET_REFERENCE.[ChainName],
    assetNamespace: ASSET_NAMESPACE.slip44,
  }
IMPORTANT: Make sure ALL chains that are in the
chainIdToRelayChainId
mapping in
constant.ts
have a corresponding case in the switch statement in
relayTokenToAssetId.ts
. Missing cases will cause runtime errors like
chainId 'XX' not supported
.
Check Relay docs for your chain:
重要:资产生成需要Zerion API密钥以进行相关资产索引。
使用
AskUserQuestion
向用户请求Zerion API密钥
问题:你有Zerion API密钥来运行资产生成吗?
选项
  1. "有,密钥是" → 用户提供密钥永远不要存储在版本控制系统中!
  2. "没有,现在跳过" → 跳过资产生成,用户稍后可以手动运行
背景:资产生成获取代币元数据,需要Zerion API密钥。密钥通过环境变量传递,永远不要提交到版本控制系统
使用
AskUserQuestion
询问用户如何运行生成
问题:你希望如何运行资产生成流程?
选项
  1. "我自己运行" → 将命令复制到剪贴板(echo | pbcopy),用户自行运行,进度可见性更好
  2. "由Claude运行" → Claude在后台运行所有步骤。⚠️ 警告:可能需要5-10分钟,进度可见性有限。你将看不到详细的进度输出。
背景:资产生成有5个步骤(caip-adapters、color-map、asset-data、tradable-asset-map、thor-longtail)。手动运行可以完全看到进度(你会看到"chain_id: hyperevm"代币正在处理)。由Claude运行无需干预,但你看不到详细进度,处理数千个代币时可能会看起来卡住几分钟。
分步运行生成脚本(比
generate:all
可见性更好):
bash
undefined

Step 5.4: Generate Assets (Step-by-Step Approach)

步骤1:生成CoinGecko CAIP适配器(从代码生成JSON映射)

IMPORTANT: Asset generation requires a Zerion API key for related asset indexing.
Ask user for Zerion API key using
AskUserQuestion
:
Question: Do you have a Zerion API key to run asset generation?
Options:
  1. "Yes, here it is" → User provides key (NEVER store in VCS!)
  2. "No, skip for now" → Skip asset generation, user can run manually later
Context: Asset generation fetches token metadata and requires a Zerion API key. The key is passed via environment variable and should NEVER be committed to VCS.
Ask user how they want to run generation using
AskUserQuestion
:
Question: How do you want to run the asset generation pipeline?
Options:
  1. "I'll run it myself" → Copy command to clipboard (echo | pbcopy), user runs it, better visibility of progress
  2. "Claude runs it" → Claude runs all steps in background. ⚠️ WARNING: May take 5-10 minutes with limited visibility. You'll see less progress output.
Context: Asset generation has 5 steps (caip-adapters, color-map, asset-data, tradable-asset-map, thor-longtail). Running manually gives full visibility of progress (you'll see "chain_id: hyperevm" tokens being processed). Claude running it is hands-off but you won't see detailed progress, and it may appear stuck for several minutes while processing thousands of tokens.
Run generation scripts ONE AT A TIME (better visibility than
generate:all
):
bash
undefined
yarn generate:caip-adapters

Step 1: Generate CoinGecko CAIP adapters (JSON mappings from our code)

✓ 生成packages/caip/src/adapters/coingecko/generated/eip155_999/adapter.json

✓ 约需10秒

步骤2:生成颜色映射(识别新资产)

yarn generate:caip-adapters
yarn generate:color-map

✓ Generates packages/caip/src/adapters/coingecko/generated/eip155_999/adapter.json

✓ 更新scripts/generateAssetData/color-map.json

✓ Takes ~10 seconds

✓ 约需5秒

Step 2: Generate color map (picks up new assets)

步骤3:生成资产数据(从CoinGecko获取代币)

yarn generate:color-map
ZERION_API_KEY=<用户提供的密钥> yarn generate:asset-data

✓ Updates scripts/generateAssetData/color-map.json

✓ 从CoinGecko平台'hyperevm'获取所有HyperEVM ERC20代币

✓ Takes ~5 seconds

✓ 更新src/assets/generated/

Step 3: Generate asset data (fetches tokens from CoinGecko)

✓ 需2-5分钟 - 你应该看到:

- "Total Portals tokens fetched for ethereum: XXXX"

- "Total Portals tokens fetched for base: XXXX"

- 输出中出现"chain_id": "hyperevm"(表示正在处理HyperEVM代币!)

- "Generated CoinGecko AssetId adapter data."

- "Asset data generated successfully"

步骤4:生成可交易资产映射(用于Swapper支持)

ZERION_API_KEY=<user-provided-key> yarn generate:asset-data
yarn generate:tradable-asset-map

✓ Fetches all HyperEVM ERC20 tokens from CoinGecko platform 'hyperevm'

✓ 生成src/lib/swapper/constants.ts映射

✓ Updates src/assets/generated/

✓ 约需10秒

✓ Takes 2-5 minutes - YOU SHOULD SEE:

步骤5:生成Thor长尾代币(特定于Thor,大多数链可选)

- "Total Portals tokens fetched for ethereum: XXXX"

- "Total Portals tokens fetched for base: XXXX"

- "chain_id": "hyperevm" appearing in output (means HyperEVM tokens found!)

- "Generated CoinGecko AssetId adapter data."

- "Asset data generated successfully"

Step 4: Generate tradable asset map (for swapper support)

yarn generate:tradable-asset-map
yarn generate:thor-longtail-tokens

✓ Generates src/lib/swapper/constants.ts mappings

✓ 更新Thor长尾代币列表

✓ Takes ~10 seconds

✓ 约需5秒

Step 5: Generate Thor longtail tokens (Thor-specific, optional for most chains)

yarn generate:thor-longtail-tokens

**分步运行优于`generate:all`的原因**:
- ✅ 确切看到哪个步骤在运行
- ✅ 立即捕获错误
- ✅ 看到进度输出(如"chain_id": "hyperevm"代币正在处理)
- ✅ 可以跳过无关步骤(如非Thor链的thor-longtail)

**提交生成的资产**:
```bash
git add src/assets/generated/ packages/caip/src/adapters/coingecko/generated/ scripts/generateAssetData/color-map.json
git commit -m "feat: 生成[chainname]资产和映射"
⚠️ 关键:永远不要提交Zerion API密钥,仅通过命令行传递!

✓ Updates Thor longtail token list

步骤5.4:调研与添加Swapper支持

✓ Takes ~5 seconds


**Why step-by-step is better than `generate:all`**:
- ✅ See exactly which step is running
- ✅ Catch errors immediately
- ✅ See progress output (like "chain_id": "hyperevm" tokens being processed)
- ✅ Can skip irrelevant steps (e.g., thor-longtail for non-Thor chains)

**Commit generated assets**:
```bash
git add src/assets/generated/ packages/caip/src/adapters/coingecko/generated/ scripts/generateAssetData/color-map.json
git commit -m "feat: generate [chainname] assets and mappings"
⚠️ CRITICAL: NEVER commit the Zerion API key. Only use it in the command line.
重要:资产生成后,检查哪些Swapper支持你的新链!

Step 5.4: Research & Add Swapper Support

步骤5.4a:询问用户关于Swapper支持

IMPORTANT: After assets are generated, check which swappers support your new chain!
使用
AskUserQuestion
确定Swapper支持:
哪些Swapper支持[ChainName]?

选项:
1. "我知道哪些Swapper支持" → 用户提供列表
2. "帮我调研一下" → AI将搜索Swapper文档
3. "现在跳过" → 稍后可以添加Swapper支持

背景:不同的DEX聚合器支持不同的链。我们需要将你的链添加到每个支持它的Swapper中,以便用户可以交易。

Step 5.4a: Ask User About Swapper Support

步骤5.4b:若需要,调研常见Swapper支持

Use
AskUserQuestion
to determine swapper support:
Which swappers support [ChainName]?

Options:
1. "I know which swappers support it" → User provides list
2. "Research it for me" → AI will search swapper docs
3. "Skip for now" → Can add swapper support later

Context: Different DEX aggregators support different chains. We need to add your chain to each swapper that supports it so users can trade.
若用户选择"帮我调研一下",检查以下来源:
Relay(最常见,支持大多数链):
  • 文档:https://docs.relay.link/resources/supported-chains
  • 通常支持:Ethereum、Base、Arbitrum、Optimism、Polygon、Avalanche、BSC、Gnosis和许多新EVM链
  • 检查你的链的viem链定义是否存在(例如:'viem/chains'中的
    plasma
其他Swapper检查

Step 5.4b: Research Common Swapper Support (if needed)

步骤5.4c:添加Relay Swapper支持(最常见)

If user chooses "Research it for me", check these sources:
Relay (most common, supports most chains):
  • Docs: https://docs.relay.link/resources/supported-chains
  • Usually supports: Ethereum, Base, Arbitrum, Optimism, Polygon, Avalanche, BSC, Gnosis, and many new EVM chains
  • Check if your chain's viem chain definition exists (e.g.,
    plasma
    from 'viem/chains')
Other swappers to check:
若Relay支持你的链:
文件
packages/swapper/src/swappers/RelaySwapper/constant.ts
typescript
// 1. 添加导入
import {
  // ... 现有导入
  plasmaChainId,
} from '@shapeshiftoss/caip'

import {
  // ... 现有链
  plasma,  // 检查viem/chains是否导出你的链
} from 'viem/chains'

// 2. 添加到chainIdToRelayChainId映射
export const chainIdToRelayChainId = {
  // ... 现有映射
  [plasmaChainId]: plasma.id,  // 使用viem链ID
}
文件
packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
typescript
// 在switch语句中添加原生资产case(约124行):
case CHAIN_REFERENCE.PlasmaMainnet:
  return {
    assetReference: ASSET_REFERENCE.Plasma,
    assetNamespace: ASSET_NAMESPACE.slip44,
  }

Step 5.4c: Add Relay Swapper Support (Most Common)

步骤5.4d:添加其他Swapper支持(若需要)

If Relay supports your chain:
File:
packages/swapper/src/swappers/RelaySwapper/constant.ts
typescript
// 1. Add imports
import {
  // ... existing imports
  plasmaChainId,
} from '@shapeshiftoss/caip'

import {
  // ... existing chains
  plasma,  // Check if viem/chains exports your chain
} from 'viem/chains'

// 2. Add to chainIdToRelayChainId mapping
export const chainIdToRelayChainId = {
  // ... existing mappings
  [plasmaChainId]: plasma.id,  // Uses viem chain ID
}
File:
packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
typescript
// Add native asset case in switch statement (around line 124):
case CHAIN_REFERENCE.PlasmaMainnet:
  return {
    assetReference: ASSET_REFERENCE.Plasma,
    assetNamespace: ASSET_NAMESPACE.slip44,
  }
遵循类似模式添加其他Swapper(CowSwap、0x等)- 参考
swapper-integration
技能获取详细指导。
参考:Plasma添加到Relay Swapper以支持兑换

Step 5.4d: Add Other Swapper Support (As Needed)

步骤5.5:将原生资产添加到热门资产

Follow similar patterns for other swappers (CowSwap, 0x, etc.) - see
swapper-integration
skill for detailed guidance.
Reference: Plasma added to Relay swapper for swap support
关键:二等公民链不在CoinGecko市值前100,因此默认不会出现在热门资产列表中。这会导致用户筛选该链时,原生资产缺失。
文件
src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx
typescript
// 在顶部添加导入
import {
  hyperEvmAssetId,
  mayachainAssetId,
  monadAssetId,
  plasmaAssetId,  // Plasma示例
  [chainLower]AssetId,  // 添加你的链的资产ID
  thorchainAssetId,
  tronAssetId,
  suiAssetId,
} from '@shapeshiftoss/caip'

// 在queryFn中添加,在mayachain检查之后(约37行)
// 将二等公民链添加到热门资产以提高可发现性
if (enabledFlags.HyperEvm) assetIds.push(hyperEvmAssetId)
if (enabledFlags.Monad) assetIds.push(monadAssetId)
if (enabledFlags.Plasma) assetIds.push(plasmaAssetId)
if (enabledFlags.[ChainName]) assetIds.push([chainLower]AssetId)  // 添加你的链
if (enabledFlags.Tron) assetIds.push(tronAssetId)
if (enabledFlags.Sui) assetIds.push(suiAssetId)
原因
  • 热门资产从CoinGecko市值前100获取
  • 新/小链不在前100
  • 若没有这一步,筛选你的链时,仅会显示代币(通过relatedAssetIds)
  • 原生资产不会显示,这会让用户困惑
  • 示例:在不支持Monad的MetaMask中搜索"monad",会显示Monad代币但不会显示MON本身
参考PR
  • 查看Monad、Tron、Sui、Plasma和HyperEVM如何在同一PR中添加

Step 5.5: Add Native Asset to Popular Assets

阶段6:Ledger支持(可选)

CRITICAL: Second-class citizen chains are not in CoinGecko's top 100 by market cap, so they won't appear in the popular assets list by default. This causes the native asset to be missing when users filter by that chain.
File:
src/components/TradeAssetSearch/hooks/useGetPopularAssetsQuery.tsx
typescript
// Add import at the top
import {
  hyperEvmAssetId,
  mayachainAssetId,
  monadAssetId,
  plasmaAssetId,  // example for Plasma
  [chainLower]AssetId,  // Add your chain's asset ID
  thorchainAssetId,
  tronAssetId,
  suiAssetId,
} from '@shapeshiftoss/caip'

// Add to the queryFn, after the mayachain check (around line 37)
// add second-class citizen chains to popular assets for discoverability
if (enabledFlags.HyperEvm) assetIds.push(hyperEvmAssetId)
if (enabledFlags.Monad) assetIds.push(monadAssetId)
if (enabledFlags.Plasma) assetIds.push(plasmaAssetId)
if (enabledFlags.[ChainName]) assetIds.push([chainLower]AssetId)  // Add your chain
if (enabledFlags.Tron) assetIds.push(tronAssetId)
if (enabledFlags.Sui) assetIds.push(suiAssetId)
Why this is needed:
  • Popular assets are fetched from CoinGecko's top 100 by market cap
  • New/small chains aren't in the top 100
  • Without this, when filtering by your chain, only tokens appear (via relatedAssetIds)
  • The native asset won't show up, which is confusing for users
  • Example: Searching "monad" in MetaMask (doesn't support Monad) shows Monad tokens but not MON itself
Reference PRs:
  • See how Monad, Tron, Sui, Plasma, and HyperEVM were added in the same PR

仅当链受Ledger硬件支持时

Phase 6: Ledger Support (Optional)

步骤6.1:检查Ledger支持

Only if chain is supported by Ledger hardware
  1. 访问https://www.ledger.com/supported-crypto-assets
  2. 搜索你的链
  3. 记录Ledger应用名称
EVM链:通过以太坊应用自动支持 非EVM链:需要链特定的Ledger应用

Step 6.1: Check Ledger Support

步骤6.2:在HDWallet中添加Ledger支持

  1. Visit https://www.ledger.com/supported-crypto-assets
  2. Search for your chain
  3. Note the Ledger app name
For EVM chains: Automatically supported via Ethereum app For non-EVM: Needs chain-specific Ledger app
文件
packages/hdwallet-ledger/src/ledger.ts
遵循现有模式添加链支持。
EVM链:只需添加到支持链列表 非EVM链:实现链特定的Ledger传输

Step 6.2: Add Ledger Support in HDWallet

步骤6.3:添加到Web Ledger常量

File:
packages/hdwallet-ledger/src/ledger.ts
Add chain support following existing patterns.
For EVM: Just add to supported chains list For non-EVM: Implement chain-specific Ledger transport
文件
src/context/WalletProvider/Ledger/constants.ts
typescript
import { [chainLower]AssetId } from '@shapeshiftoss/caip'

export const availableLedgerAppAssetIds = [
  // ...
  [chainLower]AssetId,
]

Step 6.3: Add to Web Ledger Constants

步骤6.4:测试Ledger集成

File:
src/context/WalletProvider/Ledger/constants.ts
typescript
import { [chainLower]AssetId } from '@shapeshiftoss/caip'

export const availableLedgerAppAssetIds = [
  // ...
  [chainLower]AssetId,
]
  1. 连接Ledger设备
  2. 打开链特定应用
  3. 测试地址派生
  4. 测试交易签名

Step 6.4: Test Ledger Integration

阶段7:测试与验证

步骤7.1:类型检查

  1. Connect Ledger device
  2. Open chain-specific app
  3. Test address derivation
  4. Test transaction signing

bash
yarn type-check

Phase 7: Testing & Validation

修复任何TypeScript错误

Step 7.1: Type Check

bash
yarn type-check
undefined

Fix any TypeScript errors

步骤7.2:代码检查

undefined
bash
yarn lint --fix

Step 7.2: Lint

步骤7.3:构建

bash
yarn lint --fix
bash
yarn build

Step 7.3: Build

验证无构建错误

检查包大小是否异常增加

bash
yarn build
undefined

Verify no build errors

步骤7.4:手动测试清单

Check bundle size didn't explode

undefined
  • 连接原生钱包
  • 派生账户地址
  • 查看账户余额
  • 发送原生资产
  • 查看交易状态
  • 发送后检查余额更新
  • 测试从其他链兑换到该链
  • 测试从该链兑换到其他链
  • 验证错误处理(余额不足、网络错误等)
若支持Ledger:
  • 连接Ledger
  • 打开链应用
  • 派生地址
  • 签名交易
  • 广播交易

Step 7.4: Manual Testing Checklist

阶段8:清理与提交

步骤8.1:创建功能提交

  • Connect native wallet
  • Derive account address
  • View account balance
  • Send native asset
  • View transaction status
  • Check balance updates after send
  • Test swap TO chain from another chain
  • Test swap FROM chain to another chain
  • Verify error handling (insufficient balance, network errors, etc.)
If Ledger supported:
  • Connect Ledger
  • Open chain app
  • Derive address
  • Sign transaction
  • Broadcast transaction

现在所有内容都在同一monorepo中 — hdwallet包是
packages/hdwallet-*
下的工作区包。
bash
undefined

Phase 8: Clean Up & Commit

一次性提交所有内容

Step 8.1: Create Feature Commits

Everything is in the same monorepo now — hdwallet packages are workspace packages under
packages/hdwallet-*
.
bash
undefined
git add -A git commit -m "feat: 实现[chainname]
  • 添加[ChainName] hdwallet核心接口和原生钱包支持
  • 添加[ChainName]链适配器,使用简易RPC方案
  • 支持原生资产收发
  • 添加账户派生
  • 添加功能标志VITE_FEATURE_[CHAIN]
  • 添加从CoinGecko生成[chain]资产的逻辑
  • 连接交易状态轮询
  • 添加带功能标志管控的[chain]插件
目前处于功能标志管控下。"
undefined

Commit everything together

步骤8.2:打开PR

git add -A git commit -m "feat: implement [chainname]
  • Add [ChainName] hdwallet core interfaces and native wallet support
  • Add [ChainName] chain adapter with poor man's RPC
  • Support native asset sends/receives
  • Add account derivation
  • Add feature flag VITE_FEATURE_[CHAIN]
  • Add asset generation for [chain] from CoinGecko
  • Wire transaction status polling
  • Add [chain] plugin with feature flag gating
Behind feature flag for now."
undefined
bash
git push origin HEAD

Step 8.2: Open PR

打开PR到develop分支

bash
git push origin HEAD
gh pr create --title "feat: 实现[chainname]"
--body "添加[ChainName]基础支持,作为二等公民链..."

---

Open PR to develop

阶段9:常见陷阱与故障排除

陷阱1:代币精度问题

gh pr create --title "feat: implement [chainname]"
--body "Adds basic [ChainName] support as second-class citizen..."

---
问题:代币余额显示错误 解决方案:验证小数位数/精度与链元数据匹配 示例:Tron TRC20代币硬编码精度导致显示问题(#11222)

Phase 9: Common Gotchas & Troubleshooting

陷阱2:地址验证

Gotcha 1: Token Precision Issues

Problem: Token balances display incorrectly Solution: Verify decimals/precision match chain metadata Example: Tron TRC20 tokens hardcoded precision caused display issues (#11222)
问题:接受无效地址或拒绝有效地址 解决方案:使用链特定的验证(EVM的校验和、Tron的base58等) 示例:Tron地址解析问题(#11229)

Gotcha 2: Address Validation

陷阱3:交易广播

Problem: Invalid addresses accepted or valid ones rejected Solution: Use chain-specific validation (checksumming for EVM, base58 for Tron, etc.) Example: Tron address parsing issues (#11229)
问题:签名交易无法广播 解决方案:检查序列化格式是否符合链的要求 示例:确保正确的十六进制编码、Tron的网络字节等

Gotcha 3: Transaction Broadcasting

陷阱4:包大小

Problem: Signed transactions fail to broadcast Solution: Check serialization format matches chain expectations Example: Ensure proper hex encoding, network byte for Tron, etc.
问题:添加链SDK后构建大小急剧增加 解决方案:将大依赖提取到单独的chunk 示例:Sui SDK需要代码分割(#11238评论)

Gotcha 4: Bundle Size

陷阱5:最小交易金额

Problem: Build size explodes after adding chain SDK Solution: Extract large dependencies to separate chunk Example: Sui SDK needed code splitting (#11238 comments)
问题:小额兑换失败,无明确错误 解决方案:在Swapper中添加最小金额验证 示例:Tron代币需要最小金额(#11253)

Gotcha 5: Minimum Trade Amounts

陷阱6:代币分组

Problem: Small swaps fail without clear error Solution: Add minimum amount validation in swapper Example: Tron tokens need minimum amounts (#11253)
问题:代币在UI中未与相关资产分组 解决方案:检查资产命名空间和ID生成 示例:Tron/Sui代币分组问题(#11252)

Gotcha 6: Token Grouping

陷阱7:Ledger应用不匹配

Problem: Tokens don't group with related assets in UI Solution: Check asset namespace and ID generation Example: Tron/Sui tokens grouping issues (#11252)
问题:Ledger交易失败,错误不明确 解决方案:验证映射了正确的Ledger应用 示例:EVM链使用以太坊应用,而非链特定应用

Gotcha 7: Ledger App Mismatch

陷阱8:缺少walletSupportsChain Case(关键 - 阻止账户发现!)

Problem: Ledger transactions fail with unclear error Solution: Verify correct Ledger app is mapped Example: Use Ethereum app for EVM chains, not chain-specific app
问题:资产显示,但链的账户未被派生/发现 症状
  • 链适配器已注册
  • 资产出现在资产列表中
  • 但钱包中该链无账户
  • 日志显示链ID的账户派生从未运行 根本原因
    walletSupportsChain()
    switch语句中缺少case 解决方案:在
    src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts
    中添加你的链:
typescript
// 1. 导入链ID
import { [chainLower]ChainId } from '@shapeshiftoss/caip'

// 2. 从hdwallet-core导入支持函数
import { supports[ChainName] } from '@shapeshiftoss/hdwallet-core'

// 3. 添加到switch语句(约186行+)
case [chainLower]ChainId:
  return supports[ChainName](wallet)
示例:HyperEVM遗漏了这一步 - 导致账户发现完全跳过该链 原因
useDiscoverAccounts
使用
walletSupportsChain()
筛选链。若返回false,链永远不会传递给
deriveAccountIdsAndMetadata()
→ 无账户! 参考:与Plasma PR #11361的问题相同,但针对钱包支持而非功能标志

Gotcha 8: Missing walletSupportsChain Case (CRITICAL - BLOCKS ACCOUNT DISCOVERY!)

陷阱9:功能标志不工作

Problem: Assets appear but no accounts are derived/discovered for the chain Symptoms:
  • Chain adapter is registered
  • Assets show up in asset list
  • But wallet shows no accounts for the chain
  • Logs show account derivation never runs for chainId Root Cause: Missing case in
    walletSupportsChain()
    switch statement Solution: Add your chain to the switch statement in
    src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts
    :
typescript
// 1. Import chain ID
import { [chainLower]ChainId } from '@shapeshiftoss/caip'

// 2. Import support function from hdwallet-core
import { supports[ChainName] } from '@shapeshiftoss/hdwallet-core'

// 3. Add to switch statement (around line 186+)
case [chainLower]ChainId:
  return supports[ChainName](wallet)
Example: HyperEVM was missing this - caused account discovery to skip it entirely Why it matters:
useDiscoverAccounts
filters chains using
walletSupportsChain()
. If it returns false, the chain is never passed to
deriveAccountIdsAndMetadata()
→ no accounts! Reference: Same issue as Plasma PR #11361 but for wallet support instead of feature flag
问题:即使启用标志,链也不显示 解决方案:检查所有检查标志的位置:
  • 插件注册(featureFlag数组)
  • PluginProvider管控(添加chainId筛选)
  • 资产服务筛选
  • 常量数组(SECOND_CLASS_CHAINS)

Gotcha 9: Feature Flag Not Working

陷阱10:余额更新

Problem: Chain doesn't appear even with flag enabled Solution: Check ALL places flags are checked:
  • Plugin registration (featureFlag array)
  • PluginProvider gating (add chainId filter)
  • Asset service filtering
  • Constants array (SECOND_CLASS_CHAINS)
问题:交易后余额不更新 解决方案:在交易状态订阅器中实现轮询 示例:在useSendActionSubscriber中添加链case

Gotcha 10: Balance Updates

陷阱11:RPC速率限制

Problem: Balances don't update after transactions Solution: Implement polling in tx status subscriber Example: Add chain case in useSendActionSubscriber
问题:请求间歇性失败 解决方案:添加重试逻辑,使用多个RPC端点 示例:实现备用RPC URL

Gotcha 11: RPC Rate Limiting

陷阱12:缺少CoinGecko脚本Case

Problem: Requests fail intermittently Solution: Add retry logic, use multiple RPC endpoints Example: Implement fallback RPC URLs
问题
yarn generate:asset-data
失败,错误为"no coingecko token support for chainId" 解决方案:在
scripts/generateAssetData/coingecko.ts
中添加你的链case 需更新文件
  • 从caip导入
    [chainLower]ChainId
  • 从utils导入
    [chainLower]
    基础资产
  • 在switch语句中添加case,包含assetNamespace、category、浏览器链接 示例:参考HyperEVM case(约143行)的模式

Gotcha 12: Missing CoinGecko Script Case

陷阱13:需要Zerion API密钥

Problem:
yarn generate:asset-data
fails with "no coingecko token support for chainId" Solution: Add your chain case to
scripts/generateAssetData/coingecko.ts
Files to update:
  • Import
    [chainLower]ChainId
    from caip
  • Import
    [chainLower]
    base asset from utils
  • Add case in switch statement with assetNamespace, category, explorer links Example: See HyperEVM case (line ~143) for pattern
问题:资产生成失败,错误为"Missing Zerion API key" 解决方案:通过
AskUserQuestion
获取用户密钥,作为环境变量传递 命令
ZERION_API_KEY=<key> yarn generate:all
关键:永远不要将Zerion API密钥提交到版本控制系统! 示例:仅通过命令行传递密钥

Gotcha 13: Zerion API Key Required

陷阱14:AssetService缺少功能标志筛选

Problem: Asset generation fails with "Missing Zerion API key" Solution: Get key from user via
AskUserQuestion
, pass as env var Command:
ZERION_API_KEY=<key> yarn generate:all
CRITICAL: NEVER commit the Zerion API key to VCS! Example: Always pass key via command line only
问题:即使功能标志禁用,你的链的资产仍显示 解决方案:向AssetService添加功能标志筛选 文件
src/lib/asset-service/service/AssetService.ts
代码
if (!config.VITE_FEATURE_[CHAIN] && asset.chainId === [chainLower]ChainId) return false
示例:参考Monad/Tron/Sui模式(约53行) 参考:在PR #11241(Monad)中修复 - 最初被遗漏

Gotcha 14: AssetService Missing Feature Flag Filter

陷阱15:缺少evmChainIds数组(仅EVM链)

Problem: Assets for your chain appear even when feature flag is disabled Solution: Add feature flag filter to AssetService File:
src/lib/asset-service/service/AssetService.ts
Code:
if (!config.VITE_FEATURE_[CHAIN] && asset.chainId === [chainLower]ChainId) return false
Example: See line ~53 for Monad/Tron/Sui pattern Reference: Fixed in PR #11241 (Monad) - was initially forgotten
问题:TypeScript错误"Type 'KnownChainIds.[Chain]Mainnet' is not assignable to type EvmChainId" 解决方案:将你的链添加到EvmBaseAdapter中的
evmChainIds
数组 需更新文件
  • packages/chain-adapters/src/evm/EvmBaseAdapter.ts
    (约70行)
  • 添加到
    evmChainIds
    数组:
    KnownChainIds.[Chain]Mainnet
  • 添加到
    targetNetwork
    对象(约210行):网络名称、符号、浏览器 示例:HyperEVM添加在81行和262-266行 原因:该数组定义哪些链是EVM兼容的,用于类型检查

Gotcha 15: Missing from evmChainIds Array (EVM Chains Only)

陷阱16:缺少ChainSpecific类型映射(所有链 - 4个位置!)

Problem: TypeScript errors "Type 'KnownChainIds.[Chain]Mainnet' is not assignable to type EvmChainId" Solution: Add your chain to the
evmChainIds
array in EvmBaseAdapter Files to update:
  • packages/chain-adapters/src/evm/EvmBaseAdapter.ts
    (line ~70)
  • Add to
    evmChainIds
    array:
    KnownChainIds.[Chain]Mainnet
  • Add to
    targetNetwork
    object (line ~210): network name, symbol, explorer Example: HyperEVM added at lines 81 and 262-266 Why: The array defines which chains are EVM-compatible for type checking
问题:TypeScript错误如:
  • "Property 'chainSpecific' does not exist on type 'Account<T>'"
  • "Property 'chainSpecific' does not exist on type 'BuildSendApiTxInput<T>'"
  • "Property 'chainSpecific' does not exist on type 'GetFeeDataInput<T>'"
解决方案:在chain-adapters/src/types.ts的四个类型映射对象中添加你的链
文件
packages/chain-adapters/src/types.ts
四个映射都需要
  1. ~45行:
    ChainSpecificAccount
    [KnownChainIds.[Chain]Mainnet]: evm.Account
  2. ~91行:
    ChainSpecificFeeData
    [KnownChainIds.[Chain]Mainnet]: evm.FeeData
  3. ~219行:
    ChainSpecificBuildTxInput
    [KnownChainIds.[Chain]Mainnet]: evm.BuildTxInput
  4. ~320行:
    ChainSpecificGetFeeDataInput
    [KnownChainIds.[Chain]Mainnet]: evm.GetFeeDataInput
示例:HyperEVM添加在45、91、219、320行
原因:TypeScript使用这些来确定链特定的数据结构
关键:即使遗漏一个,也会导致模糊的类型错误!所有4个都需要添加到所有链(EVM和非EVM)。

Gotcha 16: Missing ChainSpecific Type Mappings (ALL Chains - 4 Places!)

陷阱17:缺少accountIdToLabel Case(阻止地址显示!)

Problem: TypeScript errors like:
  • "Property 'chainSpecific' does not exist on type 'Account<T>'"
  • "Property 'chainSpecific' does not exist on type 'BuildSendApiTxInput<T>'"
  • "Property 'chainSpecific' does not exist on type 'GetFeeDataInput<T>'"
Solution: Add your chain to FOUR type mapping objects in chain-adapters/src/types.ts
File:
packages/chain-adapters/src/types.ts
ALL FOUR mappings required:
  1. ~Line 45:
    ChainSpecificAccount
    [KnownChainIds.[Chain]Mainnet]: evm.Account
  2. ~Line 91:
    ChainSpecificFeeData
    [KnownChainIds.[Chain]Mainnet]: evm.FeeData
  3. ~Line 219:
    ChainSpecificBuildTxInput
    [KnownChainIds.[Chain]Mainnet]: evm.BuildTxInput
  4. ~Line 320:
    ChainSpecificGetFeeDataInput
    [KnownChainIds.[Chain]Mainnet]: evm.GetFeeDataInput
Example: HyperEVM added at lines 45, 91, 219, 320
Why: TypeScript uses these to determine chain-specific data structures
CRITICAL: Missing even ONE of these causes cryptic type errors! All 4 are required for ALL chains (EVM and non-EVM).
问题:地址在以下位置不显示:
  • 账户导入UI(表格中显示空白地址)
  • 发送流程的"从"地址行(显示空的发送方地址)
  • 整个应用中的账户下拉菜单
根本原因
accountIdToLabel()
函数中缺少chainId case 文件
src/state/slices/portfolioSlice/utils/index.ts
(约80-125行)
解决方案
  1. 添加chainId导入:
    import { [chainLower]ChainId } from '@shapeshiftoss/caip'
  2. 添加到switch语句:
    case [chainLower]ChainId:
  3. 将其与其他EVM链放在一起(在thorchainChainId之前)
示例
typescript
case baseChainId:
case hyperEvmChainId:  // ← 添加此行
case monadChainId:
case plasmaChainId:
原因:该函数将accountId转换为人类可读标签。若没有case,会命中
default
并返回
''
(空字符串),导致UI中到处显示空白地址。
注意:这影响所有钱包类型(原生、Ledger、Trezor、MetaMask),而非仅一个钱包。

Gotcha 17: Missing accountIdToLabel Case (BLOCKS ADDRESS DISPLAY!)

陷阱18:缺少getNativeFeeAssetReference Case(运行时崩溃!)

Problem: Addresses don't display in:
  • Account import UI (shows blank address in table)
  • Send flow "from" address row (shows empty from address)
  • Account dropdowns throughout the app
Root Cause: Missing chainId case in
accountIdToLabel()
function File:
src/state/slices/portfolioSlice/utils/index.ts
(around line 80-125)
Solution:
  1. Add chainId import:
    import { [chainLower]ChainId } from '@shapeshiftoss/caip'
  2. Add case to switch statement:
    case [chainLower]ChainId:
  3. Place it with other EVM chains (before thorchainChainId)
Example:
typescript
case baseChainId:
case hyperEvmChainId:  // ← ADD THIS
case monadChainId:
case plasmaChainId:
Why: This function converts accountId to human-readable label. Without the case, it hits the
default
and returns
''
(empty string), causing blank addresses everywhere in the UI.
Note: This affects ALL wallet types (Native, Ledger, Trezor, MetaMask), not just one wallet.
问题:应用崩溃,错误为:
Error: Chain namespace [chain] on mainnet not supported.
    at getNativeFeeAssetReference.ts:XX:XX
根本原因
getNativeFeeAssetReference()
函数中缺少chainNamespace case 文件
packages/utils/src/getNativeFeeAssetReference.ts
解决方案: 在switch语句中添加case:
typescript
case CHAIN_NAMESPACE.[ChainName]:
  switch (chainReference) {
    case CHAIN_REFERENCE.[ChainName]Mainnet:
      return ASSET_REFERENCE.[ChainName]
    default:
      throw new Error(`Chain namespace ${chainNamespace} on ${chainReference} not supported.`)
  }
原因:该函数将chainId映射到原生费用资产引用。若没有这一步,任何需要你的链费用资产的选择器都会抛出错误并导致应用崩溃。
注意:这在应用初始化早期被调用,因此当应用尝试加载你的链的账户时,会立即崩溃。

Gotcha 18: Missing getNativeFeeAssetReference Case (RUNTIME CRASH!)

陷阱19:缺少accountToPortfolio Case(账户不可见!)

Problem: App crashes with error:
Error: Chain namespace [chain] on mainnet not supported.
    at getNativeFeeAssetReference.ts:XX:XX
Root Cause: Missing chainNamespace case in
getNativeFeeAssetReference()
function File:
packages/utils/src/getNativeFeeAssetReference.ts
Solution: Add case to the switch statement:
typescript
case CHAIN_NAMESPACE.[ChainName]:
  switch (chainReference) {
    case CHAIN_REFERENCE.[ChainName]Mainnet:
      return ASSET_REFERENCE.[ChainName]
    default:
      throw new Error(`Chain namespace ${chainNamespace} on ${chainReference} not supported.`)
  }
Why: This function maps chainId to the native fee asset reference. Without it, any selector that needs the fee asset for your chain will throw and crash the app.
Note: This is called early in the app initialization, so it will crash immediately when the app tries to load accounts for your chain.
问题:账户发现成功(getAccount返回有效数据),但账户在UI中任何位置都不显示。
根本原因
accountToPortfolio()
函数中缺少或暂存实现 文件
src/state/slices/portfolioSlice/utils/index.ts
症状:你可以通过console.log验证getAccount返回正确数据,但账户就是不会出现在投资组合UI中。
解决方案: 找到
chainNamespace
的switch语句,实现你的链的case:
typescript
case CHAIN_NAMESPACE.[ChainName]: {
  const chainAccount = account as Account<KnownChainIds.[ChainName]Mainnet>
  const { chainId, assetId, pubkey } = account
  const accountId = toAccountId({ chainId, account: pubkey })

  portfolio.accounts.ids.push(accountId)
  portfolio.accounts.byId[accountId] = { assetIds: [assetId], hasActivity }
  portfolio.accountBalances.ids.push(accountId)
  portfolio.accountBalances.byId[accountId] = { [assetId]: account.balance }

  // 若适用,添加代币支持
  chainAccount.chainSpecific.tokens?.forEach(token => {
    if (!assetIds.includes(token.assetId)) return
    if (bnOrZero(token.balance).gt(0)) portfolio.accounts.byId[accountId].hasActivity = true
    portfolio.accounts.byId[accountId].assetIds.push(token.assetId)
    portfolio.accountBalances.byId[accountId][token.assetId] = token.balance
  })

  break
}
原因:该函数将链适配器账户数据转换为Redux投资组合状态结构。若没有这一步,账户数据会被获取但不会被存储,导致账户不可见。

Gotcha 19: Missing accountToPortfolio Case (ACCOUNT NOT VISIBLE!)

陷阱20:parseTx不解析代币转账(兑换执行价格错误!)

Problem: Account discovery succeeds (getAccount returns valid data) but account doesn't appear in the UI anywhere.
Root Cause: Missing or placeholder implementation in
accountToPortfolio()
function File:
src/state/slices/portfolioSlice/utils/index.ts
Symptom: You can verify getAccount is returning correct data with console.log, but the account just doesn't show up in the portfolio UI.
Solution: Find the switch statement for
chainNamespace
and implement your chain's case:
typescript
case CHAIN_NAMESPACE.[ChainName]: {
  const chainAccount = account as Account<KnownChainIds.[ChainName]Mainnet>
  const { chainId, assetId, pubkey } = account
  const accountId = toAccountId({ chainId, account: pubkey })

  portfolio.accounts.ids.push(accountId)
  portfolio.accounts.byId[accountId] = { assetIds: [assetId], hasActivity }
  portfolio.accountBalances.ids.push(accountId)
  portfolio.accountBalances.byId[accountId] = { [assetId]: account.balance }

  // Add token support if applicable
  chainAccount.chainSpecific.tokens?.forEach(token => {
    if (!assetIds.includes(token.assetId)) return
    if (bnOrZero(token.balance).gt(0)) portfolio.accounts.byId[accountId].hasActivity = true
    portfolio.accounts.byId[accountId].assetIds.push(token.assetId)
    portfolio.accountBalances.byId[accountId][token.assetId] = token.balance
  })

  break
}
Why: This function converts chain adapter account data into the Redux portfolio state structure. Without it, the account data is fetched but never stored, making the account invisible.
问题
  • 原生资产转账正常
  • 代币转账/兑换显示错误的执行价格或$0
  • 代币转账未出现在交易详情中
根本原因
parseTx()
仅解析原生转账,不解析代币事件
症状:用户报告兑换成功,但执行价格显示错误或为零。
解决方案:在parseTx中实现代币解析,这是一个迭代过程:
  1. 从简单实现开始 - 仅解析原生资产转账(初始实现)
  2. 用户测试 - 用户进行代币转账/兑换
  3. 获取RPC响应 - 用户提供调试器作用域中的实际RPC数据
  4. 优化解析 - 根据实际数据结构添加代币事件解析
参考实现
  • SecondClassEvmAdapter.parseTx()
    - ERC-20 Transfer事件
  • SuiChainAdapter.parseTx()
    - SUI原生和币种转账
  • TronChainAdapter.parseTx()
    - TRC-20转账
  • NearChainAdapter.parseNep141Transfers()
    - NEP-141 EVENT_JSON日志
常见问题
  1. pubkey不匹配:日志使用账户地址(如
    alice.near
    ),但函数接收十六进制pubkey
    • 解决方案:比较前将pubkey转换为账户地址
  2. 代币合约ID提取:代币合约可能在receiver_id、Delegate操作或嵌套结构中
    • 解决方案:检查实际RPC响应结构
  3. 缺少事件解析:未处理链特定的事件格式
    • 解决方案:根据链类型解析EVENT_JSON、日志或objectChanges
调试方法
typescript
async parseTx(txHash: string, pubkey: string): Promise<Transaction> {
  const result = await this.rpcCall('tx', [txHash, 'dontcare'])
  console.log('parseTx result:', JSON.stringify(result, null, 2))
  console.log('pubkey:', pubkey)
  // ... 其他实现
}
向用户请求:在parseTx中设置断点,分享
result
变量以查看实际代币事件结构。

Gotcha 20: parseTx Doesn't Parse Token Transfers (SWAP EXECUTION PRICE WRONG!)

陷阱21:代币转账被构建为原生转账(isToken缺少新命名空间)

Problem:
  • Native asset sends work fine
  • Token sends/swaps show wrong execution price or $0
  • Token transfers not appearing in transaction details
Root Cause:
parseTx()
only parses native transfers, not token events
Symptom: User reports swap worked but execution price shows as incorrect or zero.
Solution: Implement token parsing in parseTx. This is an iterative process:
  1. Start naive - Parse native transfers only (initial implementation)
  2. User tests - User makes a token send/swap
  3. Get RPC response - User provides debugger scope with actual RPC data
  4. Refine parsing - Add token event parsing based on real data structure
Reference implementations:
  • SecondClassEvmAdapter.parseTx()
    - ERC-20 Transfer events
  • SuiChainAdapter.parseTx()
    - SUI coin object changes
  • TronChainAdapter.parseTx()
    - TRC-20 logs
  • NearChainAdapter.parseNep141Transfers()
    - NEP-141 EVENT_JSON logs
Common issues:
  1. pubkey mismatch: Logs use account address (e.g.,
    alice.near
    ) but function receives hex pubkey
    • Solution: Convert pubkey to account address before comparison
  2. Token contract ID extraction: Token contract might be in receiver_id, Delegate action, or nested
    • Solution: Check actual RPC response structure
  3. Missing event parsing: Chain-specific event format not handled
    • Solution: Parse EVENT_JSON, logs, or objectChanges based on chain type
How to debug:
typescript
async parseTx(txHash: string, pubkey: string): Promise<Transaction> {
  const result = await this.rpcCall('tx', [txHash, 'dontcare'])
  console.log('parseTx result:', JSON.stringify(result, null, 2))
  console.log('pubkey:', pubkey)
  // ... rest of implementation
}
Ask user for: Breakpoint in parseTx, share
result
variable to see actual token event structure.

问题
  • 代币发送UI正常,交易广播成功
  • 但交易是向自己的原生转账,而非代币转账
  • 代币余额未变,仅消耗了Gas
根本原因
packages/utils/src/index.ts
中的
isToken()
未包含新链的代币命名空间。
原因
contractAddressOrUndefined(assetId)
使用
isToken()
确定资产是否为代币。若
isToken()
返回
false
contractAddressOrUndefined()
返回
undefined
,链适配器会构建原生转账而非代币转账。
EVM链:不受影响 - EVM抽象已处理
erc20
erc721
erc1155
非EVM链:必须将其代币命名空间添加到
isToken()
typescript
// packages/utils/src/index.ts
export const isToken = (assetId: AssetId) => {
  switch (fromAssetId(assetId).assetNamespace) {
    case ASSET_NAMESPACE.erc20:
    case ASSET_NAMESPACE.erc721:
    case ASSET_NAMESPACE.erc1155:
    case ASSET_NAMESPACE.splToken:  // Solana
    case ASSET_NAMESPACE.trc20:     // Tron
    case ASSET_NAMESPACE.suiCoin:   // Sui
    case ASSET_NAMESPACE.nep141:    // NEAR <-- 添加你的命名空间
      return true
    default:
      return false
  }
}
症状:用户发送代币,交易成功,但代币余额未变。区块浏览器显示向自己的原生转账。
调试:在发送流程中添加日志:
typescript
console.log('[Send] assetId:', asset.assetId)
console.log('[Send] contractAddress:', contractAddressOrUndefined(asset.assetId))
// 若代币的contractAddress为undefined,说明isToken()缺少命名空间

Gotcha 21: Token Sends Build as Native Transfers (isToken missing new namespace)

陷阱22:Ledger "TypeError: coin"错误(缺少translateCoinAndMethod case)

Problem:
  • Token send UI works, transaction broadcasts successfully
  • But transaction is a native transfer to self, not a token transfer
  • Token balance unchanged, only gas spent
Root Cause:
isToken()
in
packages/utils/src/index.ts
doesn't include the new chain's token namespace.
Why it matters:
contractAddressOrUndefined(assetId)
uses
isToken()
to determine if an asset is a token. If
isToken()
returns
false
,
contractAddressOrUndefined()
returns
undefined
, and the chain adapter builds a native transfer instead of a token transfer.
EVM chains: Not affected - EVM abstraction already handles
erc20
,
erc721
,
erc1155
.
Non-EVM chains: MUST add their token namespace to
isToken()
:
typescript
// packages/utils/src/index.ts
export const isToken = (assetId: AssetId) => {
  switch (fromAssetId(assetId).assetNamespace) {
    case ASSET_NAMESPACE.erc20:
    case ASSET_NAMESPACE.erc721:
    case ASSET_NAMESPACE.erc1155:
    case ASSET_NAMESPACE.splToken:  // Solana
    case ASSET_NAMESPACE.trc20:     // Tron
    case ASSET_NAMESPACE.suiCoin:   // Sui
    case ASSET_NAMESPACE.nep141:    // NEAR <-- ADD YOUR NAMESPACE HERE
      return true
    default:
      return false
  }
}
Symptom: User sends token, tx succeeds, but token balance unchanged. Tx on block explorer shows native transfer to self.
Debug: Add logs in Send flow:
typescript
console.log('[Send] assetId:', asset.assetId)
console.log('[Send] contractAddress:', contractAddressOrUndefined(asset.assetId))
// If contractAddress is undefined for a token, isToken() is missing the namespace

问题
  • Ledger连接对其他链正常
  • 但新链在派生地址时抛出
    TypeError: coin
  • 错误跟踪显示
    translateCoinAndMethod
    命中
    default: throw new TypeError("coin")
根本原因:对于非EVM链,Ledger WebHID/WebUSB传输中的
translateCoinAndMethod
需要新币种类型的case。
需更新文件
  1. packages/hdwallet-ledger-webhid/src/transport.ts
  2. packages/hdwallet-ledger-webusb/src/transport.ts
解决方案:添加新链Ledger应用的导入和case:
typescript
// 1. 在顶部添加导入
import Near from "@ledgerhq/hw-app-near";

// 2. 在translateCoinAndMethod switch中添加case
case "Near": {
  const near = new Near(transport as Transport);
  const methodInstance = near[method as LedgerTransportMethodName<"Near">].bind(near);
  return methodInstance as LedgerTransportMethod<T, U>;
}
前提条件
  • @ledgerhq/hw-app-[chainname]
    必须作为npm包存在
  • 链必须在
    packages/hdwallet-ledger/src/transport.ts
    LedgerTransportCoinType
    联合类型中
EVM链:不受影响 - 它们通过
case "Eth"
使用以太坊应用。
新非EVM Ledger链检查清单
  • 添加到
    hdwallet-ledger/src/transport.ts
    LedgerTransportCoinType
  • 在同一文件中添加到
    LedgerTransportMethodMap
    类型映射
  • hdwallet-ledger-webhid/src/transport.ts
    中添加导入 + case
  • hdwallet-ledger-webusb/src/transport.ts
    中添加导入 + case

Gotcha 22: Ledger "TypeError: coin" Error (Missing translateCoinAndMethod case)

快速参考:文件清单

HDWallet文件(必需)

Problem:
  • Ledger connection works for other chains
  • But new chain throws
    TypeError: coin
    when deriving address
  • Error trace shows
    translateCoinAndMethod
    hitting
    default: throw new TypeError("coin")
Root Cause: For non-EVM chains,
translateCoinAndMethod
in Ledger WebHID/WebUSB transport needs a case for the new coin type.
Files to update:
  1. packages/hdwallet-ledger-webhid/src/transport.ts
  2. packages/hdwallet-ledger-webusb/src/transport.ts
Solution: Add import and case for the new chain's Ledger app:
typescript
// 1. Add import at top
import Near from "@ledgerhq/hw-app-near";

// 2. Add case in translateCoinAndMethod switch
case "Near": {
  const near = new Near(transport as Transport);
  const methodInstance = near[method as LedgerTransportMethodName<"Near">].bind(near);
  return methodInstance as LedgerTransportMethod<T, U>;
}
Prerequisites:
  • @ledgerhq/hw-app-[chainname]
    must exist as npm package
  • Chain must be in
    LedgerTransportCoinType
    union in
    packages/hdwallet-ledger/src/transport.ts
EVM chains: Not affected - they use the Ethereum app via
case "Eth"
.
Checklist for new non-EVM Ledger chain:
  • Add to
    LedgerTransportCoinType
    in
    hdwallet-ledger/src/transport.ts
  • Add to
    LedgerTransportMethodMap
    type mapping in same file
  • Add import + case in
    hdwallet-ledger-webhid/src/transport.ts
  • Add import + case in
    hdwallet-ledger-webusb/src/transport.ts

  • packages/hdwallet-core/src/[chainname].ts
  • packages/hdwallet-core/src/index.ts
    (导出)
  • packages/hdwallet-core/src/utils.ts
    (SLIP44)
  • packages/hdwallet-core/src/wallet.ts
    (支持函数)
  • packages/hdwallet-native/src/[chainname].ts
  • packages/hdwallet-native/src/native.ts
    (集成混入)
  • packages/hdwallet-native/src/crypto/isolation/adapters/[chainname].ts
    (仅非EVM链)

Quick Reference: File Checklist

HDWallet文件(可选 - Ledger)

HDWallet Files (Required)

  • packages/hdwallet-core/src/[chainname].ts
  • packages/hdwallet-core/src/index.ts
    (export)
  • packages/hdwallet-core/src/utils.ts
    (SLIP44)
  • packages/hdwallet-core/src/wallet.ts
    (support function)
  • packages/hdwallet-native/src/[chainname].ts
  • packages/hdwallet-native/src/native.ts
    (integrate mixin)
  • packages/hdwallet-native/src/crypto/isolation/adapters/[chainname].ts
    (if non-EVM)
  • packages/hdwallet-ledger/src/ledger.ts

HDWallet Files (Optional - Ledger)

Web文件(核心)

  • packages/hdwallet-ledger/src/ledger.ts
  • packages/caip/src/constants.ts
  • packages/types/src/base.ts
  • src/constants/chains.ts
  • packages/chain-adapters/src/[type]/[chainname]/[ChainName]ChainAdapter.ts
  • packages/chain-adapters/src/[type]/[chainname]/types.ts
  • packages/chain-adapters/src/[type]/[chainname]/index.ts
  • src/lib/utils/[chainname].ts
  • src/lib/account/[chainname].ts
  • src/lib/account/account.ts
    (连接到调度器)

Web Files (Core)

Web文件(集成)

  • packages/caip/src/constants.ts
  • packages/types/src/base.ts
  • src/constants/chains.ts
  • packages/chain-adapters/src/[type]/[chainname]/[ChainName]ChainAdapter.ts
  • packages/chain-adapters/src/[type]/[chainname]/types.ts
  • packages/chain-adapters/src/[type]/[chainname]/index.ts
  • src/lib/utils/[chainname].ts
  • src/lib/account/[chainname].ts
  • src/lib/account/account.ts
    (wire into dispatcher)
  • src/plugins/[chainname]/index.tsx
  • src/plugins/activePlugins.ts
  • src/context/PluginProvider/PluginProvider.tsx
  • src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts
  • src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx
  • src/state/slices/portfolioSlice/utils/index.ts
    (isAssetSupportedByWallet函数)

Web Files (Integration)

Web文件(配置)

  • src/plugins/[chainname]/index.tsx
  • src/plugins/activePlugins.ts
  • src/context/PluginProvider/PluginProvider.tsx
  • src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts
  • src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx
  • src/state/slices/portfolioSlice/utils/index.ts
    (isAssetSupportedByWallet function)
  • `.env``
  • `.env.development``
  • `.env.production``
  • `src/config.ts``
  • `src/state/slices/preferencesSlice/preferencesSlice.ts``
  • `src/test/mocks/store.ts``
  • `headers/csps/chains/[chainname].ts``
  • `headers/csps/index.ts``

Web Files (Config)

Web文件(工具)

  • .env
  • .env.development
  • .env.production
  • src/config.ts
  • src/state/slices/preferencesSlice/preferencesSlice.ts
  • src/test/mocks/store.ts
  • headers/csps/chains/[chainname].ts
  • headers/csps/index.ts
  • `packages/utils/src/getAssetNamespaceFromChainId.ts``
  • `packages/utils/src/getChainShortName.ts``
  • `packages/utils/src/getNativeFeeAssetReference.ts``
  • `packages/utils/src/chainIdToFeeAssetId.ts``
  • `packages/utils/src/assetData/baseAssets.ts``
  • `packages/utils/src/assetData/getBaseAsset.ts``

Web Files (Utilities)

Web文件(资产与CoinGecko)

  • packages/utils/src/getAssetNamespaceFromChainId.ts
  • packages/utils/src/getChainShortName.ts
  • packages/utils/src/getNativeFeeAssetReference.ts
  • packages/utils/src/chainIdToFeeAssetId.ts
  • packages/utils/src/assetData/baseAssets.ts
  • packages/utils/src/assetData/getBaseAsset.ts
  • packages/caip/src/adapters/coingecko/index.ts
    (添加平台枚举)
  • packages/caip/src/adapters/coingecko/utils.ts
    (添加chainId映射和原生资产)
  • packages/caip/src/adapters/coingecko/utils.test.ts
    (添加测试)
  • packages/caip/src/adapters/coingecko/index.test.ts
    (添加资产夹具)
  • scripts/generateAssetData/coingecko.ts
    (添加链case以获取代币)
  • scripts/generateAssetData/[chainname]/index.ts
    (创建资产生成器)
  • scripts/generateAssetData/generateAssetData.ts
    (连接到生成器)
  • src/lib/asset-service/service/AssetService.ts
    (添加功能标志筛选)

Web Files (Assets & CoinGecko)

Web文件(Swapper集成)

  • packages/caip/src/adapters/coingecko/index.ts
    (add platform enum)
  • packages/caip/src/adapters/coingecko/utils.ts
    (add chainId mapping and native asset)
  • packages/caip/src/adapters/coingecko/utils.test.ts
    (add test)
  • packages/caip/src/adapters/coingecko/index.test.ts
    (add asset fixture)
  • scripts/generateAssetData/coingecko.ts
    (add chain case for token fetching)
  • scripts/generateAssetData/[chainname]/index.ts
    (create asset generator)
  • scripts/generateAssetData/generateAssetData.ts
    (wire in generator)
  • src/lib/asset-service/service/AssetService.ts
    (add feature flag filter)
  • packages/swapper/src/swappers/RelaySwapper/constant.ts
    (添加链映射)
  • packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
    (添加原生资产case)
  • 其他Swapper(若需要,如CowSwap、OneInch等)

Web Files (Swapper Integration)

Web文件(Ledger - 可选)

  • packages/swapper/src/swappers/RelaySwapper/constant.ts
    (add chain mapping)
  • packages/swapper/src/swappers/RelaySwapper/utils/relayTokenToAssetId.ts
    (add native asset case)
  • Other swappers as needed (CowSwap, OneInch, etc.)
  • `src/context/WalletProvider/Ledger/constants.ts``

Web Files (Ledger - Optional)

总结

  • src/context/WalletProvider/Ledger/constants.ts

此技能涵盖将新区块链作为二等公民添加的完整流程:
  1. ✅ HDWallet原生支持(工作区包位于
    packages/hdwallet-*
  2. ✅ Web简易链适配器
  3. ✅ 功能标志与配置
  4. ✅ 资产生成
  5. ✅ 交易状态轮询
  6. ✅ Ledger支持(可选)
  7. ✅ 测试与验证
  8. ✅ 清理提交流程
核心原则
  • HDWallet包在monorepo中 — 无单独仓库、无Verdaccio、无版本升级
  • 始终从HDWallet原生开始(
    packages/hdwallet-core
    +
    packages/hdwallet-native
  • 遵循现有模式(EVM链参考Monad,非EVM链参考Tron/Sui)
  • 简易方案:公共RPC、无微服务、最小功能
  • 所有内容都添加功能标志管控
  • 清晰、聚焦的提交
记住:二等公民 = 仅基础支持,无高级功能、无微服务,仅满足收发和兑换需求。

Summary

This skill covers the COMPLETE process for adding a new blockchain as a second-class citizen:
  1. ✅ HDWallet native support (workspace packages under
    packages/hdwallet-*
    )
  2. ✅ Web poor man's chain adapter
  3. ✅ Feature flags and configuration
  4. ✅ Asset generation
  5. ✅ Transaction status polling
  6. ✅ Ledger support (optional)
  7. ✅ Testing and validation
  8. ✅ Clean commit workflow
Key Principles:
  • HDWallet packages are in the monorepo — no separate repo, no Verdaccio, no version bumps
  • Always start with HDWallet native (
    packages/hdwallet-core
    +
    packages/hdwallet-native
    )
  • Follow existing patterns (Monad for EVM, Tron/Sui for non-EVM)
  • Poor man's approach: public RPC, no microservices, minimal features
  • Feature flag everything
  • Clean, focused commits
Remember: Second-class citizen = basic support only. No fancy features, no microservices, just enough to send/receive and swap.