Skip to main content
ESC

ESC Precompiled Contracts

ESC includes custom precompiled contracts at addresses 1000 through 1009 (in addition to the standard Ethereum precompiles at 19). These provide on-chain access to Elastos consensus data, P-256 cryptography, and BPoS staking primitives directly from Solidity contracts.

Source Reference

The precompile addresses and implementations are defined in the ESC source code:

Address Map

Address (Decimal)Address (Hex)Internal NamePurpose
10000x00000000000000000000000000000003E8arbitersQuery the current BPoS validator set (returns keccak256 hashes of arbiter public keys)
10010x00000000000000000000000000000003E9p256VerifyVerify a NIST P-256 (secp256r1) signature given a public key, data, and signature
10020x00000000000000000000000000000003EApbkVerifySignatureVerify a secp256k1 signature by recovering and comparing the signer's public key
10030x00000000000000000000000000000003EBpledgeBillVerifyVerify BPoS staking pledge bills (standard or multi-sig)
10040x00000000000000000000000000000003ECpledgeBillTokenIDLook up StakeTicket NFT token ID from an ELA main chain transaction hash
10050x00000000000000000000000000000003EDpledgeBillTokenDetailRetrieve BPoS staking NFT payload details (referKey, stakeAddress, heights, votes)
10060x00000000000000000000000000000003EEpledgeBillPayloadVersionQuery the payload version of a BPoS NFT
10070x00000000000000000000000000000003EFgetMainChainBlockByHeightGet a main chain block header by height (returns Previous, Bits, MerkleRoot, Hash, Height)
10080x00000000000000000000000000000003F0getMainChainLatestHeightGet the latest main chain block height synced by the SPV module
10090x00000000000000000000000000000003F1p256VerifyDigestVerify a P-256 signature against a pre-hashed digest (no double-hashing)

Calling Precompiled Contracts from Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ElastosPrecompiles {
address constant ARBITERS = address(0x00000000000000000000000000000003E8);
address constant P256_VERIFY = address(0x00000000000000000000000000000003E9);
address constant PBK_VERIFY_SIGNATURE = address(0x00000000000000000000000000000003EA);
address constant PLEDGE_BILL_VERIFY = address(0x00000000000000000000000000000003EB);
address constant PLEDGE_BILL_TOKEN_ID = address(0x00000000000000000000000000000003EC);
address constant PLEDGE_BILL_TOKEN_DETAIL = address(0x00000000000000000000000000000003ED);
address constant PLEDGE_BILL_PAYLOAD_VERSION = address(0x00000000000000000000000000000003EE);
address constant MAIN_CHAIN_BLOCK_BY_HEIGHT = address(0x00000000000000000000000000000003EF);
address constant MAIN_CHAIN_LATEST_HEIGHT = address(0x00000000000000000000000000000003F0);
address constant P256_VERIFY_DIGEST = address(0x00000000000000000000000000000003F1);

/// @notice Query the current BPoS validator set
/// @return Keccak256 hashes of all current arbiter public keys, concatenated as 32-byte words
function getArbiters() public view returns (bytes memory) {
(bool success, bytes memory result) = ARBITERS.staticcall("");
require(success, "arbiters call failed");
return result;
}

/// @notice Verify a P-256 signature
/// @param pubkey 33-byte compressed P-256 public key
/// @param data 64-byte data that was signed
/// @param sig 64-byte P-256 signature
/// @return 32-byte result (0x01 = valid, 0x00 = invalid)
function verifyP256(bytes memory pubkey, bytes memory data, bytes memory sig) public view returns (bool) {
bytes memory input = abi.encodePacked(uint256(0), pubkey, data, sig);
(bool success, bytes memory result) = P256_VERIFY.staticcall(input);
require(success, "p256Verify call failed");
return result[31] == 0x01;
}

/// @notice Get the latest main chain height synced by SPV
/// @return height The latest main chain block height
function getMainChainLatestHeight() public view returns (uint32) {
(bool success, bytes memory result) = MAIN_CHAIN_LATEST_HEIGHT.staticcall("");
require(success, "getMainChainLatestHeight call failed");
return abi.decode(result, (uint32));
}
}

Calling Precompiled Contracts from ethers.js

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://api.elastos.io/esc");

// Address 1000: Query current BPoS arbiters
async function getArbiters(): Promise<string[]> {
const result = await provider.call({
to: "0x00000000000000000000000000000003E8", // arbiters (1000)
data: "0x",
});

// Returns concatenated 32-byte keccak256 hashes of arbiter public keys
const hashes: string[] = [];
for (let i = 2; i < result.length; i += 64) {
hashes.push("0x" + result.slice(i, i + 64));
}
return hashes;
}

// Address 1008: Get latest main chain height
async function getMainChainLatestHeight(): Promise<number> {
const result = await provider.call({
to: "0x00000000000000000000000000000003F0", // getMainChainLatestHeight (1008)
data: "0x",
});

return parseInt(result, 16);
}

// Usage
const arbiters = await getArbiters();
console.log("Active arbiters:", arbiters.length);
arbiters.forEach((hash, i) => console.log(` [${i}] ${hash}`));

const height = await getMainChainLatestHeight();
console.log("Main chain height:", height);
warning

Some precompiled contracts (like arbiters at 1000) return raw bytes, not ABI-encoded or JSON data. Decode the output according to the specific precompile's format. See Common Pitfalls for details.

EID Precompiled Contracts

The EID chain has DID-specific precompiled contracts for on-chain identity operations:

Address (Decimal)Address (Hex)Internal NamePurpose
220x0000000000000000000000000000000000000016operationDIDPerform DID operations (create, update, deactivate)
230x0000000000000000000000000000000000000017resolveDIDResolve a DID to its document
20070x00000000000000000000000000000007D7kycKYC (Know Your Customer) verification operations
Source Reference

EID DID precompiles are defined in core/vm/did_contracts.go.

// Resolve a DID via EID precompiled contract
async function resolveDID(didString: string): Promise<any> {
const eidProvider = new ethers.JsonRpcProvider("https://api.elastos.io/eid");

const result = await eidProvider.call({
to: "0x0000000000000000000000000000000000000017", // resolveDID (address 23)
data: ethers.hexlify(ethers.toUtf8Bytes(didString)),
});

if (result === "0x" || result.length <= 2) {
return null;
}

return JSON.parse(ethers.toUtf8String(result));
}
tip

For a higher-level DID SDK that wraps these precompiled contracts, see DID Integration.

Using Precompiles in a dApp

A practical example: querying BPoS staking data on-chain:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract StakingDashboard {
address constant ARBITERS = address(0x00000000000000000000000000000003E8);
address constant MAIN_CHAIN_HEIGHT = address(0x00000000000000000000000000000003F0);
address constant PLEDGE_PAYLOAD_VER = address(0x00000000000000000000000000000003EE);

event StakingSnapshot(
uint256 indexed blockNumber,
bytes arbiters,
bytes mainChainHeight,
bytes pledgeVersion
);

function takeSnapshot() public {
(, bytes memory arbiters) = ARBITERS.staticcall("");
(, bytes memory mainHeight) = MAIN_CHAIN_HEIGHT.staticcall("");
(, bytes memory pledgeVer) = PLEDGE_PAYLOAD_VER.staticcall("");

emit StakingSnapshot(block.number, arbiters, mainHeight, pledgeVer);
}
}