Use system modules
Stable exposes protocol-level logic through precompiled contracts at fixed addresses. You can call these precompiles from Solidity or ethers.js instead of reimplementing Stable SDK behavior in application contracts.
What's exposed
| Module | Precompile address | Use for |
|---|---|---|
| Bank | 0x0000000000000000000000000000000000001003 | STABLE token transfers and balance operations |
| Distribution | 0x0000000000000000000000000000000000000801 | Claiming staking rewards, reward queries, commission management |
| Staking | 0x0000000000000000000000000000000000000800 | Delegation, undelegation, redelegation, validator queries |
| Gov | 0x0000000000000000000000000000000000000805 | Proposals, tally results, and on-chain vote records |
| Slashing | 0x0000000000000000000000000000000000000806 | Validator signing info and uptime |
| StableSystem | 0x0000000000000000000000000000000000009999 | Guaranteed blockspace lane queries and EVM event emission for system transactions |
Read methods are callable from any EVM contract or off-chain client. Some state-changing methods enforce caller authorization, and notifySystemTxLogs() accepts only protocol-generated system transactions. The addresses are 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.
// SAFE: the precompile address is fixed on Stable.
// 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.
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.tsDelegation shares: 1000000000000000000000
Delegation balance: 1000.0 STABLESubscribe 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, uint64 indexed originBlockHeight, uint256 amount)",
],
provider
);
stableSystem.on("UnbondingCompleted", (delegator, validator, originBlockHeight, amount, event) => {
console.log("Unbonding completed for:", delegator);
console.log("Origin block:", originBlockHeight.toString());
console.log("Amount:", ethers.formatEther(amount), "STABLE");
console.log("Tx:", event.log.transactionHash);
});
console.log("Listening for UnbondingCompleted events...");npx tsx watchUnbonding.tsListening for UnbondingCompleted events...
Unbonding completed for: 0xabcd...
Origin block: 36975999
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.
- Bank precompile: STABLE token transfers and supply queries.
- Distribution precompile: reward claims and commission.
- Staking precompile: delegate, undelegate, redelegate, validator queries.
- System transactions: StableSystem event format and authorization.
Where to go next
- 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.

