Protocol

How JOULE moves

Five contracts and one gateway. Everything an app or an autonomous agent needs is a public function call or an HTTP request, with no account and no off-chain approval. Machine-readable copies live at /agents.md and /llms.txt.

Lifecycle

  1. 01VOLTStakeVOLT holders lock into VoltStaking. Longer locks carry more weight.
  2. 02mintEarn JOULEEach finalized hour, JOULE is minted to stakers pro rata to weight.
  3. 03GridTradeHolders list JOULE on the Grid at 0 to 90% off, or trade it on a DEX.
  4. 04burnActivateBuyers burn JOULE into a non-transferable balance keyed to an address.
  5. 05APICall modelsThe gateway reads that balance and meters every request at list price.

Pricing rule

91 levels, 0 to 90% off. Buys walk from the deepest level down; within a level, orders fill first-in, first-out. A buy touches at most 64 orders.

Fees

A protocol fee is added on top of what sellers receive on buy, and part of it can go to a referrer. buyAndActivate skips it; only the JOULE activation fee applies.

Sellers are paid by pull

Fills accrue to proceedsOf. Claim USDG, or swap to VOLT in the same call with a minimum-out guard.

Agents

Integrate in four calls

Fund, activate, derive a key, call a model. An agent with a wallet can do all of it without a human.

1 · Buy on-chain (Solidity)

AgentTopUp.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IERC20 { function approve(address, uint256) external returns (bool); }

interface IVoltExchange {
    struct Quote { uint256 creditOut; uint256 usdgSpent; uint256 feeAtoms; uint256 fills; uint8 reason; }
    function getQuoteForCredit(uint256 creditOut, uint256 maxFills) external view returns (Quote memory);
    function getActivationQuote(uint256 usdgIn, uint256 maxFills)
        external view returns (Quote memory q, uint256 creditedAtoms, uint256 activationFeeAtoms);
    function buy(uint256 usdgIn, uint256 minCreditOut, address recipient, uint256 maxFills)
        external returns (uint256 creditOut, uint256 usdgSpent);
    function buyAndActivate(uint256 usdgIn, uint256 minCreditOut, bytes32 beneficiary, uint256 maxFills)
        external returns (uint256 creditOut, uint256 usdgSpent, uint256 activationId);
}

contract AgentTopUp {
    IVoltExchange public immutable grid;
    IERC20 public immutable usdg;

    constructor(IVoltExchange grid_, IERC20 usdg_) { grid = grid_; usdg = usdg_; }

    /// Buy `credit` JOULE (6 decimals) into this contract.
    /// Pass `minOut` from an off-chain quote: a same-tx quote gives no slippage protection.
    function topUp(uint256 credit, uint256 minOut) external {
        IVoltExchange.Quote memory q = grid.getQuoteForCredit(credit, 64);
        uint256 budget = q.usdgSpent + q.feeAtoms;      // fee is added on top of seller price
        usdg.approve(address(grid), budget);
        grid.buy(budget, minOut, address(this), 64);
    }

    /// Spend `usdgIn` and credit `agent`'s API balance directly (no fee on this path).
    function fundAgent(uint256 usdgIn, uint256 minOut, address agent) external {
        usdg.approve(address(grid), usdgIn);
        grid.buyAndActivate(usdgIn, minOut, bytes32(uint256(uint160(agent))), 64);
    }
}

1b · Or from TypeScript

buy.ts
import { createWalletClient, createPublicClient, http, pad } from "viem";
import { VoltExchangeAbi } from "./abi/VoltExchange";

const credit = 25_000_000n; // $25 of inference (6 decimals)
const q = await pub.readContract({
  address: EXCHANGE, abi: VoltExchangeAbi,
  functionName: "getQuoteForCredit", args: [credit, 64n],
});
const budget = q.usdgSpent + q.feeAtoms;
// approve USDG for `budget`, then:
await wallet.writeContract({
  address: EXCHANGE, abi: VoltExchangeAbi, functionName: "buy",
  args: [budget, (q.creditOut * 995n) / 1000n, account.address, 64n],
});

2 · Derive the API key

key.ts
import { privateKeyToAccount } from "viem/accounts";

// The agent's own wallet. Load it from your secret store, never from source.
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);

const epoch = 1; // bump to rotate: the first use of a higher epoch revokes older keys
const message = `Voltio API key · chain 4663 · epoch ${epoch}`;
const signature = await account.signMessage({ message });

// sk-volt-<epoch>-<base64(signature bytes)>
const apiKey = `sk-volt-${epoch}-${Buffer.from(signature.slice(2), "hex").toString("base64")}`;

3 · Call a model

shell
curl https://voltiodotso.app/api/v1/chat/completions \
  -H "Authorization: Bearer $VOLTIO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-sonnet-5",
    "messages": [{"role": "user", "content": "Plan my next three tool calls."}],
    "stream": false
  }'

4 · Check the balance

shell
curl https://voltiodotso.app/api/v1/key \
  -H "Authorization: Bearer $VOLTIO_KEY"

# {
#   "object": "key",
#   "address": "0xAgent…",
#   "balance": { "currency": "USD", "available": "41.27", "used": "8.73", "credited": "50.00" },
#   "rate_limit": { "requests_per_minute": 120, "concurrent": 8 }
# }
The signed message must match exactly: Voltio API key · chain 4663 · epoch 1 (with your epoch). Beneficiaries are 32-byte left-padded addresses: pad(address, { size: 32 }).
Deployments

Contracts on Robinhood Chain

Not deployed on this network yet. Addresses will appear here at launch.

ContractRoleAddress
JOULECredit token (ERC-20, 6 dp) · activationnot deployed
VoltExchangeThe Grid · order book, quotes, affiliatesnot deployed
VoltStakingVOLT staking, hourly JOULE distributionnot deployed
VoltPayoutUSDG → VOLT payout routenot deployed
VOLTStaking token (ERC-20, 18 dp)launching on pons
USDGPayment stablecoin (6 dp)0x5fc5360d0400a0fd4f2af552add042d716f1d168 ↗
Network
Robinhood Chain
Chain ID
4663
RPC
https://rpc.mainnet.chain.robinhood.com
Explorer
Blockscout ↗
Deploy block
0
Gateway base URL
/api/v1