Skip to content
Protocol contracts not yet configured for this build
Enter app

Developers

Indexing staking positions

Positions are readable directly, so a simple integration needs no indexer at all.

Reading a wallet's positions

typescript
const ids = await client.readContract({  address: staking,  abi: gravityStakingAbi,  functionName: 'positionsOf',  args: [owner],}); const data = await client.multicall({  contracts: ids.flatMap((id) => [    {address: staking, abi: gravityStakingAbi, functionName: 'getPosition', args: [id]},    {address: staking, abi: gravityStakingAbi, functionName: 'pendingRewards', args: [id]},  ]),});
solidity
struct Position {    address owner;    uint256 amount;    uint256 weight;    uint64  createdAt;    uint64  unlockAt;    uint256 rewardDebt;}

Events

solidity
event Staked(address indexed owner, uint256 indexed positionId, uint256 amount, uint256 weight, uint64 unlockAt);event RewardsClaimed(address indexed owner, uint256 indexed positionId, uint256 amount);event Withdrawn(address indexed owner, uint256 indexed positionId, uint256 amount, uint256 rewards);event EmergencyWithdrawn(address indexed owner, uint256 indexed positionId, uint256 amount, uint256 forfeited);event RewardsNotified(uint256 amount, uint256 accRewardPerWeight, uint256 heldUndistributed);

Both owner and positionId are indexed, so filtering by wallet is cheap.

Computing pending rewards off chain

If you need pending amounts without an RPC round-trip per position:

text
pending = (weight × accRewardPerWeight) / 1e27 − rewardDebt

Track accRewardPerWeight from RewardsNotified, and rewardDebt from the position. Remember that rewardDebt is rewritten on every claim, so an index must apply RewardsClaimed events too.

The safest approach is to read pendingRewards(id) from chain; the formula above is for building a leaderboard, not for showing a user what they can withdraw.

Lifecycle state machine

text
Staked ──────► open   │             │   │             ├─ RewardsClaimed  (repeatable, position stays open)   │             │   │             ├─ Withdrawn       ──► closed  (matured; principal + rewards)   │             │   │             └─ EmergencyWithdrawn ──► closed  (principal only; rewards forfeited)

There is no partial withdrawal and no transfer. A position belongs to its opener until it closes.

Suggested schema

sql
CREATE TABLE position (  position_id   NUMERIC(78,0) PRIMARY KEY,  owner         BYTEA NOT NULL,  amount_wei    NUMERIC(78,0) NOT NULL,  weight_wei    NUMERIC(78,0) NOT NULL,  created_at    TIMESTAMPTZ NOT NULL,  unlock_at     TIMESTAMPTZ NOT NULL,  closed_at     TIMESTAMPTZ,  close_kind    TEXT CHECK (close_kind IN ('withdraw','emergency'))); CREATE INDEX ON position (owner) WHERE closed_at IS NULL;

Reconciling with protocol totals

text
Σ open position.amount == totalStakedΣ open position.weight == totalWeight

Both are asserted as invariants in the test suite. If your index disagrees with the contract, trust the contract.