JSON Precompile Usage
Address: 0x0000000000000000000000000000000000001003
The Sei JSON precompile allows EVM applications to efficiently parse and query JSON data directly within smart contracts. This enables complex data handling capabilities that are not natively available in Solidity, making it easier to work with structured data from external sources or APIs.
How Does the JSON Precompile Work?
The JSON precompile at address 0x0000000000000000000000000000000000001003
exposes functions like extractAsBytes()
, extractAsBytesList()
, and extractAsUint256()
.
- Direct Integration: EVM contracts and dApps can parse JSON data like any other smart contract method.
- Native Execution: JSON parsing is executed at the native level for maximum efficiency.
- Seamless Bridge: No need for complex workarounds or external libraries for JSON handling.
Use Cases
- Oracle Integration: Parse complex oracle responses containing multiple data points.
- DeFi Applications: Process structured price feeds and market data.
- Gaming: Handle complex game state and player data stored in JSON format.
- Cross-Chain Communication: Parse messages and data from other chains.
- NFT Metadata: Extract and manipulate NFT metadata stored in JSON format.
Functions
The JSON precompile exposes the following functions:
Query Functions
/// Extracts data as bytes from the input using the specified key.
/// @param input The input data.
/// @param key The key to extract.
/// @return The extracted data as bytes.
function extractAsBytes(
bytes memory input,
string memory key
) external view returns (bytes memory response);
/// Extracts data as a list of bytes from the input using the specified key.
/// @param input The input data.
/// @param key The key to extract.
/// @return The extracted data as bytes collection.
function extractAsBytesList(
bytes memory input,
string memory key
) external view returns (bytes[] memory response);
/// Extracts data as a uint256 from the input using the specified key.
/// @param input The input data.
/// @param key The key to extract.
/// @return The extracted uint256.
function extractAsUint256(
bytes memory input,
string memory key
) 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
- MetaMask or compatible EVM wallet configured for Sei Mainnet
- SEI tokens for gas fees
Install Dependencies
Install the required packages for interacting with Sei precompiles:
# Install ethers.js for smart contract interactions
npm install ethers
# Install Sei EVM bindings for precompile addresses and ABIs
npm install @sei-js/precompiles@^2.1.2
# Install dotenv for managing private keys (recommended for security)
npm install dotenv
Import Precompile Components
// Import JSON precompile address and ABI
// View the entire ABI here: https://github.com/sei-protocol/sei-chain/tree/main/precompiles/json
import { JSON_PRECOMPILE_ABI, JSON_PRECOMPILE_ADDRESS } from '@sei-js/precompiles';
import { ethers } from 'ethers';
0x0000000000000000000000000000000000001003
Contract Initialization
Set up your provider, signer, and contract instance:
// Using MetaMask as the signer and provider (browser environment)
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();
// Or for Node.js environment
const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Create a contract instance for the JSON precompile
const jsonPrecompile = new ethers.Contract(JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, signer);
Data Type Handling
The JSON precompile has specific limitations for different data types:
Supported Data Types
Function | Supports | Limitations |
---|---|---|
extractAsBytes | Strings, Objects, Arrays | Returns raw bytes - needs conversion |
extractAsUint256 | Integers only | No decimals, booleans, or negative numbers |
extractAsBytesList | Arrays of strings/objects | Each element returned as bytes |
Data Type Conversion Strategies
// For decimal numbers - store as integers with known precision
const priceData = { price: 250 }; // Represents 2.50 with 2 decimal places
const price = await jsonPrecompile.extractAsUint256(inputData, 'price');
const actualPrice = parseFloat(price.toString()) / 100; // Convert back to 2.50
// For boolean values - use 0/1 integers
const statusData = { isActive: 1 }; // 1 = true, 0 = false
const isActiveInt = await jsonPrecompile.extractAsUint256(inputData, 'isActive');
const isActive = isActiveInt === 1n;
// For complex objects - extract as bytes and parse
const userData = { user: { name: 'Alice', balance: 1000 } };
const userBytes = await jsonPrecompile.extractAsBytes(inputData, 'user');
const userString = ethers.toUtf8String(userBytes);
const userObject = JSON.parse(userString);
Error Handling Utilities
Create comprehensive error handling for all extraction functions:
// Safe extraction for bytes data
async function safeExtractBytes(jsonPrecompile: ethers.Contract, data: any, key: string, defaultValue: string = ''): Promise<string> {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsBytes(inputData, key);
return ethers.toUtf8String(result);
} catch (error: any) {
console.log(`Key "${key}" not found or invalid, using default value`);
return defaultValue;
}
}
// Safe extraction for uint256 data
async function safeExtractUint256(jsonPrecompile: ethers.Contract, data: any, key: string, defaultValue: bigint = 0n): Promise<bigint> {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsUint256(inputData, key);
return result;
} catch (error: any) {
console.log(`Key "${key}" not found or not a valid integer, using default value`);
return defaultValue;
}
}
// Safe extraction for array data
async function safeExtractBytesList(jsonPrecompile: ethers.Contract, data: any, key: string, defaultValue: string[] = []): Promise<string[]> {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsBytesList(inputData, key);
return result.map((bytes: any) => ethers.toUtf8String(bytes));
} catch (error: any) {
console.log(`Key "${key}" not found or not a valid array, using default value`);
return defaultValue;
}
}
Step-by-Step Guide: Using the JSON Precompile
Extract String Data
// JSON data with string values
const data = { name: 'Sei Token', symbol: 'SEI' };
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
// Extract string using safe method
const name = await safeExtractBytes(jsonPrecompile, data, 'name');
console.log('Name:', name); // Output: "Sei Token"
// Or direct extraction with error handling
try {
const symbolBytes = await jsonPrecompile.extractAsBytes(inputData, 'symbol');
const symbol = ethers.toUtf8String(symbolBytes);
console.log('Symbol:', symbol); // Output: "SEI"
} catch (error) {
console.error('Failed to extract symbol:', error.message);
}
Extract Numeric Data
// JSON data with numbers (integers only)
const data = { price: 42, volume: 1000000 };
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
// Extract integer using safe method
const price = await safeExtractUint256(jsonPrecompile, data, 'price');
console.log('Price:', price.toString()); // Output: "42"
// Handle decimal numbers by using integer representation
const decimalData = { rate: 275 }; // Represents 2.75 with 2 decimal places
const rate = await safeExtractUint256(jsonPrecompile, decimalData, 'rate');
const actualRate = Number(rate) / 100; // Convert to 2.75
console.log('Rate:', actualRate);
Extract Array Data
// JSON data with array
const data = { tokens: ['SEI', 'USDC', 'USDT'] };
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
// Extract array using safe method
const tokens = await safeExtractBytesList(jsonPrecompile, data, 'tokens');
console.log('Tokens:', tokens); // Output: ["SEI", "USDC", "USDT"]
// Handle mixed arrays by extracting as bytes and parsing
const mixedData = { values: [42, 'test', 100] };
const valuesBytes = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(mixedData)), 'values');
const valuesString = ethers.toUtf8String(valuesBytes);
const valuesArray = JSON.parse(valuesString);
console.log('Mixed values:', valuesArray); // Output: [42, "test", 100]
Extract Nested Data
// For nested data, extract the parent object first
const data = {
user: {
wallet: {
balance: 1000,
currency: 'SEI'
}
}
};
// Method 1: Extract parent object and parse manually (recommended)
const userBytes = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(data)), 'user');
const userData = JSON.parse(ethers.toUtf8String(userBytes));
const balance = userData.wallet.balance;
const currency = userData.wallet.currency;
console.log('Balance:', balance); // Output: 1000
console.log('Currency:', currency); // Output: "SEI"
// Method 2: Extract deeper nested object
const walletBytes = await jsonPrecompile.extractAsBytes(
ethers.toUtf8Bytes(JSON.stringify(data)),
'wallet' // Direct key, not dot notation
);
const walletData = JSON.parse(ethers.toUtf8String(walletBytes));
console.log('Wallet data:', walletData); // Output: { balance: 1000, currency: "SEI" }
Complete Integration Example
Create a comprehensive JSON parsing application for mainnet:
// json-precompile-mainnet.js
const { ethers } = require('ethers');
const { JSON_PRECOMPILE_ABI, JSON_PRECOMPILE_ADDRESS } = require('@sei-js/precompiles');
require('dotenv').config();
// Safe extraction utilities
async function safeExtractBytes(jsonPrecompile, data, key, defaultValue = '') {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsBytes(inputData, key);
return ethers.toUtf8String(result);
} catch (error) {
console.log(` β οΈ Key "${key}" not found, using default: "${defaultValue}"`);
return defaultValue;
}
}
async function safeExtractUint256(jsonPrecompile, data, key, defaultValue = 0n) {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsUint256(inputData, key);
return result;
} catch (error) {
console.log(` β οΈ Key "${key}" not found or invalid integer, using default: ${defaultValue}`);
return defaultValue;
}
}
async function safeExtractBytesList(jsonPrecompile, data, key, defaultValue = []) {
try {
const inputData = ethers.toUtf8Bytes(JSON.stringify(data));
const result = await jsonPrecompile.extractAsBytesList(inputData, key);
return result.map((bytes) => ethers.toUtf8String(bytes));
} catch (error) {
console.log(` β οΈ Key "${key}" not found or invalid array, using default: [${defaultValue.join(', ')}]`);
return defaultValue;
}
}
async function main() {
try {
// Connect to Sei Mainnet
console.log('π Connecting to Sei Mainnet...');
const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
// Use private key from environment variables (secure approach)
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error('Please set PRIVATE_KEY in .env file');
}
const wallet = new ethers.Wallet(privateKey, provider);
console.log(`π€ Wallet address: ${wallet.address}`);
// Check balance
const balance = await provider.getBalance(wallet.address);
const balanceEther = ethers.formatEther(balance);
console.log(`π° Wallet balance: ${balanceEther} SEI\n`);
// Initialize JSON precompile contract
const jsonPrecompile = new ethers.Contract(JSON_PRECOMPILE_ADDRESS, JSON_PRECOMPILE_ABI, wallet);
console.log('π === Sei Mainnet JSON Precompile Demo ===\n');
// Complex sample data - simulating a real-world oracle response
const oracleData = {
market: {
prices: {
SEI: 275, // 2.75 USD (stored as integer with 2 decimal places)
BTC: 5000000, // 50,000.00 USD
ETH: 300000 // 3,000.00 USD
},
volume: {
daily: 1500000000, // $15M daily volume
weekly: 8500000000 // $85M weekly volume
}
},
metadata: {
timestamp: '2024-12-27T12:00:00Z',
source: 'MainnetPriceOracle',
version: '2.1.0',
isLive: 1 // 1 = true, 0 = false
},
assets: ['SEI', 'BTC', 'ETH', 'USDC'],
validators: {
active: 125,
total: 200
}
};
console.log('π Input JSON Data:');
console.log(JSON.stringify(oracleData, null, 2));
console.log();
// 1. Extract string data with error handling
console.log('1οΈβ£ Extracting String Data...');
// Extract metadata object first, then parse individual fields
const metadataBytes = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(oracleData)), 'metadata');
const metadata = JSON.parse(ethers.toUtf8String(metadataBytes));
console.log(` π Source: ${metadata.source}`);
console.log(` π’ Version: ${metadata.version}`);
console.log(` β° Timestamp: ${metadata.timestamp}`);
console.log(` π’ Is Live: ${metadata.isLive === 1 ? 'Yes' : 'No'}`);
// Test missing key
const missingKey = await safeExtractBytes(jsonPrecompile, oracleData, 'nonexistent', 'N/A');
console.log(` β Missing Key: ${missingKey}`);
// 2. Extract numeric data with proper object parsing
console.log('\n2οΈβ£ Extracting Numeric Data...');
// Extract market object first
const marketBytes = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(oracleData)), 'market');
const market = JSON.parse(ethers.toUtf8String(marketBytes));
// Extract validators object
const validatorsBytes = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(oracleData)), 'validators');
const validators = JSON.parse(ethers.toUtf8String(validatorsBytes));
// Convert integers back to decimals
const seiPrice = market.prices.SEI / 100; // 2 decimal places
const btcPrice = market.prices.BTC / 100; // 2 decimal places
const isLive = metadata.isLive === 1; // Convert to boolean
console.log(` π° SEI Price: ${seiPrice} (raw: ${market.prices.SEI})`);
console.log(` βΏ BTC Price: ${btcPrice.toLocaleString()} (raw: ${market.prices.BTC})`);
console.log(` πΉ Daily Volume: ${market.volume.daily.toLocaleString()}`);
console.log(` β
Active Validators: ${validators.active}`);
console.log(` π΄ Is Live: ${isLive} (raw: ${metadata.isLive})`);
// 3. Extract array data
console.log('\n3οΈβ£ Extracting Array Data...');
const assets = await safeExtractBytesList(jsonPrecompile, oracleData, 'assets');
const invalidArray = await safeExtractBytesList(jsonPrecompile, oracleData, 'invalid.array', ['DEFAULT']);
console.log(` πͺ Assets: [${assets.join(', ')}]`);
console.log(` β Invalid Array: [${invalidArray.join(', ')}]`);
// 4. Extract complex nested objects
console.log('\n4οΈβ£ Extracting Complex Nested Data...');
console.log(' π Market Data:');
console.log(` Prices: SEI=${market.prices.SEI / 100}, BTC=${market.prices.BTC / 100}, ETH=${market.prices.ETH / 100}`);
console.log(` Volume: Daily=${market.volume.daily.toLocaleString()}, Weekly=${market.volume.weekly.toLocaleString()}`);
console.log(` π₯ Validator Data:`);
console.log(` Active: ${validators.active}, Total: ${validators.total}`);
// 5. Gas optimization demonstration
console.log('\n5οΈβ£ Gas Usage Analysis...');
const gasEstimates = {
small: 50000, // Simple key extraction
medium: 80000, // Nested object extraction
large: 120000 // Complex array/object processing
};
console.log(' β½ Recommended Gas Limits:');
console.log(` Simple extraction: ${gasEstimates.small.toLocaleString()} gas`);
console.log(` Nested extraction: ${gasEstimates.medium.toLocaleString()} gas`);
console.log(` Complex processing: ${gasEstimates.large.toLocaleString()} gas`);
// 6. Performance test with corrected key extraction
console.log('\n6οΈβ£ Performance & Error Testing...');
const testCases = [
{ key: 'metadata', expected: 'object', description: 'metadata object' },
{ key: 'market', expected: 'object', description: 'market data' },
{ key: 'assets', expected: 'array', description: 'assets array' },
{ key: 'nonexistent', expected: 'error', description: 'missing key' }
];
for (const testCase of testCases) {
try {
const start = Date.now();
const result = await safeExtractBytes(jsonPrecompile, oracleData, testCase.key);
const duration = Date.now() - start;
const preview = result.length > 50 ? result.substring(0, 47) + '...' : result;
console.log(` β
${testCase.description}: "${preview}" (${duration}ms)`);
} catch (error) {
console.log(` β ${testCase.description}: ${error.message}`);
}
}
console.log('\nπ === JSON Precompile Demo Completed Successfully! ===');
} catch (error) {
console.error('\nβ Error:', error.message);
console.log('\nπ§ Troubleshooting tips:');
console.log('1. Ensure PRIVATE_KEY is set in .env file');
console.log('2. Verify sufficient SEI balance for gas fees');
console.log('3. Check network connectivity to Sei mainnet');
console.log('4. Validate JSON structure before processing');
process.exit(1);
}
}
// Run the demo
main().catch(console.error);
Running the Mainnet Example
- Create a new directory and initialize npm:
mkdir sei-json-mainnet
cd sei-json-mainnet
npm init -y
- Install dependencies:
npm install ethers @sei-js/precompiles@^2.1.2 dotenv
- Create a
.env
file:
PRIVATE_KEY=your_private_key_here
.env
file should be added to your .gitignore
.-
Create the demo file: Copy the complete integration example above into
json-precompile-mainnet.js
-
Ensure you have SEI tokens:
- SEI for gas fees (small amounts needed for view functions)
-
Run the script:
node json-precompile-mainnet.js
Expected Output
π Connecting to Sei Mainnet...
π€ Wallet address: 0x742d35Cc6634C0532925a3b844Bc6789e065f3B
π° Wallet balance: 15.5 SEI
π === Sei Mainnet JSON Precompile Demo ===
π Input JSON Data:
{
"market": {
"prices": {
"SEI": 275,
"BTC": 5000000,
"ETH": 300000
},
"volume": {
"daily": 1500000000,
"weekly": 8500000000
}
},
"metadata": {
"timestamp": "2024-12-27T12:00:00Z",
"source": "MainnetPriceOracle",
"version": "2.1.0",
"isLive": 1
},
"assets": ["SEI", "BTC", "ETH", "USDC"],
"validators": {
"active": 125,
"total": 200
}
}
1οΈβ£ Extracting String Data...
π Source: MainnetPriceOracle
π’ Version: 2.1.0
β° Timestamp: 2024-12-27T12:00:00Z
β οΈ Key "nonexistent.key" not found, using default: "N/A"
β Missing Key: N/A
2οΈβ£ Extracting Numeric Data...
π° SEI Price: $2.75 (raw: 275)
βΏ BTC Price: $50,000 (raw: 5000000)
πΉ Daily Volume: $1,500,000,000
β
Active Validators: 125
π΄ Is Live: true (raw: 1)
3οΈβ£ Extracting Array Data...
πͺ Assets: [SEI, BTC, ETH, USDC]
β οΈ Key "invalid.array" not found or invalid array, using default: [DEFAULT]
β Invalid Array: [DEFAULT]
4οΈβ£ Extracting Complex Nested Data...
π Market Data:
Prices: SEI=$2.75, BTC=$50000, ETH=$3000
Volume: Daily=$1,500,000,000, Weekly=$8,500,000,000
5οΈβ£ Gas Usage Analysis...
β½ Recommended Gas Limits:
Simple extraction: 50,000 gas
Nested extraction: 80,000 gas
Complex processing: 120,000 gas
6οΈβ£ Performance & Error Testing...
β
metadata.source: "MainnetPriceOracle" (15ms)
β
market.prices.SEI: "275" (12ms)
β
assets: "[\"SEI\",\"BTC\",\"ETH\",\"USDC\"]" (18ms)
β οΈ Key "nonexistent.deeply.nested.key" not found, using default: ""
β
nonexistent.deeply.nested.key: "" (8ms)
π === JSON Precompile Demo Completed Successfully! ===
Advanced Usage Examples
Oracle Price Feed Integration
// Advanced oracle integration with comprehensive error handling
class SeiPriceOracle {
private jsonPrecompile: ethers.Contract;
constructor(jsonPrecompile: ethers.Contract) {
this.jsonPrecompile = jsonPrecompile;
}
async parsePriceFeed(oracleResponse: any): Promise<{
prices: Map<string, number>;
timestamp: string;
isValid: boolean;
}> {
try {
// Extract timestamp
const timestamp = await safeExtractBytes(this.jsonPrecompile, oracleResponse, 'metadata.timestamp');
// Extract validation flag
const isValidRaw = await safeExtractUint256(this.jsonPrecompile, oracleResponse, 'metadata.isValid');
const isValid = isValidRaw === 1n;
// Extract price data
const prices = new Map<string, number>();
const assets = await safeExtractBytesList(this.jsonPrecompile, oracleResponse, 'assets');
for (const asset of assets) {
const priceRaw = await safeExtractUint256(this.jsonPrecompile, oracleResponse, `prices.${asset}`);
// Convert from integer with 6 decimal places
prices.set(asset, Number(priceRaw) / 1000000);
}
return { prices, timestamp, isValid };
} catch (error) {
console.error('Failed to parse oracle response:', error);
return {
prices: new Map(),
timestamp: '',
isValid: false
};
}
}
}
Troubleshooting
Common Issues and Solutions
Key Not Found
// Comprehensive error handling for missing keys
async function handleMissingKeys(jsonPrecompile: ethers.Contract, data: any) {
const keys = ['required.key', 'optional.key', 'deeply.nested.key'];
for (const key of keys) {
try {
const result = await jsonPrecompile.extractAsBytes(ethers.toUtf8Bytes(JSON.stringify(data)), key);
console.log(`β
${key}: ${ethers.toUtf8String(result)}`);
} catch (error: any) {
if (error.message.includes('key not found')) {
console.log(`β οΈ Key "${key}" not found in JSON structure`);
} else {
console.log(`β Error extracting "${key}": ${error.message}`);
}
}
}
}
Error Code Reference
Error | Cause | Solution |
---|---|---|
key not found | Specified key doesnβt exist in JSON | Verify key path and JSON structure |
invalid JSON format | Malformed JSON input | Use |
execution reverted | Gas limit too low | Increase gas limit based on data complexity |
invalid integer | Non-numeric value for | Ensure the value is a valid integer |
out of gas | Insufficient gas for large JSON | Use |
Key Considerations and Tricks
- Integers Only:
extractAsUint256
only handles integers - no decimals, booleans, or negative numbers - Decimal Handling: Store decimal numbers as integers with known precision (e.g., 275 for 2.75 with 2 decimal places)
- Boolean Values: Use 0/1 integers to represent false/true
- Key Paths: Extract parent objects first, then parse manually - dot notation may not be supported
- Arrays: Must use
extractAsBytesList()
for array data - Gas Costs: Large JSON objects require higher gas limits
- Encoding: Always use UTF-8 encoding with
ethers.toUtf8Bytes()
- Error Handling: Always implement fallback values for production applications