Pointer Precompile
Address: 0x000000000000000000000000000000000000100B
The Sei Pointer precompile enables EVM applications to create EVM-compatible interfaces (pointers) for native Cosmos SDK tokens and CosmWasm contracts. This allows developers to migrate Cosmos-native assets to EVM, bridging the gap between the two ecosystems.
How Does the Pointer Precompile Work?
The Pointer precompile at address 0x000000000000000000000000000000000000100B
provides functions to create EVM pointers for different types of Cosmos assets:
- Native Asset Integration: Create erc20 compatible interfaces for Bank Module native tokens
- CosmWasm Compatibility: Bridge CW20, CW721 and CW1155 contracts to EVM standards
- Unified Interfaces: Create applications that seamlessly work with both EVM and Cosmos assets
- Migration Tools: Help projects transition from Cosmos to EVM ecosystem
- Multi-Standard Support: Support both fungible (ERC20) and non-fungible (ERC721, ERC1155) token standards
What You’ll Learn in This Guide
By the end of this guide, you’ll be able to:
- Create Pointers - Generate EVM interfaces for native Cosmos SDK Bank Tokens and CW20, CW721, CW1155 contracts
- Manage Pointer Lifecycles - Understand pointer creation, usage, and best practices
- Build Interoperable dApps - Develop applications that work across both ecosystems
Functions
The Pointer precompile exposes the following functions:
Transaction Functions
/// Adds a native pointer for the contract.
/// @param token The native token denomination to add (e.g., "usei", "uatom").
/// @return ret An Ethereum address of the created pointer contract.
function addNativePointer(
string memory token
) external returns (address ret);
/// Adds a CW20 pointer for the contract.
/// @param cwAddr The CW20 contract address to add.
/// @return ret An Ethereum address of the created pointer contract.
function addCW20Pointer(
string memory cwAddr
) external returns (address ret);
/// Adds a CW721 pointer for the contract.
/// @param cwAddr The CW721 contract address to add.
/// @return ret An Ethereum address of the created pointer contract.
function addCW721Pointer(
string memory cwAddr
) external returns (address ret);
/// Adds a CW1155 pointer for the contract.
/// @param cwAddr The CW1155 contract address to add.
/// @return ret An Ethereum address of the created pointer contract.
function addCW1155Pointer(
string memory cwAddr
) external returns (address ret);
Using the Precompile
Setup
Prerequisites
Before getting started, ensure you have:
- Node.js (v16 or higher)
- npm or yarn package manager
- EVM-compatible wallet with SEI tokens for gas fees
- Knowledge of target assets - native token denoms or CosmWasm contract addresses
- Hardhat for Solidity development
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 Project
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:
PRIVATE_KEY=your_private_key_here
RPC_URL=https://evm-rpc.sei-apis.com
Import Precompile Components
JavaScript
// Import ethers for contract interactions
import { ethers } from 'ethers';
import { POINTER_PRECOMPILE_ADDRESS, POINTER_PRECOMPILE_ABI } from '@sei-js/precompiles';
0x000000000000000000000000000000000000100B
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 Pointer precompile
const pointer = new ethers.Contract(POINTER_PRECOMPILE_ADDRESS, POINTER_PRECOMPILE_ABI, signer);
Understanding Pointer Parameters
Each pointer type requires specific parameters:
Pointer Type | Parameter | Example | Result |
---|---|---|---|
Native | Token denomination | tokenfactory/… | ERC20 compatible contract |
CW20 | Contract address | sei1abc… | ERC20 compatible contract |
CW721 | Contract address | sei1def… | ERC721 compatible contract |
CW1155 | Contract address | sei1ghi… | ERC1155 compatible contract |
Pointer Types Comparison
Native Pointers:
- Bridge Cosmos SDK Bank Module tokens to ERC20
- Automatic decimals handling
- Direct bank module integration
CW20 Pointers:
- Bridge CosmWasm CW20 tokens to ERC20
- Compatible with existing CW20 contracts
- Maintains token metadata and functionality
- Ideal for custom fungible tokens
CW721 Pointers:
- Bridge CosmWasm CW721 NFTs to ERC721
- Full NFT metadata support
CW1155 Pointers:
- Bridge CosmWasm CW1155 to ERC1155
- Full NFT metadata support
Step-by-Step Guide: Using the Pointer Precompile
Creating a Native Token Pointer
JavaScript
// Create a pointer for native SEI tokens
async function createNativePointer() {
const tokenDenom = 'tokenfactory/....'; // Native SEI denomination
try {
console.log('Creating native pointer for:', tokenDenom);
// Create the pointer
const tx = await pointer.addNativePointer(tokenDenom, {
gasLimit: 3000000n
});
const receipt = await tx.wait();
console.log('Transaction successful:', receipt.transactionHash);
// Extract the pointer address from events/logs
// The returned address will be in the transaction logs
console.log('Native pointer created successfully');
// The actual pointer address would be returned by the function
// You can call the function as a view to get the address without creating
return receipt;
} catch (error) {
console.error('Failed to create native pointer:', error.message);
if (error.message.includes('pointer already exists')) {
console.log('Pointer for this token already exists');
} else if (error.message.includes('invalid token denom')) {
console.error('Invalid token denomination');
}
throw error;
}
}
Creating a CW20 Token Pointer
JavaScript
// Create a pointer for a CW20 token
async function createCW20Pointer() {
// Example CW20 contract address (use actual contract address)
const cw20Address = 'sei1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3yqtxqz3t';
try {
console.log('Creating CW20 pointer for:', cw20Address);
// Create the pointer
const tx = await pointer.addCW20Pointer(cw20Address);
const receipt = await tx.wait();
console.log('CW20 pointer created:', receipt.transactionHash);
// The pointer address would be available in the transaction logs
// or returned by the function call
return receipt;
} catch (error) {
console.error('Failed to create CW20 pointer:', error.message);
if (error.message.includes('contract does not exist')) {
console.error('CW20 contract not found at the provided address');
} else if (error.message.includes('not a CW20 contract')) {
console.error('Contract exists but is not a valid CW20 contract');
}
throw error;
}
}
Creating a CW721 NFT Pointer
JavaScript
// Create a pointer for a CW721 NFT contract
async function createCW721Pointer() {
// Example CW721 contract address (use actual contract address)
const cw721Address = 'sei1nft789abcdef123456789abcdef123456789abcdef123456789abcdef12';
try {
console.log('Creating CW721 pointer for:', cw721Address);
// Create the pointer with higher gas limit for NFT operations
const tx = await pointer.addCW721Pointer(cw721Address, {
gasLimit: 3000000n
});
const receipt = await tx.wait();
console.log('CW721 pointer created:', receipt.transactionHash);
// The erc721 compatible contract is now available at the returned address
console.log('NFT collection is now EVM-compatible');
return receipt;
} catch (error) {
console.error('Failed to create CW721 pointer:', error.message);
if (error.message.includes('contract does not exist')) {
console.error('CW721 contract not found at the provided address');
} else if (error.message.includes('not a CW721 contract')) {
console.error('Contract exists but is not a valid CW721 contract');
}
throw error;
}
}
Security Considerations & Best Practices
Pointer Creation Validation
- Asset Verification: Always verify that the target asset (native token or CosmWasm contract) exists before creating a pointer
- Duplicate Prevention: Check if a pointer already exists to avoid unnecessary gas costs
- Address Format: Validate CosmWasm contract address formats before pointer creation
Working with Created Pointers
Using Native Token Pointers
Once you create a native token pointer, you can interact with it like any erc20 token:
// After creating a native pointer for 'tokenfactory/....'
const ERC20_ABI = ['function balanceOf(address) view returns (uint256)', 'function transfer(address, uint256) returns (bool)', 'function approve(address, uint256) returns (bool)'];
const seiPointer = new ethers.Contract(pointerAddress, ERC20_ABI, signer);
// Check balance
const balance = await seiPointer.balanceOf(userAddress);
console.log('SEI balance:', ethers.formatUnits(balance, 6)); // SEI has 6 decimals
// Transfer tokens
const transferTx = await seiPointer.transfer(recipientAddress, amount);
Using CW20 Pointers
CW20 pointers function as standard erc20 tokens:
// Interact with CW20 pointer as erc20
const cw20Pointer = new ethers.Contract(pointerAddress, ERC20_ABI, signer);
// All standard erc20 functions are available
const totalSupply = await cw20Pointer.totalSupply();
const decimals = await cw20Pointer.decimals();
const symbol = await cw20Pointer.symbol();
Using CW721 Pointers
CW721 pointers work as standard erc721 tokens:
const ERC721_ABI = ['function balanceOf(address) view returns (uint256)', 'function ownerOf(uint256) view returns (address)', 'function transferFrom(address, address, uint256)', 'function approve(address, uint256)'];
const nftPointer = new ethers.Contract(pointerAddress, ERC721_ABI, signer);
// Check NFT ownership
const owner = await nftPointer.ownerOf(tokenId);
const balance = await nftPointer.balanceOf(userAddress);
Troubleshooting
Common Issues and Solutions
Pointer Creation Failures
// Handle common pointer creation errors
async function createPointerWithErrorHandling(type, asset) {
try {
let tx;
switch (type) {
case 'native':
tx = await pointer.addNativePointer(asset);
break;
case 'cw20':
tx = await pointer.addCW20Pointer(asset);
break;
case 'cw721':
tx = await pointer.addCW721Pointer(asset);
break;
}
return await tx.wait();
} catch (error) {
if (error.message.includes('pointer already exists')) {
console.log('Pointer already exists, retrieving existing address...');
// Query existing pointer address
} else if (error.message.includes('invalid token denom')) {
console.error('Invalid token denomination:', asset);
} else if (error.message.includes('contract does not exist')) {
console.error('Contract not found:', asset);
}
throw error;
}
}
Gas Estimation Issues
// Proper gas estimation for different pointer types
async function estimatePointerGas(type, asset) {
try {
let gasEstimate;
switch (type) {
case 'native':
gasEstimate = await pointer.addNativePointer.estimateGas(asset);
break;
case 'cw20':
gasEstimate = await pointer.addCW20Pointer.estimateGas(asset);
break;
case 'cw721':
gasEstimate = await pointer.addCW721Pointer.estimateGas(asset);
gasEstimate = (gasEstimate * 15n) / 10n; // Add extra buffer for NFTs
break;
}
return gasEstimate;
} catch (error) {
console.error('Gas estimation failed:', error);
// Return default gas limits based on type
switch (type) {
case 'native':
return 200000n;
case 'cw20':
return 250000n;
case 'cw721':
return 400000n;
default:
return 300000n;
}
}
}
Error Code Reference
Error | Cause | Solution |
---|---|---|
pointer already exists | Pointer for this asset was already created | Query existing pointer address instead |
invalid token denom | Invalid or non-existent native token denomination | Verify token denomination exists on chain |
contract does not exist | CosmWasm contract not found at address | Verify contract address and ensure it’s deployed |
not a CW20 contract | Contract exists but doesn’t implement CW20 standard | Use correct contract address for CW20 token |
not a CW721 contract | Contract exists but doesn’t implement CW721 standard | Use correct contract address for CW721 NFT |
not a CW1155 contract | Contract exists but doesn’t implement CW1155 standard | Use correct contract address for the CW1155 contract |
insufficient gas | Gas limit too low for pointer creation | Increase gas limit, especially for CW721 |
Important Notes
Pointer Lifecycle
- Creation: Pointer is created via precompile function
- Registration: Pointer address is returned and can be stored
- Usage: EVM applications can interact with the pointer using standard interfaces
- Persistence: Pointers remain active
Best Practices
- Validation First: Always verify asset existence before creating pointers
- Store Addresses: Keep track of created pointer addresses for reuse
- Gas Management: Use appropriate gas limits
Asset Requirements
Native Tokens:
- Must be valid denominations registered on the Cosmos SDK bank module.
- Can be any bank module asset excluding
usei
CW20 Contracts:
- Must be deployed CosmWasm contracts implementing CW20 standard
- Address format:
sei1...
(bech32 encoded)
CW721 Contracts:
- Must be deployed CosmWasm contracts implementing CW721 standard
- Address format:
sei1...
(bech32 encoded)
CW1155 Contracts:
- Must be deployed CosmWasm contracts implementing CW1155 standard
- Address format:
sei1...
(bech32 encoded)