Skip to main content
ESC

Smart Contract Deep Dive (ESC)

This page documents two major Solidity contract systems deployed on ESC (Elastos Smart Chain): the BPoS staking NFTs and the ELink oracle bridge.


StakeTicket: BPoS staking NFTs

Source: Elastos.ELA.StakeTicket.Solidity

Stack
  • Solidity: 0.7.6
  • Tooling: Hardhat + OpenZeppelin

Purpose

Users stake ELA on the main chain, then claim an ERC-721 NFT on ESC that represents their staking position. The NFT is the on-chain handle for that BPoS position; burning it is part of releasing the position.

Architecture

The system centers on the StakeTicket contract (built from Initializable, Arbiter, OwnableUpgradeable) together with NFT modules:

  • ERC721MinterBurnerPauser: standard ERC-721 variant
  • ERC721UpgradeableMinterBurnerPauser: upgradeable ERC-721 variant
Two NFT implementations
  • v0: Standard ERC-721, no rich metadata.
  • v1+: Upgradeable ERC-721 with a StakeTickNFT struct carrying on-chain BPoS metadata.

Key functions (StakeTicket)

FunctionRole
claim()Orchestrates precompile checks and mints the staking NFT
burnTick()Burns the NFT to release the staking position
getTickFromTokenId()Reads claim history for a token
canClaim()Double-claim guard

The claim() path is the critical integration surface: it invokes ESC precompiles in a fixed order before minting.

Arbiter base contract (precompile surface)

The Arbiter base wires ESC precompiles used for BPoS verification and NFT payload versioning:

Internal NamePrecompile
arbiters1000
pledgeBillVerify1003
pledgeBillTokenID1004
pledgeBillTokenDetail1005
getBPosNFTPayloadVersion()1006
claim() precompile usage

claim() specifically uses 1003 (pledgeBillVerify), 1004 (canClaim / getTokenIDByTxhash semantics in the stack), and 1006 (getBPosNFTPayloadVersion) before minting, aligning verification, eligibility, and payload version with the deployed NFT logic.

Security model

  • Mint/burn authority: Only the StakeTicket contract is allowed to mint and burn NFTs (minter/burner roles on the ERC-721 modules).
  • Double claims: Prevented via precompile 1004 (canClaim / token ID by tx hash), so the same pledge cannot back two live claims.

Example: integrating a dApp with StakeTicket

Below, a minimal consumer contract pattern: call claim() after main-chain staking is reflected in the arbiter/precompile layer, and use burnTick() when your product flow releases the position.

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

interface IStakeTicket {
function claim() external;

function burnTick(uint256 tokenId) external;

function canClaim() external view returns (bool);

function getTickFromTokenId(uint256 tokenId)
external
view
returns (bytes memory);
}

contract StakeTicketClient {
IStakeTicket public immutable stakeTicket;

constructor(address stakeTicket_) {
stakeTicket = IStakeTicket(stakeTicket_);
}

/// @notice Mint the BPoS position NFT on ESC (precompiles 1003/1004/1006 inside StakeTicket).
function claimPosition() external {
require(stakeTicket.canClaim(), "StakeTicket: cannot claim");
stakeTicket.claim();
}

/// @notice Release staking position by burning the NFT (StakeTicket enforces auth).
function releasePosition(uint256 tokenId) external {
stakeTicket.burnTick(tokenId);
}

/// @notice Read persisted tick / claim payload for analytics or UI.
function readTick(uint256 tokenId) external view returns (bytes memory) {
return stakeTicket.getTickFromTokenId(tokenId);
}
}
Deployment addresses

Replace stakeTicket_ with the deployed StakeTicket address on your target ESC network. Always verify against the official deployment manifest for the environment you use (mainnet vs testnet).


Source: Elastos.ELink.Solidity

Stack
  • Solidity: ^0.7.6
  • License: GPL v3.0

Purpose

ELink lets ESC smart contracts query DID sidechain data through a decentralized oracle network, not a single RPC or indexer controlled by one party.

ELink implements a Chainlink-compatible oracle stack adapted to Elastos: BPoS validators act as oracle nodes.

ContractRole
DataConsumerClient-facing API for requesting DID-backed data
ArbiterBFT consensus aggregation and validation
OperatorPer-arbiter oracle node contract (12 deployments)
ChainlinkClientCompatibility with Chainlink-style request/callback patterns
ERC677 (LinkToken)Payment token for oracle requests

12-arbiter BFT consensus

  • All 12 arbiters independently fetch the requested data.
  • A 2/3 BFT threshold applies: 8 of 12 must agree before a response is accepted and delivered.
Why BFT matters

A single compromised or buggy arbiter cannot unilaterally forge the consensus result; the 8/12 rule is the core integrity guarantee for oracle outputs.

50-channel concurrent requests

The design supports up to 50 simultaneous oracle requests (channelized concurrency). This matters for dApps that issue bursts of DID lookups without serializing everything through one global mutex.

P-256 signature verification (ESC precompile)

ELink uses ESC precompile at address 1008 for NIST P-256 signature verification, aligning off-chain arbiter attestations with on-chain validation economics.

Request / response flow

  1. dApp calls DataConsumer.requestDIDData() (or equivalent entry on your fork).
  2. The request fans out to all 12 Operator contracts.
  3. Each arbiter fetches data from the DID chain (per oracle node logic).
  4. Arbiters submit responses on-chain.
  5. When 8/12 agree, the consensus result is delivered to the callback registered by the consumer.

Fee model

Clients pay in LinkToken (ERC677) per request. Budget LinkToken allowance + balance before high-volume integrations.

Security

The BFT threshold is the primary defense against false data from a minority of arbiters; it complements cryptographic checks (including P-256 verification via precompile 1008).

Example: requesting DID data via DataConsumer

The following illustrates a minimal consumer that prepares a request payload, pays in LinkToken, and handles the callback. Adjust types (bytes32 jobId, address callback, etc.) to match the exact DataConsumer ABI in your pinned release.

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

interface IERC677 {
function transferAndCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool);

function approve(address spender, uint256 value) external returns (bool);
}

interface IDataConsumer {
function requestDIDData(
bytes32 jobId,
address callback,
bytes4 callbackSelector,
bytes calldata requestPayload
) external returns (bytes32 requestId);
}

contract DIDLookupClient {
IDataConsumer public immutable dataConsumer;
IERC677 public immutable linkToken;

bytes32 public lastRequestId;
bytes public lastResult;

constructor(address dataConsumer_, address linkToken_) {
dataConsumer = IDataConsumer(dataConsumer_);
linkToken = IERC677(linkToken_);
}

/// @notice Pay LinkToken and fan out oracle work to 12 Operator contracts via DataConsumer.
function requestDid(
bytes32 jobId,
bytes calldata requestPayload,
uint256 linkFee
) external {
linkToken.approve(address(dataConsumer), linkFee);
lastRequestId = dataConsumer.requestDIDData(
jobId,
address(this),
this.oracleCallback.selector,
requestPayload
);
}

/// @notice Called when 8/12 arbiters agree (exact signature per release).
function oracleCallback(bytes32 requestId, bytes calldata result) external {
require(msg.sender == address(dataConsumer), "only consumer");
lastResult = result;
}
}
ABI drift

Oracle interfaces (requestDIDData parameters, callback shape, and LinkToken payment path) vary by tagged release. Pin the repository commit and generate interfaces from the same artifacts you deploy against.


Quick comparison

AspectStakeTicketELink
Primary goalBPoS position as ERC-721 on ESCDID data via decentralized oracle
Trust anchorESC precompiles 1000–1006 family12 arbiters, 8/12 BFT
PaymentStaking on main chain (off ESC)LinkToken (ERC677) per request
Notable precompile1003 / 1004 / 1006 in claim()1008 for P-256 verification

Further reading

  • Review Elastos.ELA.StakeTicket.Solidity for exact upgradeable proxy layout, initializer arguments, and role grants.
  • Review Elastos.ELink.Solidity for Operator deployment topology, request ID lifecycle, and LinkToken economics on ESC.
Source of truth

When this page disagrees with a specific tagged release, the Solidity source and deployment scripts in that tag always win.