> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-evm-cookbook.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Solana to Sei EVM

> A comprehensive guide for Solana developers transitioning to Sei EVM, covering architectural differences, concept mapping, code translation patterns, and step-by-step migration strategies.

Sei combines parallelized execution, which you know from Solana, with full EVM compatibility and the extensive Ethereum tooling ecosystem. This guide helps Rust and Anchor developers translate their mental models and codebases to Solidity on Sei.

<Info>
  **Why Solana developers choose Sei**

  * Sei uses optimistic parallel execution, similar to Solana's Sealevel.
  * Block times of 400 ms are comparable to Solana's speed, and finality is instant.
  * Throughput is approximately 100 MGas/s, with full EVM compatibility.
  * You can use Ethereum's mature tooling, audited contracts, and developer resources.
  * You do not declare dependencies. Unlike Solana, Sei handles parallelization automatically.
</Info>

## Understanding the paradigm shift

Before you write code, understand the architectural differences between Solana and EVM-based chains such as Sei.

### Execution model comparison

| Aspect | Solana | Sei EVM |
| - | - | - |
| Language | Rust (with Anchor framework) | Solidity |
| Account model | Programs + Accounts (separated code and data) | Contracts (code and storage unified) |
| State storage | Flat account data with owner programs | Contract storage slots (key-value) |
| Parallelization | Explicit (declare accounts upfront) | Optimistic (automatic conflict detection) |
| Block time | \~400ms | 400ms |
| Finality | \~2.5-4.5 seconds (32 confirmations) | Instant (single block) |
| Fee model | Compute units + priority fees + rent | Gas × Gas Price (no rent) |
| Cross-contract calls | CPI (Cross-Program Invocation) | Internal/External function calls |
| Deterministic addresses | PDAs (Program Derived Addresses) | CREATE2 / ImmutableCreate2Factory |
| Token standard | SPL Token | ERC-20 / ERC-721 / ERC-1155 |
| Dev tooling | Anchor, Solana CLI, solana-web3.js | Hardhat, Foundry, ethers.js, viem |

## Core concept mapping

### Programs → smart contracts

On Solana, you write **programs** that are stateless executables. Data lives in separate **accounts** that programs can read and modify. On Sei EVM, **smart contracts** combine code and state in a single entity.

<Tabs>
  <Tab title="Solana (Anchor)">
    ```rust theme={null}
    // Solana: Program is stateless, data in accounts
    #[program]
    pub mod counter {
        use super::*;

        pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
            let counter = &mut ctx.accounts.counter;
            counter.count = 0;
            counter.authority = ctx.accounts.authority.key();
            Ok(())
        }

        pub fn increment(ctx: Context<Increment>) -> Result<()> {
            let counter = &mut ctx.accounts.counter;
            counter.count += 1;
            Ok(())
        }
    }

    #[account]
    pub struct Counter {
        pub count: u64,
        pub authority: Pubkey,
    }

    #[derive(Accounts)]
    pub struct Initialize<'info> {
        #[account(init, payer = authority, space = 8 + 8 + 32)]
        pub counter: Account<'info, Counter>,
        #[account(mut)]
        pub authority: Signer<'info>,
        pub system_program: Program<'info, System>,
    }
    ```
  </Tab>

  <Tab title="Sei EVM (Solidity)">
    ```solidity theme={null}
    // Sei EVM: Contract holds both code and state
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.22;

    contract Counter {
        uint256 public count;
        address public authority;

        constructor() {
            count = 0;
            authority = msg.sender;
        }

        function increment() external {
            count += 1;
        }
    }
    ```
  </Tab>
</Tabs>

**Key differences:**

* You do not allocate account space. Storage grows dynamically.
* You do not validate a `Signer` explicitly. `msg.sender` is always authenticated.
* You do not import a system program. Native operations are built into the EVM.

### PDAs → CREATE2 deterministic addresses

Solana's **Program Derived Addresses (PDAs)** let you create deterministic addresses from seeds. On the EVM, you get similar functionality with `CREATE2`.

<Tabs>
  <Tab title="Solana PDA">
    ```rust theme={null}
    // Solana: PDA derivation
    let (pda, bump) = Pubkey::find_program_address(
        &[
            b"vault",
            user.key().as_ref(),
        ],
        &program_id,
    );

    // In Anchor account validation
    #[account(
        seeds = [b"vault", user.key().as_ref()],
        bump,
    )]
    pub vault: Account<'info, Vault>,
    ```
  </Tab>

  <Tab title="Sei EVM CREATE2">
    ```solidity theme={null}
    // Sei EVM: CREATE2 for deterministic addresses
    // Using ImmutableCreate2Factory at 0x0000000000FFe8B47B3e2130213B802212439497

    function computeAddress(
        bytes32 salt,
        bytes32 bytecodeHash
    ) public view returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            bytecodeHash
        )))));
    }

    // Or use a mapping pattern for user-specific data
    mapping(address => Vault) public vaults;
    ```
  </Tab>
</Tabs>

### CPI → contract calls

Solana's **Cross-Program Invocation (CPI)** becomes a simple function call in Solidity:

<Tabs>
  <Tab title="Solana CPI">
    ```rust theme={null}
    // Solana: CPI to token program
    use anchor_spl::token::{self, Transfer};

    let cpi_accounts = Transfer {
        from: ctx.accounts.from_token_account.to_account_info(),
        to: ctx.accounts.to_token_account.to_account_info(),
        authority: ctx.accounts.authority.to_account_info(),
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

    token::transfer(cpi_ctx, amount)?;
    ```
  </Tab>

  <Tab title="Sei EVM Contract Call">
    ```solidity theme={null}
    // Sei EVM: Direct contract call
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

    function transferTokens(
        address token,
        address to,
        uint256 amount
    ) external {
        // Direct interface call - no account setup needed
        IERC20(token).transferFrom(msg.sender, to, amount);
    }
    ```
  </Tab>
</Tabs>

### SPL Token → ERC-20

<Tabs>
  <Tab title="SPL Token (Rust)">
    ```rust theme={null}
    // Solana SPL Token - requires token accounts
    #[derive(Accounts)]
    pub struct TransferTokens<'info> {
        #[account(mut)]
        pub from: Account<'info, TokenAccount>,
        #[account(mut)]
        pub to: Account<'info, TokenAccount>,
        pub authority: Signer<'info>,
        pub token_program: Program<'info, Token>,
    }
    ```
  </Tab>

  <Tab title="ERC-20 (Solidity)">
    ```solidity theme={null}
    // Sei EVM ERC-20 - balances stored in contract
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.22;

    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

    contract MyToken is ERC20 {
        constructor() ERC20("MyToken", "MTK") {
            _mint(msg.sender, 1000000 * 10**18);
        }
    }
    ```
  </Tab>
</Tabs>

**Key ERC-20 differences from SPL:**

* ERC-20 has no Associated Token Accounts (ATAs). Balances are stored directly in the contract.
* Approvals use the `approve()` and `transferFrom()` pattern.
* The basic implementation has no mint or freeze authorities. To add them, use `Ownable`.

## Fee model translation

To estimate costs accurately, learn how the fee models differ:

| Solana concept | Sei EVM equivalent | Notes |
| - | - | - |
| Compute units (CU) | Gas | Both measure computational work |
| Priority fee | Gas price | Higher price = faster inclusion |
| Rent | None | Sei has no rent. Storage is permanent. |
| Rent exemption | N/A | No minimum balance requirements |
| Base fee | Dynamic base fee | Sei does not burn the base fee |

```ts theme={null}
// Solana fee estimation
const computeUnits = 200_000;
const priorityFee = 1_000; // microlamports per CU
const rentExempt = await connection.getMinimumBalanceForRentExemption(accountSize);

// Sei EVM fee estimation
const gasLimit = 200_000n;
const gasPrice = await provider.getGasPrice(); // ~50–55 gwei on Sei (mainnet floor is 50 gwei)
const fee = gasLimit * gasPrice; // No rent to consider
```

<Info>
  **No rent on Sei**

  On Solana, accounts can be garbage collected if rent is not paid. On Sei EVM, storage is permanent. This simplifies your application logic: you do not need to track rent-exempt minimums or worry about account closure.
</Info>

## Parallelization: automatic vs explicit

On Sei, parallelization is **automatic**.

### Solana: explicit account declaration

On Solana, you must declare upfront every account that a transaction will touch:

```rust theme={null}
// Solana: Must declare all accounts for parallelization
#[derive(Accounts)]
pub struct Swap<'info> {
    #[account(mut)]
    pub user_token_a: Account<'info, TokenAccount>,
    #[account(mut)]
    pub user_token_b: Account<'info, TokenAccount>,
    #[account(mut)]
    pub pool_token_a: Account<'info, TokenAccount>,
    #[account(mut)]
    pub pool_token_b: Account<'info, TokenAccount>,
    #[account(mut)]
    pub pool_state: Account<'info, PoolState>,
    // ... more accounts
}
```

### Sei: optimistic parallelization

On Sei, you write normal Solidity, and the runtime handles parallelization:

```solidity theme={null}
// Sei EVM: Just write normal code
function swap(
    address tokenIn,
    address tokenOut,
    uint256 amountIn
) external returns (uint256 amountOut) {
    // Sei's parallelization engine automatically:
    // 1. Estimates which storage slots will be accessed
    // 2. Runs non-conflicting transactions in parallel
    // 3. Re-executes conflicts sequentially

    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
    amountOut = calculateOutput(amountIn);
    IERC20(tokenOut).transfer(msg.sender, amountOut);
}
```

<Warning>
  **Optimizing for parallelization**

  Avoid global counters that every transaction updates. Use user-partitioned storage instead. Sei handles parallelization automatically, but you can still optimize your contracts for better parallel performance. For detailed patterns, see [Optimizing for Parallelization](/evm/best-practices/optimizing-for-parallelization).
</Warning>

## Step 1: Set up your development environment

### Install required tools

```bash theme={null}
# Install Node.js (if not already installed)
# https://nodejs.org/

# Install Hardhat (recommended for Solana devs transitioning)
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox-mocha-ethers

# Or install Foundry (Rust-based, may feel more familiar)
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

### Configure for Sei

<Tabs>
  <Tab title="Hardhat">
    ```ts title="hardhat.config.ts" theme={null}
    import { defineConfig, configVariable } from 'hardhat/config';
    import hardhatToolboxMochaEthers from '@nomicfoundation/hardhat-toolbox-mocha-ethers';

    export default defineConfig({
      solidity: '0.8.22',
      networks: {
        seiMainnet: {
          type: 'http',
          chainId: 1329,
          url: 'https://evm-rpc.sei-apis.com',
          accounts: [configVariable('SEI_PRIVATE_KEY')]
        },
        seiTestnet: {
          type: 'http',
          chainId: 1328,
          url: 'https://evm-rpc-testnet.sei-apis.com',
          accounts: [configVariable('SEI_PRIVATE_KEY')]
        }
      },
      plugins: [hardhatToolboxMochaEthers]
    });
    ```

    Store your deployer key in Hardhat's encrypted keystore instead of a plaintext `.env` file:

    ```bash theme={null}
    npx hardhat keystore set SEI_PRIVATE_KEY
    ```
  </Tab>

  <Tab title="Foundry">
    ```toml title="foundry.toml" theme={null}
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.22"

    [rpc_endpoints]
    sei_mainnet = "https://evm-rpc.sei-apis.com"
    sei_testnet = "https://evm-rpc-testnet.sei-apis.com"

    # Verification uses Sourcify (no API key needed)
    # Run: forge verify-contract --verifier sourcify --chain-id <CHAIN_ID> <ADDRESS> <PATH:CONTRACT>
    ```
  </Tab>
</Tabs>

### Wallet setup

Configure MetaMask or any EVM wallet for Sei:

```ts theme={null}
const seiMainnet = {
  chainId: '0x531', // 1329 in hex
  chainName: 'Sei',
  nativeCurrency: { name: 'Sei', symbol: 'SEI', decimals: 18 },
  rpcUrls: ['https://evm-rpc.sei-apis.com'],
  blockExplorerUrls: ['https://seiscan.io']
};
```

## Step 2: Translate your Solana program

### Common pattern translations

#### Initializing state

<Tabs>
  <Tab title="Solana">
    ```rust theme={null}
    pub fn initialize(ctx: Context<Initialize>, initial_value: u64) -> Result<()> {
        let state = &mut ctx.accounts.state;
        state.value = initial_value;
        state.authority = ctx.accounts.authority.key();
        state.bump = ctx.bumps.state;
        Ok(())
    }

    #[account]
    pub struct State {
        pub value: u64,
        pub authority: Pubkey,
        pub bump: u8,
    }
    ```
  </Tab>

  <Tab title="Sei EVM">
    ```solidity theme={null}
    contract MyContract {
        uint256 public value;
        address public authority;

        constructor(uint256 initialValue) {
            value = initialValue;
            authority = msg.sender;
        }
    }
    ```
  </Tab>
</Tabs>

#### Access control

<Tabs>
  <Tab title="Solana">
    ```rust theme={null}
    // Solana: Check signer matches authority
    pub fn restricted_action(ctx: Context<RestrictedAction>) -> Result<()> {
        require!(
            ctx.accounts.authority.key() == ctx.accounts.state.authority,
            ErrorCode::Unauthorized
        );
        // ... action
        Ok(())
    }

    #[derive(Accounts)]
    pub struct RestrictedAction<'info> {
        #[account(mut)]
        pub state: Account<'info, State>,
        pub authority: Signer<'info>,
    }
    ```
  </Tab>

  <Tab title="Sei EVM">
    ```solidity theme={null}
    // Sei EVM: Use modifier pattern
    import "@openzeppelin/contracts/access/Ownable.sol";

    contract MyContract is Ownable {
        constructor() Ownable(msg.sender) {}

        function restrictedAction() external onlyOwner {
            // ... action
        }
    }

    // Or manual check
    contract MyContract {
        address public authority;

        modifier onlyAuthority() {
            require(msg.sender == authority, "Unauthorized");
            _;
        }

        function restrictedAction() external onlyAuthority {
            // ... action
        }
    }
    ```
  </Tab>
</Tabs>

#### Error handling

<Tabs>
  <Tab title="Solana">
    ```rust theme={null}
    // Solana: Custom error enum
    #[error_code]
    pub enum ErrorCode {
        #[msg("Insufficient balance")]
        InsufficientBalance,
        #[msg("Invalid amount")]
        InvalidAmount,
        #[msg("Unauthorized")]
        Unauthorized,
    }

    // Usage
    require!(amount > 0, ErrorCode::InvalidAmount);
    ```
  </Tab>

  <Tab title="Sei EVM">
    ```solidity theme={null}
    // Sei EVM: Custom errors (gas efficient)
    error InsufficientBalance(uint256 available, uint256 required);
    error InvalidAmount();
    error Unauthorized();

    // Usage
    if (amount == 0) revert InvalidAmount();
    if (balance < required) revert InsufficientBalance(balance, required);
    ```
  </Tab>
</Tabs>

#### Events/logs

<Tabs>
  <Tab title="Solana">
    ```rust theme={null}
    // Solana: Emit event macro
    use anchor_lang::prelude::*;

    #[event]
    pub struct TransferEvent {
        pub from: Pubkey,
        pub to: Pubkey,
        pub amount: u64,
    }

    // Emit
    emit!(TransferEvent {
        from: ctx.accounts.from.key(),
        to: ctx.accounts.to.key(),
        amount,
    });
    ```
  </Tab>

  <Tab title="Sei EVM">
    ```solidity theme={null}
    // Sei EVM: Event declaration and emission
    event Transfer(
        address indexed from,
        address indexed to,
        uint256 amount
    );

    // Emit
    emit Transfer(from, to, amount);
    ```
  </Tab>
</Tabs>

## Step 3: Frontend migration

### SDK comparison

| Solana | Sei EVM | Notes |
| - | - | - |
| `@solana/web3.js` | `ethers.js` / `viem` | Core blockchain interaction |
| `@coral-xyz/anchor` | `typechain` | Type-safe contract interaction |
| `@solana/wallet-adapter` | `wagmi` / `RainbowKit` | Wallet connection |
| Phantom, Solflare | MetaMask, Rabby, Backpack | Popular wallets |

### Code translation

<Tabs>
  <Tab title="Solana (web3.js)">
    ```ts theme={null}
    import { Connection, PublicKey } from '@solana/web3.js';
    import { Program, AnchorProvider } from '@coral-xyz/anchor';

    // Connect
    const connection = new Connection('https://api.mainnet-beta.solana.com');
    const provider = new AnchorProvider(connection, wallet, {});
    const program = new Program(idl, programId, provider);

    // Read state
    const state = await program.account.state.fetch(stateAddress);

    // Send transaction
    const tx = await program.methods
      .increment()
      .accounts({
        state: stateAddress,
        authority: wallet.publicKey
      })
      .rpc();
    ```
  </Tab>

  <Tab title="Sei EVM (ethers.js)">
    ```ts theme={null}
    import { ethers } from 'ethers';

    // Connect
    const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
    const signer = new ethers.Wallet(privateKey, provider);
    const contract = new ethers.Contract(contractAddress, abi, signer);

    // Read state
    const value = await contract.value();

    // Send transaction
    const tx = await contract.increment();
    await tx.wait();
    ```
  </Tab>
</Tabs>

## Step 4: Testing your migrated code

### Test framework comparison

<Tabs>
  <Tab title="Anchor Tests">
    ```ts theme={null}
    import * as anchor from '@coral-xyz/anchor';
    import { Program } from '@coral-xyz/anchor';
    import { Counter } from '../target/types/counter';

    describe('counter', () => {
      const provider = anchor.AnchorProvider.env();
      anchor.setProvider(provider);
      const program = anchor.workspace.Counter as Program<Counter>;

      it('Initializes', async () => {
        const counter = anchor.web3.Keypair.generate();
        await program.methods.initialize().accounts({ counter: counter.publicKey }).signers([counter]).rpc();

        const account = await program.account.counter.fetch(counter.publicKey);
        expect(account.count.toNumber()).to.equal(0);
      });
    });
    ```
  </Tab>

  <Tab title="Hardhat Tests">
    ```ts theme={null}
    import { expect } from 'chai';
    import { network } from 'hardhat';

    // Hardhat 3 exposes ethers through a network connection rather than a global import
    const { ethers } = await network.create();

    describe('Counter', function () {
      it('Initializes', async function () {
        const Counter = await ethers.getContractFactory('Counter');
        const counter = await Counter.deploy();

        expect(await counter.count()).to.equal(0);
      });

      it('Increments', async function () {
        const Counter = await ethers.getContractFactory('Counter');
        const counter = await Counter.deploy();

        await counter.increment();
        expect(await counter.count()).to.equal(1);
      });
    });
    ```
  </Tab>
</Tabs>

## Step 5: Deploy and verify

### Deploy to testnet

```bash theme={null}
# Hardhat
npx hardhat run scripts/deploy.ts --network seiTestnet

# Foundry
forge create --rpc-url https://evm-rpc-testnet.sei-apis.com \
  --private-key $PRIVATE_KEY \
  --broadcast \
  src/Counter.sol:Counter
```

### Verify contract

```bash theme={null}
# Hardhat
npx hardhat verify --network seiTestnet <CONTRACT_ADDRESS>

# Foundry
forge verify-contract \
  --chain-id 1328 \
  --verifier sourcify \
  <CONTRACT_ADDRESS> \
  src/Counter.sol:Counter
```

## Common migration pitfalls

### 1. Expecting rent

```solidity theme={null}
// ❌ Wrong: No need to check rent exemption
require(address(this).balance >= rentExempt, "Not rent exempt");

// ✅ Correct: Just use the contract normally
// Storage persists without rent payments
```

### 2. Manual account validation

```solidity theme={null}
// ❌ Wrong: Over-engineering account checks (Solana habit)
require(accountOwner == expectedOwner, "Invalid account owner");

// ✅ Correct: EVM handles this via contract addresses
// msg.sender is already authenticated
```

### 3. Expecting explicit parallelization

```solidity theme={null}
// ❌ Wrong: Trying to declare "accounts" for parallelization
function swap(address[] memory accounts) external { ... }

// ✅ Correct: Write normal code, Sei handles parallelization
function swap(address tokenIn, address tokenOut, uint256 amount) external { ... }
```

### 4. Using lamports mental model

```solidity theme={null}
// ❌ Wrong: Solana-style lamports
uint256 amount = 1_000_000_000; // 1 SOL in lamports

// ✅ Correct: Use wei (18 decimals for SEI)
uint256 amount = 1 ether; // 1 SEI = 1e18 wei
uint256 amount = 1e18;    // Same thing
```

## Ecosystem infrastructure

### Available on Sei

| Component | Solana equivalent | Sei address/info |
| - | - | - |
| Multicall3 | N/A | `0xcA11bde05977b3631167028862bE2a173976CA11` |
| Permit2 | N/A | `0xB952578f3520EE8Ea45b7914994dcf4702cEe578` |
| CREATE2 Factory | N/A | `0x0000000000FFe8B47B3e2130213B802212439497` |
| Oracle | Pyth Network, Switchboard | [Pyth](/evm/oracles/pyth-network), [RedStone](/evm/oracles/redstone), [Chainlink](/evm/oracles/chainlink) |
| Bridge | Wormhole | [LayerZero](/evm/bridging/layerzero), Wormhole, and many others |

## Helpful resources

### Learning Solidity

* [EVM with Foundry](/evm/evm-foundry): Foundry setup guide and starter tutorial
* [EVM with Hardhat](/evm/evm-hardhat): Hardhat setup guide and starter tutorial
* [Solidity Resources](/evm/solidity-resources): curated Solidity learning resources
* [CryptoZombies](https://cryptozombies.io): interactive Solidity tutorial
* [Solidity by Example](https://solidity-by-example.org): pattern reference

### Sei-specific

* [Divergence from Ethereum](/evm/differences-with-ethereum): technical differences
* [Optimizing for Parallelization](/evm/best-practices/optimizing-for-parallelization): performance patterns
* [Ecosystem Contracts](/evm/ecosystem-contracts): canonical addresses

<Info>
  **Need help?**

  For developer support, join the [Sei Tech Chat](https://t.me/+KZdhZ1eE-G01NmZk) on Telegram. The community is active and helps with migration questions.
</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.