Introduction

What is InferStake?

InferStake is an AI-powered native token staking protocol deployed on Ritual Chain (Chain ID 1979). Users stake native RITUAL tokens directly from their wallet and earn yield at a configurable APY — currently 15% annualised.

What makes InferStake unique is its integration with Ritual Infernet — an on-chain AI inference layer. Using the 0x080C precompile address, the protocol can submit staking data to off-chain AI nodes and receive cryptographically verifiable advisory results without leaving the chain.

Native Token

InferStake uses native RITUAL — no ERC-20 approval step needed. Just send RITUAL with your transaction and you're staked.

The contract is immutable (no proxy pattern), open-source, and all reward calculations are executed deterministically on-chain — every number you see in the dashboard is computed directly from contract state.

Getting Started

Quick Start

1
Add Ritual Chain to MetaMask
Open MetaMask → Add Network → fill in: Network Name: Ritual Chain · RPC: https://rpc.ritualfoundation.org · Chain ID: 1979 · Symbol: RITUAL · Explorer: https://explorer.ritualfoundation.org
2
Get RITUAL tokens
Obtain native RITUAL from the faucet or bridge. Ensure your wallet has at least 0.01 RITUAL (minimum stake) plus a small amount for gas fees.
3
Connect wallet
Go to /app and click "Connect Wallet". InferStake will auto-detect your chain — if you're not on Ritual Chain, it will prompt you to switch.
4
Stake RITUAL
Enter the amount you wish to stake and click "Stake RITUAL →". Your RITUAL is sent as msg.value — no approve() step needed. Rewards begin accruing immediately.
5
Claim rewards
Your claimable rewards update in real-time in the dashboard. Click "Claim →" at any time to withdraw accrued yield to your wallet.
6
Request AI Advisory
In the AI Advisor panel, click "Request AI Advisory" to submit your staking data to the Infernet network and receive a personalised recommendation.
Economics

How Yield Works

InferStake uses a simple, transparent, time-weighted reward formula. There are no lock-up periods, no vesting schedules, and no compounding complexity — rewards accrue every second and can be claimed at any time.

Reward Formula

stakedAmount × apyBasisPoints × timeElapsed
10,000 × 365 days (in seconds)
Where apyBasisPoints = 1500 means 15.00% APY

Example Calculation

Staking 100 RITUAL for 30 days at 15% APY:

Reward calculation
staked     = 100 RITUAL
APY        = 15%  →  1500 basis points
timeElapsed = 30 days  →  2,592,000 seconds

reward = (100 × 1500 × 2,592,000) / (10,000 × 31,536,000)
       = 388,800,000,000 / 315,360,000,000
       ≈ 1.2328 RITUAL

Reward Accrual

Rewards accrue continuously and are snapshotted into accruedRewards whenever you stake more or unstake. This prevents reward dilution — your earned yield is never lost when changing your position.

Reward Reserve

The contract owner pre-funds a native RITUAL reserve via fundRewardReserve(). When you claim, rewards come from this reserve. If the reserve is insufficient, claims will revert — check rewardReserve() on the contract before claiming.

APY Updates

The owner can update the APY via setAPY(newApyBps). APY changes affect future accrual only — already-accrued rewards are always preserved in accruedRewards.

Ritual Infernet

AI Advisor

InferStake integrates with Ritual's Infernet network — a decentralised AI inference layer built into Ritual Chain. The 0x080C precompile address exposes a requestCompute() function that submits AI jobs to off-chain Infernet nodes.

How it works

1
User calls requestAIAdvisory()
The contract encodes your staked amount, accrued rewards, APY, and time staked into an ABI-encoded payload.
2
Payload sent to Infernet (0x080C)
The IInfernetCoordinator precompile receives the payload and emits it to the Infernet P2P network.
3
Off-chain node runs the model
An Infernet node runs the inferstake-advisor-v1 model against your payload. The model analyses your position and generates a recommendation.
4
Result returned via callback
The node delivers the result via an on-chain callback or an off-chain indexer. The dApp reads the result and displays it in the AI Advisor panel.

Infernet payload structure

requestAIAdvisory() — payload encoding
bytes memory payload = abi.encode(
    msg.sender,                          // address: user
    info.stakedAmount,                   // uint256: wei staked
    info.accruedRewards + pendingRewards, // uint256: total claimable wei
    apyBasisPoints,                       // uint256: e.g. 1500 = 15%
    block.timestamp - info.stakeTimestamp // uint256: seconds staked
);

IInfernetCoordinator(0x000...080C).requestCompute(
    "inferstake-advisor-v1",  // model ID
    payload,
    200_000                   // callback gas limit
);
Verifiable AI

Every Infernet result is tied to a unique requestId emitted in the AIAdvisoryRequested event. You can verify the computation on Ritual's Infernet explorer using this ID.

Solidity

Contract Reference

Contract Address

0x6e4463b62a2e332b007573dbe7f0Bf3B784B4838

Functions

stake()payable
Stake native RITUAL. Send RITUAL as msg.value. No approve() needed. Rewards begin accruing immediately.
unstake(uint256 amount)write
Withdraw staked RITUAL. Rewards are snapshotted to accruedRewards first. Call claimRewards() separately.
claimRewards()write
Claim all accrued native RITUAL rewards. Sends from the reward reserve. Reverts if reserve is insufficient.
requestAIAdvisory()write
Submit staking data to the Ritual Infernet precompile (0x080C). Returns a requestId.
getStakedBalance(address user)view
Returns the current staked balance (wei) for any address.
getRewards(address user)view
Returns total claimable rewards (accrued + live pending) in wei.
getUserDashboard(address user)view
Returns all dashboard data in a single call: stakedAmount, totalRewards, apyBps, timeStakedSeconds, lastAiRequestId.
getProtocolStats()view
Returns protocol-wide stats: totalStaked, totalStakers, totalAiQueries, apyBasisPoints.
contractBalance()view
Returns total native RITUAL held by the contract (staked + reserve).
fundRewardReserve()owner
Owner only. Fund the reward reserve by sending native RITUAL as msg.value.
setAPY(uint256 newApyBps)owner
Owner only. Update APY. 1500 = 15%, max 10000 = 100%.
setMinStakeAmount(uint256 amount)owner
Owner only. Update the minimum stake threshold (wei).
setAIModelId(string newModelId)owner
Owner only. Update the Infernet model identifier string.
transferOwnership(address newOwner)owner
Owner only. Transfer contract ownership to a new address.
emergencyWithdraw(uint256 amount)owner
Owner only. Emergency native RITUAL withdrawal. Use only for migrations.

Full ABI

[
  {
    "name": "stake",
    "type": "function",
    "stateMutability": "payable",
    "inputs": [],
    "outputs": []
  },
  {
    "name": "unstake",
    "type": "function",
    "stateMutability": "nonpayable",
    "inputs": [
      {
        "name": "amount",
        "type": "uint256"
      }
    ],
    "outputs": []
  },
  {
    "name": "claimRewards",
    "type": "function",
    "stateMutability": "nonpayable",
    "inputs": [],
    "outputs": []
  },
  {
    "name": "requestAIAdvisory",
    "type": "function",
    "stateMutability": "nonpayable",
    "inputs": [],
    "outputs": [
      {
        "name": "requestId",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "getStakedBalance",
    "type": "function",
    "stateMutability": "view",
    "inputs": [
      {
        "name": "user",
        "type": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "getRewards",
    "type": "function",
    "stateMutability": "view",
    "inputs": [
      {
        "name": "user",
        "type": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "getUserDashboard",
    "type": "function",
    "stateMutability": "view",
    "inputs": [
      {
        "name": "user",
        "type": "address"
      }
    ],
    "outputs": [
      {
        "name": "stakedAmount",
        "type": "uint256"
      },
      {
        "name": "totalRewards",
        "type": "uint256"
      },
      {
        "name": "apyBps",
        "type": "uint256"
      },
      {
        "name": "timeStakedSeconds",
        "type": "uint256"
      },
      {
        "name": "lastAiRequestId",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "getProtocolStats",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "_totalStaked",
        "type": "uint256"
      },
      {
        "name": "_totalStakers",
        "type": "uint256"
      },
      {
        "name": "_totalAiQueries",
        "type": "uint256"
      },
      {
        "name": "_apyBasisPoints",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "contractBalance",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "fundRewardReserve",
    "type": "function",
    "stateMutability": "payable",
    "inputs": [],
    "outputs": []
  },
  {
    "name": "setAPY",
    "type": "function",
    "stateMutability": "nonpayable",
    "inputs": [
      {
        "name": "newApyBps",
        "type": "uint256"
      }
    ],
    "outputs": []
  },
  {
    "name": "apyBasisPoints",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "totalStaked",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "owner",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address"
      }
    ]
  },
  {
    "name": "rewardReserve",
    "type": "function",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "Staked",
    "type": "event",
    "inputs": [
      {
        "indexed": true,
        "name": "user",
        "type": "address"
      },
      {
        "name": "amount",
        "type": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "Unstaked",
    "type": "event",
    "inputs": [
      {
        "indexed": true,
        "name": "user",
        "type": "address"
      },
      {
        "name": "amount",
        "type": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "RewardsClaimed",
    "type": "event",
    "inputs": [
      {
        "indexed": true,
        "name": "user",
        "type": "address"
      },
      {
        "name": "amount",
        "type": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256"
      }
    ]
  },
  {
    "name": "AIAdvisoryRequested",
    "type": "event",
    "inputs": [
      {
        "indexed": true,
        "name": "user",
        "type": "address"
      },
      {
        "name": "requestId",
        "type": "uint256"
      }
    ]
  }
]

ethers.js Integration

Connect and stake with ethers.js
import { ethers } from "ethers";

const CONTRACT = "0x6e4463b62a2e332b007573dbe7f0Bf3B784B4838";
const ABI = [
  "function stake() external payable",
  "function unstake(uint256 amount) external",
  "function claimRewards() external",
  "function getUserDashboard(address user) view returns (uint256,uint256,uint256,uint256,uint256)",
  "function getProtocolStats() view returns (uint256,uint256,uint256,uint256)",
];

// Connect
const provider = new ethers.BrowserProvider(window.ethereum);
const signer   = await provider.getSigner();
const contract = new ethers.Contract(CONTRACT, ABI, signer);

// Stake 10 RITUAL (native token — no approve needed)
const tx = await contract.stake({ value: ethers.parseEther("10") });
await tx.wait();

// Read dashboard
const [staked, rewards, apyBps, timeSecs] = await contract.getUserDashboard(userAddress);
console.log("Staked:", ethers.formatEther(staked), "RITUAL");
console.log("Rewards:", ethers.formatEther(rewards), "RITUAL");
console.log("APY:", Number(apyBps) / 100, "%");

// Unstake
await contract.unstake(ethers.parseEther("5"));

// Claim
await contract.claimRewards();
Infrastructure

Network & RPC

ParameterValue
Network NameRitual Chain
Chain ID1979
CurrencyRITUAL (18 decimals)
Block Time~350ms
RPC (HTTP)https://rpc.ritualfoundation.org
RPC (WebSocket)wss://rpc.ritualfoundation.org/ws
Explorerhttps://explorer.ritualfoundation.org
Contract0x6e4463b62a2e332b007573dbe7f0Bf3B784B4838
Infernet Coord.0x000000000000000000000000000000000000080C

MetaMask — Add Ritual Chain

wallet_addEthereumChain
await window.ethereum.request({
  method: "wallet_addEthereumChain",
  params: [{
    chainId:  "0x7BB",   // 1979 in hex
    chainName: "Ritual Chain",
    nativeCurrency: {
      name: "RITUAL", symbol: "RITUAL", decimals: 18
    },
    rpcUrls:            ["https://rpc.ritualfoundation.org"],
    blockExplorerUrls:  ["https://explorer.ritualfoundation.org"],
  }],
});
Support

FAQ