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.
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
JavaScript
// 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';
0x0000000000000000000000000000000000001001
Contract Initialization
JavaScript
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
JavaScript
// 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
JavaScript
// 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
JavaScript
// 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
JavaScript
// 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
JavaScript
// 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
JavaScript
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
Error | Cause | Solution |
---|---|---|
insufficient funds | Not enough balance for transfer | Check balance before transfer |
invalid address | Malformed address format | Use proper EVM (0x…) or Sei (sei1…) format |
unknown denomination | Token denom doesn’t exist | Verify token denomination format |
amount overflow | Amount exceeds uint256 limits | Use appropriate amount ranges |
metadata not found | Token metadata not available | Handle 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