reference

docs

Three read-only HTTP endpoints and one shared ES module. Everything on this page is taken from the source in this repository; the example responses were captured from a running server, so the numbers are old but the shapes are exact.

1 · overview

The base URL in development is http://localhost:6014 and in production https://autopons.ai. Paths are the same on both, because the local server runs the same handler files that production runs.

Every endpoint is read-only. Nothing here writes to a chain, holds a key or signs anything. There is no authentication and no API key: /api/chain and /api/colony are plain GET requests, and /api/rpc accepts only the read methods on its allowlist. Responses are JSON with content-type: application/json.

Honesty. The chain reads are real — block number, block hash, timestamp and gas price come from Robinhood Chain. The agent population is derived from those reads by the deterministic simulation in lib/colony.js. Those are not deployed contracts, and no address returned by this API belongs to anything on chain.

endpointmethodreturns
/api/chainGEThead-of-chain summary: block, gas price, chain id
/api/colonyGETthe block, the grid, the census and the roster
/api/rpcPOSTone JSON-RPC read, or a batch, forwarded upstream

2 · GET /api/chain

A small cacheable summary for the status strips. It takes no query parameters; anything you send is ignored. Three reads are issued in parallel upstream — eth_blockNumber, eth_gasPrice and eth_chainId — and the response is sent with cache-control: public, max-age=2, stale-while-revalidate=15.

fieldtypedescription
onlinebooleantrue when the head block, gas price and chain id all came back
chainIdnumber4663, the chain this site is configured for
chainIdHexstring"0x1237", the same id as hex
reportedChainIdnumberwhat the node answered to eth_chainId; online only
blocknumberhead block number, decimal; online only
hashstringhash of that block — the seed every colony on this site grows from; online only
timestampnumberblock timestamp, seconds since the epoch; online only
txCountnumbertransactions in that block; online only
gasUsednumbergas the block consumed; online only
gasLimitnumbergas the block allowed; online only
baseFeeWeistringEIP-1559 base fee in wei, or null if the block carries none; online only
gasWeistringgas price in wei, decimal digits; online only
gasGweinumberthe same price in gwei, four decimal places; online only
explorerstringbase URL of the block explorer
endpointsnumberhow many upstream RPC URLs are configured
errorstringwhy the read failed; present only when offline
# request
curl -s https://autopons.ai/api/chain

# 200, trimmed from a real response
{
  "online": true,
  "chainId": 4663,
  "chainIdHex": "0x1237",
  "reportedChainId": 4663,
  "block": 56136099,
  "hash": "0xebd8b10430b11b5e8ac2860f28b982fc528bcb26acf679d73634327567e12a80",
  "timestamp": 1788713682,
  "txCount": 9,
  "gasUsed": 3033796,
  "gasLimit": 1125899906842624,
  "baseFeeWei": "372126000",
  "gasWei": "367586000",
  "gasGwei": 0.3676,
  "explorer": "https://robinhoodchain.blockscout.com",
  "endpoints": 2
}

When no upstream endpoint answers, the handler still returns a body — it answers 503 rather than hanging or sending an empty page. Treat a 503 as a normal outcome and render the offline state.

# 503 — the shape when the chain is unreachable
{
  "online": false,
  "chainId": 4663,
  "chainIdHex": "0x1237",
  "explorer": "https://robinhoodchain.blockscout.com",
  "endpoints": 2,
  "error": "upstream timed out"
}

3 · GET /api/colony

Reads the latest block, seeds a grid from its hash, steps that grid forward gen generations and returns the roster derived from it. Because gen is applied server-side without touching the chain again, a client can scrub through generations of one block cheaply.

Every parameter is parsed with parseInt and clamped. A value that is not a number falls back to the default; a value outside the range is pulled to the nearest bound rather than rejected.

paramtypedefaultrangedescription
winteger248 – 64grid width in cells
hinteger146 – 48grid height in cells
geninteger00 – 512generations stepped forward from the seed
limitinteger641 – 256maximum agents returned, in row-major order
fieldtypedescription
sourcestring"chain" when the block was read, "genesis" when it was not
chain.idnumber4663
chain.explorerstringbase URL of the block explorer
block.numbernumberblock the colony was seeded from; 0 on genesis
block.hashstringthe seed itself, 0x-prefixed
block.timestampnumberunix seconds; 0 on genesis
block.txCountnumbertransactions in that block
block.gasUsednumbergas used by that block
grid.wnumberwidth actually used, after clamping
grid.hnumberheight actually used, after clamping
grid.gennumbergenerations actually stepped
grid.populationnumberlive cells at generation gen
grid.seedPopulationnumberlive cells at generation 0
grid.fingerprintstring0x-prefixed uint32 hash of this generation
grid.cellsstringw×h characters of "0" and "1", row-major
census.alivenumberagents in state alive, within limit
census.replicatingnumberagents in state replicating
census.starvingnumberagents in state starving
census.crowdednumberagents in state crowded
census.totalnumberagents returned, equal to agents.length
agentsarraythe roster; fields below
agent fieldtypedescription
idstring"apx" plus six hex characters, stable for a cell in a block
namestringtwo words, derived from the seed, the cell index and the generation
rolestringrole id, one of the eight in ROLES
roleLabelstringthe role's display label
taskstringone sentence describing what the role does
statestringalive, replicating, starving or crowded
cell[number, number]grid position as [x, y]
neighboursnumberlive neighbours, 0–8, on the wrapped grid
energynumber4–100; 100 while replicating, otherwise distance from three neighbours
generationnumberthe gen this roster was read at
bornBlocknumberblock.number minus ageBlocks, never below 0
ageBlocksnumberblocks since birth; 0 while replicating
txCountnumberderived transaction count
gasSpentnumberderived gas total, six decimal places
addressstringderived 0x address, simulated, not deployed
# request
curl -s 'https://autopons.ai/api/colony?w=24&h=14&gen=0&limit=3'

# 200, trimmed: cells shortened, one agent of three shown
{
  "source": "chain",
  "chain": { "id": 4663, "explorer": "https://robinhoodchain.blockscout.com" },
  "block": {
    "number": 56098398,
    "hash": "0x32114445caca441a600e7e81a020c0a3f7783aa8ff71e30c1adfef1d68359c62",
    "timestamp": 1788709880,
    "txCount": 9,
    "gasUsed": 1789688
  },
  "grid": {
    "w": 24, "h": 14, "gen": 0,
    "population": 96,
    "seedPopulation": 96,
    "fingerprint": "0x7bf7d786",
    "cells": "000000110100101000001100110000000011000000010001…"
  },
  "census": { "alive": 0, "replicating": 2, "starving": 1, "crowded": 0, "total": 3 },
  "agents": [
    {
      "id": "apx5c4a8d",
      "name": "marrow-cascade",
      "role": "router",
      "roleLabel": "router",
      "task": "splits an order across venues and settles the remainder",
      "state": "replicating",
      "cell": [2, 0],
      "neighbours": 3,
      "energy": 100,
      "generation": 0,
      "bornBlock": 56098398,
      "ageBlocks": 0,
      "txCount": 221,
      "gasSpent": 0.161749,
      "address": "0x261810edc9b3b83e93ffe72640f89a0a3db9e734"
    }
  ]
}

If the chain read fails the endpoint still answers 200. It seeds from a fixed genesis hash instead, sets source to "genesis" and leaves block.number and block.timestamp at 0. Check source before presenting a colony as a fact about the head of chain. An unexpected server-side failure answers 500 with { "error": "…" }.

4 · GET /api/work

Deploying an agent, and reading the ones that exist. An agent is a contract that holds its own balance and carries its own task; it is alive exactly while it can pay, which is the argument the rest of this site makes. Pay a small fee to the factory and everything you send above that fee becomes the new agent's opening balance.

Two things hold throughout. Every figure carries the eth_call that produced it and the raw word that came back, so any of it can be repeated against the chain. And nothing signs: deploy is calldata, this repository holds no key, /api/rpc refuses write methods, and the agent you make is owned by you — the factory keeps no power over it once it exists.

query

parameterdefaultaccepted
addressnonea 0x-address, which adds the you block. Anything else is ignored
limit241 to 64 agents, newest last; anything else is clamped
namequiet-ledgerseeds the worked deploy example
taskkeep an index warmseeds the worked deploy example
kind00-4, matching the contract's Kind enum

response

fieldtypedescription
chainobject4663 and the explorer these addresses can be checked on
blockobjectnumber, hash and timestamp of the block every figure was read at
gasobjectwei and gwei at that block
factoryobjectthe agent factory: deployed, and either its fee, treasury and count, or a reason it is not up
kindsarraythe five task kinds, in the order of the contract's Kind enum
deployobjecta worked deploy call: to, signature, data, note
gasBudgetobjectthe gas a deploy and a single action are budgeted at
agentsarrayevery agent the factory has made, newest last
alivenumberhow many of them still hold a balance
youobjectpresent only with a valid address: that wallet's balance, nonce and what it can afford
notestringa plain statement that nothing here signs, and who owns the agent

an agent

fieldtypedescription
addressstringthe agent contract
namestringwritten by its owner at deploy; treat as hostile and escape it
taskstringwhat it was told to do; also owner-written
kindstringmcp, trade, index, keeper or watch
ownerstringwho deployed it, and the only account that can make it act
balanceWeistringwhat it has left, as decimal wei
alivebooleanexactly balanceWei > 0
runwaynumberhow many more actions it can pay for at the current gas price
actionsnumberhow many times it has acted
bornBlocknumberthe block it was deployed in
lastBeatnumberblock of its last owner-driven action
statestringalive, starving or crowded — the colony's states, applied to a real balance
readsarraythe calls behind the figures above
# request
curl -s 'https://autopons.ai/api/work'

# trimmed from a real response at block 56664479
{
  "factory": {
    "address": null,
    "deployed": false,
    "reason": "the agent factory is not deployed yet"
  },
  "gasBudget": {
    "perAction": 120000,
    "perDeploy": 900000
  },
  "alive": 0,
  "deploy": {
    "to": null,
    "signature": "deploy(string,string,uint8)",
    "data": "0xa105fa73000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c71756965742d6c6564676572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000126b65657020616e20696e646578207761726d0000000000000000000000000000",
    "note": "the fee goes to the treasury and everything above it becomes the agent's balance. You own the agent; the factory keeps no power over it."
  },
  "kinds": [
    {
      "id": 0,
      "key": "mcp",
      "label": "MCP",
      "what": "answers over the Model Context Protocol, and pays its own way"
    },
    {
      "id": 1,
      "key": "trade",
      "label": "trade",
      "what": "quotes a pool and swaps within a band its owner sets"
    },
    "\u2026"
  ],
  "agents": []
}

A contract cannot wake itself up, so an agent is an account with a task rather than a process. Its owner signs when it should act, and the gas comes out of the agent's own balance through execute. That is the honest shape of the idea: the money and the identity live on chain, the decision to act does not.

When the chain cannot be reached the endpoint answers 503 with an error and an empty agents array. When the factory has not been broadcast yet, factory.deployed is false and factory.reason says so — the endpoint still returns everything else, because a page that cannot tell you why it is empty is worse than one that can.

6 · lib/colony.js

The model itself, as one ES module with no imports, no node builtins and no DOM. The server loads it from api/colony.js and the browser loads the same file over /lib/colony.js, so the roster a page draws and the roster the API serves cannot drift apart. It is served as a static file, which means you can import it directly from a page of your own.

exportsignaturereturns
rngrng(seed: number)a function of no arguments giving the next float in [0, 1); the same seed gives the same stream on every runtime
fnv1afnv1a(str: string)a uint32 hash of the string; used to fold a block hash into a seed
hexBodyhexBody(hash: string)the hash lowercased and trimmed with any 0x prefix removed
RULE{ born: [3], survive: [2, 3] }B3/S23 as data, read by step and classify
seedGridseedGrid(hash, w, h, density = 0.32)a Uint8Array of w×h zeroes and ones, row-major, seeded from the hash
neighboursneighbours(g, w, h, x, y)the live neighbour count, 0–8, on a toroidal grid — the colony wraps and has no edge
stepstep(g, w, h, out?)the next generation as a Uint8Array; writes into out when one of the right length is passed
classifyclassify(g, w, h)an Array of w×h strings: alive, replicating, starving, crowded or empty — why a cell is about to change, not only that it will
populationpopulation(g)the number of live cells
fingerprintfingerprint(g)a 0x-prefixed uint32 string identifying a generation, for cycle detection and display
addressForaddressFor(id, chainSeed)a deterministic 0x address, 40 hex characters; simulated, never deployed
rosterroster({ blockHash, blockNumber = 0, w = 24, h = 14, gen = 0, limit = 64, density = 0.32 })an array of agent objects with the fields listed in section 3, read in row-major order and capped at limit
censuscensus(agents){ alive, replicating, starving, crowded, total } counted from a roster
ROLESArray of { id, label, task }the eight roles an agent can take
STATES{ [state]: { label, hint } }the four agent states and the one-line reason for each
// worked example — same rule as the API, no network beyond one read
import { seedGrid, step, classify, population, fingerprint,
         roster, census } from '/lib/colony.js';

const head = await fetch('/api/chain').then(r => r.json());
const hash = '0x32114445caca441a600e7e81a020c0a3f7783aa8ff71e30c1adfef1d68359c62';
const w = 24, h = 14;

let g = seedGrid(hash, w, h);        // generation 0
g = step(g, w, h);                    // generation 1

population(g);                        // -> 98 live cells at generation 1
fingerprint(g);                       // -> '0x…' identifies this generation
classify(g, w, h)[0];                 // -> 'alive' | 'replicating' | 'starving' | 'crowded' | 'empty'

const agents = roster({ blockHash: hash, blockNumber: head.block, w, h, gen: 1, limit: 8 });
census(agents);                       // -> { alive, replicating, starving, crowded, total }

7 · the rule

An agent is a cell. The block hash supplies the seed and B3/S23 supplies the biology. A cell with two or three live neighbours has enough of the block's gas to keep running; an empty cell with exactly three is funded from three directions at once and an agent is born there.

neighbourscellstatewhat it means
2 – 3livesalivefunded, running
exactly 3, emptybornreplicatinga child is being funded here
under 2diesstarvingunderfunded, halts next block
over 3diescrowdedpriced out of the block, halts

Nothing in the roster is random. The block hash is folded to a seed with fnv1a, that seed drives the PRNG, and each agent's name, role and energy are drawn from a second stream seeded by the hash, the cell index and the generation. Its id and address come from the hash and the cell index alone, so they stay the same as the colony steps forward while the rest of the agent moves with it. Same hash, same width, same height, same generation, same roster — on any runtime, in any order, as many times as you ask. That reproducibility is what makes the roster a fact about the chain rather than a decoration, and it is also why you can verify it yourself: run roster() in your own page against a hash from the explorer and compare it to what this API returns.

8 · limits and honesty

The agent population is derived. Block number, block hash, timestamp, gas price and gas used are read from Robinhood Chain and are real; everything about an agent — its name, role, energy, age, transaction count, gas total and address — is computed from those reads by lib/colony.js. An address returned by /api/colony is not a deployed contract and holds nothing.

No endpoint here can send anything: the RPC route allows reads only and the server holds no key. $AUTOPONS (0x4dBb51a72ab680DadDEA5Eb6A1e6c43552e9a5EE) is on the ponsfamily bonding curve and is linked in the footer — it is the only buyable thing on this site, it is not returned by any endpoint, and holding it grants no claim on any agent. Agents are funded in native ETH and owned by their deployer.

The endpoints are read-only and their rate is the upstream's rate. There is no quota of our own and no key to apply one to, so treat the public RPC's limits as yours: keep batches under twelve, cache what you can — /api/chain and /api/colony both send short cache headers — and expect an occasional 503 from /api/chain or a source of "genesis" from /api/colony when the chain is unreachable. Both are normal, and both are documented above rather than hidden.