Skip to Content

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.

What is a pointer? The Pointer precompile enables creation of EVM-compatible pointer contracts that bridge CosmWasm tokens (CW20, CW721, CW1155) and native bank module tokens to the EVM ecosystem. This allows seamless interoperability and unified token management across both environments.

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

// Import ethers for contract interactions import { ethers } from 'ethers'; import { POINTER_PRECOMPILE_ADDRESS, POINTER_PRECOMPILE_ABI } from '@sei-js/precompiles';
Precompile Address: The Pointer precompile is deployed at 0x000000000000000000000000000000000000100B

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 Pointer precompile const pointer = new ethers.Contract(POINTER_PRECOMPILE_ADDRESS, POINTER_PRECOMPILE_ABI, signer);

Understanding Pointer Parameters

Each pointer type requires specific parameters:

Pointer TypeParameterExampleResult
NativeToken denominationtokenfactory/…ERC20 compatible contract
CW20Contract addresssei1abc…ERC20 compatible contract
CW721Contract addresssei1def…ERC721 compatible contract
CW1155Contract addresssei1ghi…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

// 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

// 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

// 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

ErrorCauseSolution
pointer already existsPointer for this asset was already createdQuery existing pointer address instead
invalid token denomInvalid or non-existent native token denominationVerify token denomination exists on chain
contract does not existCosmWasm contract not found at addressVerify contract address and ensure it’s deployed
not a CW20 contractContract exists but doesn’t implement CW20 standardUse correct contract address for CW20 token
not a CW721 contractContract exists but doesn’t implement CW721 standardUse correct contract address for CW721 NFT
not a CW1155 contractContract exists but doesn’t implement CW1155 standardUse correct contract address for the CW1155 contract
insufficient gasGas limit too low for pointer creationIncrease gas limit, especially for CW721

Important Notes

Remember: Pointer creation is a one-time operation per asset. Once created, the pointer address remains constant and can be used by any application.

Pointer Lifecycle

  1. Creation: Pointer is created via precompile function
  2. Registration: Pointer address is returned and can be stored
  3. Usage: EVM applications can interact with the pointer using standard interfaces
  4. 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)
View the Pointer precompile source code and the contract ABI here.
Last updated on