ESC Precompiled Contracts
ESC includes custom precompiled contracts at addresses 1000 through 1009 (in addition to the standard Ethereum precompiles at 1–9). These provide on-chain access to Elastos consensus data, P-256 cryptography, and BPoS staking primitives directly from Solidity contracts.
The precompile addresses and implementations are defined in the ESC source code:
params/protocol_params.go— address constantscore/vm/contracts.go— implementations
Address Map
| Address (Decimal) | Address (Hex) | Internal Name | Purpose |
|---|---|---|---|
| 1000 | 0x00000000000000000000000000000003E8 | arbiters | Query the current BPoS validator set (returns keccak256 hashes of arbiter public keys) |
| 1001 | 0x00000000000000000000000000000003E9 | p256Verify | Verify a NIST P-256 (secp256r1) signature given a public key, data, and signature |
| 1002 | 0x00000000000000000000000000000003EA | pbkVerifySignature | Verify a secp256k1 signature by recovering and comparing the signer's public key |
| 1003 | 0x00000000000000000000000000000003EB | pledgeBillVerify | Verify BPoS staking pledge bills (standard or multi-sig) |
| 1004 | 0x00000000000000000000000000000003EC | pledgeBillTokenID | Look up StakeTicket NFT token ID from an ELA main chain transaction hash |
| 1005 | 0x00000000000000000000000000000003ED | pledgeBillTokenDetail | Retrieve BPoS staking NFT payload details (referKey, stakeAddress, heights, votes) |
| 1006 | 0x00000000000000000000000000000003EE | pledgeBillPayloadVersion | Query the payload version of a BPoS NFT |
| 1007 | 0x00000000000000000000000000000003EF | getMainChainBlockByHeight | Get a main chain block header by height (returns Previous, Bits, MerkleRoot, Hash, Height) |
| 1008 | 0x00000000000000000000000000000003F0 | getMainChainLatestHeight | Get the latest main chain block height synced by the SPV module |
| 1009 | 0x00000000000000000000000000000003F1 | p256VerifyDigest | Verify 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);
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 Name | Purpose |
|---|---|---|---|
| 22 | 0x0000000000000000000000000000000000000016 | operationDID | Perform DID operations (create, update, deactivate) |
| 23 | 0x0000000000000000000000000000000000000017 | resolveDID | Resolve a DID to its document |
| 2007 | 0x00000000000000000000000000000007D7 | kyc | KYC (Know Your Customer) verification operations |
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));
}
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);
}
}