Skip to main content
Stable exposes protocol-level settlement logic through precompiled contracts at fixed addresses. The precompiles let EVM code call Stable SDK modules (staking, reward distribution, STABLE token operations) without re-implementing them. They’re significantly more gas efficient than equivalent Solidity implementations because they run at the protocol level. This guide shows how to call a precompile from both Solidity and ethers.js, and when to use one over a regular contract.
Concept: For what system modules do and why they’re precompiles, see System modules. For per-module method signatures and events, see the System modules reference.

What’s exposed

ModulePrecompile addressUse for
Bank0x0000000000000000000000000000000000001003STABLE token transfers and balance operations
Distribution0x0000000000000000000000000000000000000801Claiming staking rewards, reward queries, commission management
Staking0x0000000000000000000000000000000000000800Delegation, undelegation, redelegation, validator queries
StableSystem0x0000000000000000000000000000000000009999EVM event emission for system transactions (unbonding completions)
All four are callable from any EVM contract or off-chain client. The addresses are stable and identical on mainnet and testnet.

When to call a precompile vs a regular contract

  • Use a precompile when the operation maps to a Stable SDK module: staking, reward distribution, STABLE token ops. Calling the precompile is both cheaper and the only way to trigger protocol-level behavior.
  • Use a regular contract when the operation is application logic: escrow, pricing, access control. Wrap the precompile call in your own contract if you need custom authorization or validation.
Precompiles are not a replacement for application contracts. They’re a stable interface into the underlying protocol.

Call from Solidity

Declare an interface for the methods you need, then call the precompile as if it were a deployed contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IStaking {
    function delegate(
        address delegatorAddress,
        string calldata validatorAddress,
        uint256 amount
    ) external returns (bool success);

    function delegation(
        address delegatorAddress,
        string calldata validatorAddress
    ) external view returns (uint256 shares, uint256 balance);
}

contract StakingHelper {
    address constant STAKING_PRECOMPILE =
        0x0000000000000000000000000000000000000800;

    /// @notice Delegate STABLE tokens to a validator from this contract.
    function delegateToValidator(
        string calldata validatorAddress,
        uint256 amount
    ) external returns (bool) {
        return IStaking(STAKING_PRECOMPILE).delegate(
            address(this),
            validatorAddress,
            amount
        );
    }

    /// @notice Read the current delegation of this contract to a validator.
    function myDelegation(string calldata validatorAddress)
        external
        view
        returns (uint256 shares, uint256 balance)
    {
        return IStaking(STAKING_PRECOMPILE).delegation(
            address(this),
            validatorAddress
        );
    }
}
Compile and deploy with Foundry or Hardhat. The precompile address is burned into the contract at the constant slot, so there’s nothing to wire up post-deployment.
// SAFE: precompile address is fixed on Stable and never changes.

Call from ethers.js

For off-chain clients, declare the same interface as a minimal ABI and instantiate a contract pointed at the precompile address.
// queryDelegation.ts
import { ethers } from "ethers";
import "dotenv/config";

const provider = new ethers.JsonRpcProvider("https://rpc.testnet.stable.xyz");

const STAKING_PRECOMPILE = "0x0000000000000000000000000000000000000800";

const staking = new ethers.Contract(
  STAKING_PRECOMPILE,
  [
    "function delegation(address delegator, string validator) view returns (uint256 shares, uint256 balance)",
  ],
  provider
);

const delegator = "0xDelegatorAddress";
const validator = "stablevaloper1..."; // bech32 validator operator address

const [shares, balance] = await staking.delegation(delegator, validator);
console.log("Delegation shares: ", shares.toString());
console.log("Delegation balance:", ethers.formatUnits(balance, 18), "STABLE");
npx tsx queryDelegation.ts
Delegation shares:  1000000000000000000000
Delegation balance: 1000.0 STABLE

Subscribe to system transaction events

Some Stable SDK operations (unbonding completions, for example) don’t naturally emit EVM events. Stable closes this gap with system transactions: validator-generated transactions that call the StableSystem precompile to emit standard EVM events during the next block. To watch UnbondingCompleted, subscribe at the precompile address like any ERC-20 Transfer listener.
// watchUnbonding.ts
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://rpc.testnet.stable.xyz");

const STABLE_SYSTEM = "0x0000000000000000000000000000000000009999";

const stableSystem = new ethers.Contract(
  STABLE_SYSTEM,
  [
    "event UnbondingCompleted(address indexed delegator, address indexed validator, uint256 amount)",
  ],
  provider
);

stableSystem.on("UnbondingCompleted", (delegator, validator, amount, event) => {
  console.log("Unbonding completed for:", delegator);
  console.log("Amount:", ethers.formatEther(amount), "STABLE");
  console.log("Tx:", event.log.transactionHash);
});

console.log("Listening for UnbondingCompleted events...");
npx tsx watchUnbonding.ts
Listening for UnbondingCompleted events...
Unbonding completed for: 0xabcd...
Amount: 100.0 STABLE
Tx: 0x12ab...
For the full system-transaction mechanism and the filter-by-user / historical-query patterns, see Track unbonding completions.

Per-module references

Each precompile’s full method list, events, and authorization rules live in its reference page.

Track unbonding completions

Subscribe to the UnbondingCompleted event emitted via the StableSystem precompile.

System modules reference

Jump to the per-module ABI, method signatures, and event schemas.

System modules concept

Understand why Stable exposes SDK modules through precompiles.
Last modified on April 23, 2026