Skip to main content

ERC-721 Interaction

Standard ERC-721 contracts work on Sei without modification. This page covers reading token ownership, transferring NFTs, and managing approvals.

Try it: deploy an ERC-721

Compile and deploy a real ERC-721 to Sei testnet from the browser — the Remix sandbox preloads an OpenZeppelin-based DemoNFT with a public mint(). Then point the patterns below at your deployed address. First, add Sei testnet to your wallet: In Remix, compile under Solidity Compiler, then Deploy & Run with Environment set to Injected Provider — MetaMask.

Setup

import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { sei } from 'viem/chains';

const client = createPublicClient({ chain: sei, transport: http() });
const account = privateKeyToAccount('0xYourPrivateKey');
const walletClient = createWalletClient({ account, chain: sei, transport: http() });

const ERC721_ABI = parseAbi([
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function totalSupply() view returns (uint256)',
  'function balanceOf(address owner) view returns (uint256)',
  'function ownerOf(uint256 tokenId) view returns (address)',
  'function tokenURI(uint256 tokenId) view returns (string)',
  'function getApproved(uint256 tokenId) view returns (address)',
  'function isApprovedForAll(address owner, address operator) view returns (bool)',
  'function approve(address to, uint256 tokenId)',
  'function setApprovalForAll(address operator, bool approved)',
  'function transferFrom(address from, address to, uint256 tokenId)',
  'function safeTransferFrom(address from, address to, uint256 tokenId)',
  'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)',
  'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)',
]);

const NFT = '0xNFTContractAddress';
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
const wallet = new ethers.Wallet('0xYourPrivateKey', provider);

const ERC721_ABI = [
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function totalSupply() view returns (uint256)',
  'function balanceOf(address owner) view returns (uint256)',
  'function ownerOf(uint256 tokenId) view returns (address)',
  'function tokenURI(uint256 tokenId) view returns (string)',
  'function getApproved(uint256 tokenId) view returns (address)',
  'function isApprovedForAll(address owner, address operator) view returns (bool)',
  'function approve(address to, uint256 tokenId)',
  'function setApprovalForAll(address operator, bool approved)',
  'function transferFrom(address from, address to, uint256 tokenId)',
  'function safeTransferFrom(address from, address to, uint256 tokenId)',
  'event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)',
  'event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)',
];

const NFT = '0xNFTContractAddress';
const readContract = new ethers.Contract(NFT, ERC721_ABI, provider);
const writeContract = new ethers.Contract(NFT, ERC721_ABI, wallet);

Reading Token Data

const tokenId = 1n;

const [owner, uri, approved] = await Promise.all([
  client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'ownerOf', args: [tokenId] }),
  client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'tokenURI', args: [tokenId] }),
  client.readContract({ address: NFT, abi: ERC721_ABI, functionName: 'getApproved', args: [tokenId] }),
]);
const tokenId = 1n;

const [owner, uri, approved] = await Promise.all([
  readContract.ownerOf(tokenId),
  readContract.tokenURI(tokenId),
  readContract.getApproved(tokenId),
]);

Checking Ownership Count

const balance = await client.readContract({
  address: NFT,
  abi: ERC721_ABI,
  functionName: 'balanceOf',
  args: ['0xOwnerAddress'],
});
const balance = await readContract.balanceOf('0xOwnerAddress');

Transferring an NFT

const hash = await walletClient.writeContract({
  address: NFT,
  abi: ERC721_ABI,
  functionName: 'safeTransferFrom',
  args: [account.address, '0xRecipient', 1n],
});

const receipt = await client.waitForTransactionReceipt({ hash });
const tx = await writeContract.safeTransferFrom(wallet.address, '0xRecipient', 1n);
const receipt = await tx.wait();

Approving a Single Token

const hash = await walletClient.writeContract({
  address: NFT,
  abi: ERC721_ABI,
  functionName: 'approve',
  args: ['0xOperatorAddress', 1n],
});
const tx = await writeContract.approve('0xOperatorAddress', 1n);

Approving All Tokens (Operator)

// Grant operator approval
const hash = await walletClient.writeContract({
  address: NFT,
  abi: ERC721_ABI,
  functionName: 'setApprovalForAll',
  args: ['0xOperatorAddress', true],
});

// Revoke
const revokeHash = await walletClient.writeContract({
  address: NFT,
  abi: ERC721_ABI,
  functionName: 'setApprovalForAll',
  args: ['0xOperatorAddress', false],
});
// Grant operator approval
const tx = await writeContract.setApprovalForAll('0xOperatorAddress', true);

// Revoke
const revokeTx = await writeContract.setApprovalForAll('0xOperatorAddress', false);

Watching Transfer Events

const unwatch = client.watchContractEvent({
  address: NFT,
  abi: ERC721_ABI,
  eventName: 'Transfer',
  onLogs: (logs) => {
    logs.forEach(({ args }) => {
      console.log(`Token ${args.tokenId}: ${args.from}${args.to}`);
    });
  },
});

// Stop watching
unwatch();
readContract.on('Transfer', (from, to, tokenId) => {
  console.log(`Token ${tokenId}: ${from}${to}`);
});

// Stop watching
readContract.off('Transfer');

CosmWasm NFT Compatibility

CW721 tokens on Sei have ERC-721 pointer contracts that expose the standard ERC-721 interface. You can use all of the patterns above against a CW721 pointer address. See Pointer Contracts for how to look up the pointer address.