# Chain Integration

> Integrate a new blockchain as a second-class citizen in ShapeShift Web. HDWallet packages live in the monorepo under packages/hdwallet-*. Covers everything from HDWallet native/Ledger support to Web chain adapter, asset generation, and feature flags. Activates when user wants to add basic support for a new blockchain.

- Skill: `shapeshift/chain-integration` (Agent Skill)
- Install (CLI): `npx skillmds@latest add shapeshift/chain-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shapeshift/chain-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: shapeshift (https://skillmd.com/u/shapeshift)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shapeshift/chain-integration

---


**Second-class EVM chain?** If this is a second-class EVM chain integration,
disregard the rest of this skill. Load and follow the contract at
`.claude/contracts/second-class-evm-chain.md` instead - it contains the
complete, authoritative checklist of every integration point required.
Use the contract as your build todo list, checking off items as you go.

# 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.

## 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"

## 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)

### 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)

## Phase 0: Deep Research & Information Gathering

**CRITICAL**: This phase determines the entire integration strategy. Take time to research thoroughly.

### Step 0.1: Initial Chain Discovery

**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**
   - Search: "[ChainName] public RPC endpoint"
   - Check official docs for RPC URLs
   - Look for ChainList entry: https://chainlist.org
   - Check: https://github.com/arddluma/awesome-list-rpc-nodes-providers

**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.

### Step 0.2: Interactive Information Gathering

**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
```

### Step 0.3: Structured Information Collection

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:**

4. **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:**

5. **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")

6. **Ledger Hardware Wallet Support**
   - Search: "Ledger [ChainName] support"
   - Check: https://www.ledger.com/supported-crypto-assets
   - Ask user: "Does Ledger support [ChainName]?"
   - If yes, note the Ledger app name

**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.

---

## Integration Path Decision

**Based on Phase 0 research, choose your path:**

### Path A: EVM Chain Integration (SIMPLE)
**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

### Path B: Non-EVM Chain Integration (COMPLEX)
**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

---

## Phase 1: HDWallet Native Support

**Working Directory**: Same monorepo — hdwallet packages are at `packages/hdwallet-*`

### Step 1.0: Choose Implementation Strategy

**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 Chains: Minimal HDWallet Work Required

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 `pnpm run 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:**
- Monad hdwallet: <https://github.com/shapeshift/hdwallet/pull/753>
- HyperEVM hdwallet: <https://github.com/shapeshift/hdwallet/pull/756>

**Time estimate**: 30 minutes for hdwallet changes (vs 1-2 days for non-EVM)

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

Examine existing implementations to understand patterns:

**For non-EVM chains** (like Tron, Sui):
```bash
# In the 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.

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

**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).

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

**For EVM chains only** (like Monad, HyperEVM). Follow these PRs as reference:
- **Monad hdwallet**: <https://github.com/shapeshift/hdwallet/pull/753>
- **HyperEVM hdwallet**: <https://github.com/shapeshift/hdwallet/pull/756>

**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)

---

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

**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]' }
```

### Step 1.3: Implement Native Wallet Support

**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
```

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

**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]'
```

### Step 1.5: Update Core Wallet Interface

**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]
}
```

### Step 1.6: Build & Verify

```bash
# Build hdwallet packages to verify
pnpm run hdwallet:build

# Run hdwallet tests
pnpm run hdwallet:test
```

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

---

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

### Step 3.1: Add Chain Constants

**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
```

### Step 3.2: Create Chain Adapter

**Directory**: `packages/chain-adapters/src/[adaptertype]/[chainname]/`

#### **For EVM Chains** (SIMPLE!)

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.

#### **For Non-EVM Chains** (COMPLEX)

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]'
```

### Step 3.2a: Implement parseTx (Iterative Approach)

**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, /* ... */ }
}
```

2. **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

3. **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**:

| Aspect | Native Asset | Tokens | Internal Transfers |
|--------|--------------|--------|-------------------|
| Where to find | Transaction value field | Event logs / receipts | Nested calls / traces |
| Asset ID | `this.assetId` | `chainId/namespace:contractAddress` | Varies |
| pubkey comparison | Usually sender field | Event old_owner/new_owner | May 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."

### Step 3.3: Add Utility Functions

**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
```

### Step 3.4: Create Chain Utils (Transaction Status)

**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
}
```

### Step 3.5: Wire Up Transaction Status Polling

**File**: `src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx`

Add case for your chain:
```typescript
case KnownChainIds.[ChainName]Mainnet:
  txStatus = await get[Chain]TransactionStatus(txHash, adapter)
  break
```

### Step 3.6: Add Account Derivation

**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)
```

### Step 3.7: Add Wallet Support Detection

**File**: `src/hooks/useWalletSupportsChain/useWalletSupportsChain.ts`

```typescript
// Add to switch statement
case KnownChainIds.[ChainName]Mainnet:
  return supports[Chain](wallet) // from hdwallet-core
```

### Step 3.8: Add Asset Support Detection (CRITICAL!)

**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)
```

---

## Phase 4: Web Plugin & Feature Flags

### 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
```

### Step 4.2: Environment Variables

**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
```

### Step 4.3: Config Validation

**File**: `src/config.ts`

```typescript
const validators = {
  // ...
  VITE_[CHAIN]_NODE_URL: url(),
  VITE_FEATURE_[CHAIN]: bool({ default: false }),
}
```

### Step 4.4: Feature Flag State

**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,
}
```

### Step 4.5: CSP Headers

**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],
]
```

---

## Phase 5: Asset Generation

### 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/index.ts` (3 touchpoints — enum + 2 switch statements)

CRITICAL: This file has 3 separate places to update. Missing any causes runtime failures.

Touchpoint 1 — Already shown above: `CoingeckoAssetPlatform` enum entry.

Touchpoint 2 — `chainIdToCoingeckoAssetPlatform()` forward mapping (CHAIN_REFERENCE → platform):
```typescript
// For EVM chains, add to the EVM switch (inside chainNamespace Evm case)
case CHAIN_REFERENCE.[ChainName]Mainnet:
  return CoingeckoAssetPlatform.[ChainName]
```

Touchpoint 3 — `coingeckoAssetPlatformToChainId()` reverse mapping (platform → chainId):
```typescript
case CoingeckoAssetPlatform.[ChainName]:
  return [chainLower]ChainId
```
NOTE: This reverse mapping requires importing `[chainLower]ChainId` from `../../constants`. Only import what is used — `chainIdToCoingeckoAssetPlatform` uses `CHAIN_REFERENCE` not chainId constants.

**File**: `packages/caip/src/adapters/coingecko/utils.ts` (2 touchpoints)

Touchpoint 4 — Add chainId to `buildByChainId` loop in `COINGECKO_ASSET_PLATFORM_TO_CHAIN_ID_MAP` (~line 280-310):
```typescript
// Import chainId + assetId from constants at top of file
import { [chainLower]AssetId, [chainLower]ChainId, ... } from '../../constants'

// Add to the switch/if chain inside the buildByChainId loop
prev[[chainLower]ChainId][assetId] = id
```

Touchpoint 5 — Add native asset to `COINGECKO_NATIVE_ASSET_PLATFORM_TO_CHAIN_ID_MAP` (~line 370-390):
```typescript
[[chainLower]ChainId]: { [[chainLower]AssetId]: '[coingecko-native-coin-id]' },
// e.g., for Cronos: [cronosChainId]: { [cronosAssetId]: 'crypto-com-chain' }
// e.g., for ETH-native chains: [scrollChainId]: { [scrollAssetId]: 'ethereum' }
```

**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
```

### Step 5.2: Create Asset Generator

**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]'
```

2. **Fetch assets** in the `generateAssetData()` function:
```typescript
const [chainLower]Assets = await [chainLower].getAssets()
```

3. **Add to unfilteredAssetData array**:
```typescript
  ...[chainLower]Assets,
```

**Add chain to CoinGecko script**:

**File**: `scripts/generateAssetData/coingecko.ts`

Import your chain:
```typescript
import {
 

…(truncated)
