Skip to Content

Bank Precompile

Address: 0x0000000000000000000000000000000000001001

The Sei bank precompile allows EVM applications to interact directly with Sei’s native banking system through standard smart contract calls. This enables querying balances, transferring tokens, and accessing token metadata for both native SEI tokens and Cosmos SDK-based assets, providing seamless integration between EVM and Cosmos ecosystems.

What is a precompile? A precompile is a special smart contract deployed at a fixed address by the Sei protocol itself, that exposes custom native chain logic to EVM-based applications. It acts like a regular contract from the EVM’s perspective, but executes privileged, low-level logic efficiently.

How Does the Bank Precompile Work?

The bank precompile at address 0x0000000000000000000000000000000000001001 exposes functions like send(), sendNative(), balance(), all_balances(), and token metadata queries.

  • Direct Integration: EVM contracts and dApps can call banking functions like any other smart contract method.
  • Native Execution: Operations are executed at the Cosmos SDK level for maximum efficiency and security.
  • Cross-Chain Assets: Manage both native SEI tokens and IBC assets seamlessly from EVM contracts.

Use Cases

  • DeFi Applications: Build decentralized finance protocols that can handle native SEI and Cosmos assets.
  • Portfolio Management: Build tools to track and manage multi-asset portfolios across Cosmos and EVM.
  • Token Information Services: Query comprehensive token metadata for UI display and analytics.

What You’ll Learn in This Guide

By the end of this guide, you’ll be able to:

  • Execute Token Transfers - Send both native SEI and custom tokens between addresses
  • Query Account Balances - Check single and multi-asset balances for any address
  • Access Token Metadata - Retrieve names, symbols, decimals, and supply information `hh1h

Functions

The bank precompile exposes the following functions:

Transaction Functions

/// Sends non-native tokens from one address to another. /// @param fromAddress The Sei address to send funds from. /// @param toAddress The Sei address to send funds to. /// @param denom The denomination of funds to send. /// @param amount The amount of the above denom to send. /// @return success Whether the send was successfully executed. function send( address fromAddress, address toAddress, string memory denom, uint256 amount ) external returns (bool success); /// Sends Sei to the specified user. /// @param toNativeAddress The Sei address of the recipient. /// @return success Whether the tokens were successfully sent. function sendNative( string memory toNativeAddress ) payable external returns (bool success);

Query Functions

/// Queries the balance of the given account for the specified denom. /// @param acc The Sei address of the account to query. /// @param denom The denomination to query for. /// @return amount The amount of denom held by acc. function balance( address acc, string memory denom ) external view returns (uint256 amount); /// Queries the balance of the given account for all balances. /// @param acc The Sei address of the account to query. /// @return response Balances for all coins/denoms. function all_balances( address acc ) external view returns (Coin[] memory response); /// Queries the name of the specified denom. /// @param denom The denomination to query about. /// @return response The name of the specified denom. function name( string memory denom ) external view returns (string memory response); /// Queries the symbol of the specified denom. /// @param denom The denomination to query about. /// @return response The symbol of the specified denom. function symbol( string memory denom ) external view returns (string memory response); /// Queries the number of decimal places for the specified denom. /// @param denom The denomination to query about. /// @return response The number of decimals for the specified denom. function decimals( string memory denom ) external view returns (uint8 response); /// Queries the total supply of the specified denom. /// @param denom The denomination to query about. /// @return response The total supply of the specified denom. function supply( string memory denom ) external view returns (uint256 response);

Using the Precompile

Setup

Prerequisites

Before getting started, ensure you have:

  • Node.js (v16 or higher)
  • npm or yarn package manager
  • EVM-compatible wallet
  • SEI tokens for gas fees and testing transfers
  • Hardhat for development and testing

Install Dependencies

Install the required packages for interacting with Sei precompiles:

# Instantiate a new hardhat project npx hardhat init # Install ethers.js for smart contract interactions npm install ethers dotenv # Install Sei EVM bindings for precompile addresses and ABIs npm install @sei-js/precompiles@2.1.2

Setup Hardhat Environment

Create a hardhat.config.js file with the following content:

require('@nomicfoundation/hardhat-toolbox'); require('dotenv').config(); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: '0.8.28', networks: { sei: { url: 'https://evm-rpc.sei-apis.com', chainId: 1329, accounts: [process.env.PRIVATE_KEY] } } };

Create a .env file in the root directory with your private key and RPC URL:

RPC_URL=https://evm-rpc.sei-apis.com PRIVATE_KEY=your_private_key_here

Import Precompile Components

// Import Bank precompile address and ABI // View the entire ABI here: https://github.com/sei-protocol/sei-chain/tree/main/precompiles/bank import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; import { ethers } from 'ethers';
Precompile Address: The bank precompile is deployed at 0x0000000000000000000000000000000000001001

Contract Initialization

Set up your provider, signer, and contract instance:

// Using EVM-compatible wallet as the signer and provider const provider = new ethers.BrowserProvider(window.ethereum); await provider.send('eth_requestAccounts', []); const signer = await provider.getSigner(); // Create a contract instance for the bank precompile const bank = new ethers.Contract(BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, signer);

Native SEI vs Custom Tokens

Native SEI Transfers:

  • Use sendNative() with payable value
  • Denomination is always usei (micro-SEI)
  • Parse to 18 digits when calling sendNative()

Custom Token Transfers:

  • Use send() with specific denomination
  • Requires prior token approval or ownership
  • Support various decimal configurations

Step-by-Step Guide: Using the Bank Precompile

Send Native SEI Tokens

// Send native SEI tokens to another address const recipientAddress = 'sei1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6s8dpw'; const amountInSei = '0.1'; // 0.1 SEI try { // Convert SEI to usei (1 SEI = 1,000,000 usei) const amountInUsei = ethers.parseUnits(amountInSei, 6); // Send native SEI const tx = await bank.sendNative(recipientAddress, { value: amountInUsei, gasLimit: 300000n }); const receipt = await tx.wait(); console.log('Native SEI transfer successful:', receipt.transactionHash); console.log(`Sent ${amountInSei} SEI to ${recipientAddress}`); } catch (error) { console.error('Native SEI transfer failed:', error); }

Send Custom Tokens

// Send custom tokens between addresses const fromAddress = '0x1234567890123456789012345678901234567890'; const toAddress = '0x9876543210987654321098765432109876543210'; const tokenDenom = 'usei'; const amount = '1'; try { // Convert addresses to proper format if needed const tx = await bank.send( fromAddress, toAddress, tokenDenom, ethers.parseUnits(amount, 18), // Adjust decimals based on token { gasLimit: 300000n } ); const receipt = await tx.wait(); console.log('Custom token transfer successful:', receipt.transactionHash); console.log(`Sent ${amount} ${tokenDenom} from ${fromAddress} to ${toAddress}`); } catch (error) { console.error('Custom token transfer failed:', error); }

Query Account Balance

// Query specific token balance const accountAddress = '0x1234567890123456789012345678901234567890'; const tokenDenom = 'usei'; try { const balance = await bank.balance(accountAddress, tokenDenom); // Convert usei to SEI for display if (tokenDenom === 'usei') { const seiBalance = ethers.formatUnits(balance, 6); console.log(`SEI Balance: ${seiBalance} SEI`); } else { console.log(`${tokenDenom} Balance: ${balance.toString()}`); } } catch (error) { console.error('Balance query failed:', error); }

Query All Balances

// Query all token balances for an account const accountAddress = '0x1234567890123456789012345678901234567890'; try { const allBalances = await bank.all_balances(accountAddress); console.log(`Account: ${accountAddress}`); console.log('All Balances:'); allBalances.forEach((coin, index) => { if (coin.denom === 'usei') { const seiAmount = ethers.formatUnits(coin.amount, 6); console.log(` ${index + 1}. ${seiAmount} SEI (${coin.denom})`); } else { console.log(` ${index + 1}. ${coin.amount.toString()} ${coin.denom}`); } }); } catch (error) { console.error('All balances query failed:', error); }

Query Token Metadata

// Query comprehensive token metadata const tokenDenom = 'usei'; try { // Get all metadata in parallel const [name, symbol, decimals, totalSupply] = await Promise.all([bank.name(tokenDenom), bank.symbol(tokenDenom), bank.decimals(tokenDenom), bank.supply(tokenDenom)]); console.log('Token Metadata:'); console.log(` Denomination: ${tokenDenom}`); console.log(` Name: ${name}`); console.log(` Symbol: ${symbol}`); console.log(` Decimals: ${decimals}`); console.log(` Total Supply: ${ethers.formatUnits(totalSupply, decimals)}`); } catch (error) { console.error('Metadata query failed:', error); } // Function to get formatted token info async function getTokenInfo(denom) { try { const metadata = { denom: denom, name: await bank.name(denom), symbol: await bank.symbol(denom), decimals: await bank.decimals(denom), supply: await bank.supply(denom) }; return { ...metadata, formattedSupply: ethers.formatUnits(metadata.supply, metadata.decimals) }; } catch (error) { console.error(`Failed to get info for ${denom}:`, error); return null; } }

Complete Integration Example

import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles'; import { ethers } from 'ethers'; class SeiTokenManager { private bank: ethers.Contract; private signer: ethers.Signer; constructor(signer: ethers.Signer) { this.signer = signer; this.bank = new ethers.Contract(BANK_PRECOMPILE_ADDRESS, BANK_PRECOMPILE_ABI, signer); } // Send native SEI tokens async sendSei(recipientAddress: string, amountInSei: string) { try { const amountInUsei = ethers.parseUnits(amountInSei, 18); const tx = await this.bank.sendNative(recipientAddress, { value: amountInUsei, gasLimit: 100000 }); const receipt = await tx.wait(); return { success: true, transactionHash: receipt.transactionHash, amount: amountInSei, recipient: recipientAddress }; } catch (error) { return { success: false, error: error.message }; } } // Get user's portfolio async getPortfolio(address: string) { try { const balances = await this.bank.all_balances(address); const portfolio = []; for (const coin of balances) { try { const metadata = await this.getTokenMetadata(coin.denom); portfolio.push({ denom: coin.denom, amount: coin.amount.toString(), formattedAmount: ethers.formatUnits(coin.amount, metadata.decimals), ...metadata }); } catch (metadataError) { // If metadata fails, still include the balance portfolio.push({ denom: coin.denom, amount: coin.amount.toString(), formattedAmount: coin.amount.toString(), name: 'Unknown', symbol: 'Unknown', decimals: 0 }); } } return { success: true, portfolio }; } catch (error) { return { success: false, error: error.message }; } } // Get token metadata async getTokenMetadata(denom: string) { const [name, symbol, decimals, supply] = await Promise.all([this.bank.name(denom).catch(() => 'Unknown'), this.bank.symbol(denom).catch(() => 'Unknown'), this.bank.decimals(denom).catch(() => 0), this.bank.supply(denom).catch(() => 0n)]); return { name, symbol, decimals, supply: supply.toString() }; } // Transfer custom tokens async transferToken(toAddress: string, denom: string, amount: string, decimals: number = 18) { try { const fromAddress = await this.signer.getAddress(); const parsedAmount = ethers.parseUnits(amount, decimals); const tx = await this.bank.send(fromAddress, toAddress, denom, parsedAmount, { gasLimit: 150000 }); const receipt = await tx.wait(); return { success: true, transactionHash: receipt.transactionHash, from: fromAddress, to: toAddress, denom, amount }; } catch (error) { return { success: false, error: error.message }; } } // Batch operations async batchBalanceCheck(addresses: string[], denom: string) { try { const balances = await Promise.allSettled(addresses.map((addr) => this.bank.balance(addr, denom))); return balances.map((result, index) => ({ address: addresses[index], balance: result.status === 'fulfilled' ? result.value.toString() : '0', success: result.status === 'fulfilled' })); } catch (error) { throw new Error(`Batch balance check failed: ${error.message}`); } } } // Usage example async function bankExample() { const provider = new ethers.BrowserProvider(window.ethereum); await provider.send('eth_requestAccounts', []); const signer = await provider.getSigner(); const tokenManager = new SeiTokenManager(signer); console.log('=== Sei Token Manager Demo ==='); // 1. Get current user's portfolio const userAddress = await signer.getAddress(); const portfolio = await tokenManager.getPortfolio(userAddress); if (portfolio.success) { console.log('User Portfolio:'); portfolio.portfolio.forEach((token, index) => { console.log(` ${index + 1}. ${token.formattedAmount} ${token.symbol} (${token.name})`); }); } // 2. Send native SEI const sendResult = await tokenManager.sendSei('sei1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6s8dpw', '0.01'); if (sendResult.success) { console.log('SEI transfer successful:', sendResult.transactionHash); } // 3. Check balances for multiple addresses const addresses = ['0x1234567890123456789012345678901234567890', '0x9876543210987654321098765432109876543210']; const batchBalances = await tokenManager.batchBalanceCheck(addresses, 'usei'); console.log('Batch balance results:', batchBalances); }

Security Considerations & Risks

Transaction Security

  • Amount Validation: Always validate transfer amounts and ensure sufficient balances
  • Address Verification: Verify recipient addresses are valid before sending tokens
  • Reentrancy Protection: Be aware of potential reentrancy when combining with other contracts

Permission Management

// Example of secure permission patterns modifier onlyTokenHolder(string memory denom, uint256 minAmount) { require(BANK.balance(msg.sender, denom) >= minAmount, "Insufficient token balance"); _; } modifier validRecipient(address recipient) { require(recipient != address(0), "Invalid recipient"); require(recipient != address(this), "Cannot send to contract"); _; }

Troubleshooting

Common Issues and Solutions

Transaction Failures

// Handle common transfer errors try { const tx = await bank.sendNative(recipient, { value: amount }); await tx.wait(); } catch (error) { if (error.message.includes('insufficient funds')) { console.error('Insufficient balance for transfer'); } else if (error.message.includes('invalid address')) { console.error('Invalid recipient address format'); } else { console.error('Transfer failed:', error.message); } }

Balance Query Issues

// Safe balance checking with error handling async function safeGetBalance(address: string, denom: string) { try { const balance = await bank.balance(address, denom); return { success: true, balance: balance.toString() }; } catch (error) { if (error.message.includes('not found')) { return { success: true, balance: '0' }; // No balance = 0 } return { success: false, error: error.message }; } }

Error Code Reference

ErrorCauseSolution
insufficient fundsNot enough balance for transferCheck balance before transfer
invalid addressMalformed address formatUse proper EVM (0x…) or Sei (sei1…) format
unknown denominationToken denom doesn’t existVerify token denomination format
amount overflowAmount exceeds uint256 limitsUse appropriate amount ranges
metadata not foundToken metadata not availableHandle missing metadata gracefully

Important Notes

Remember: Always verify token denominations and handle errors gracefully in production applications!

  • SEI Native: While reading from chain it is formatted to 6 decimal places (1 SEI = 1,000,000 usei) and while writing to chain it is parsed to 18 decimal places.
  • Custom Tokens: Check decimals using decimals() function
  • Display Formatting: Always format amounts with proper decimal places for user display

Gas Optimization

  • Batch Operations: Use batch functions for multiple operations to save gas
  • Query Efficiency: Cache frequently accessed token metadata
  • Error Handling: Implement proper error handling to avoid failed transaction costs

Integration Best Practices

  • Balance Checks: Always verify sufficient balance before transfers
  • Error Recovery: Implement retry logic for failed transactions
  • User Experience: Provide clear feedback on transaction status
  • Decimal Handling: Use proper decimal formatting for different token types
View the Bank precompile source code and the contract ABI here.
Last updated on