Your backend for Aptos.

Live tables, an API and a change feed, built from everything your contract wrote — not just the events it announced.

Events, resources and the entries inside a SmartTable, folded into state your frontend can query and your backend can subscribe to. On the Aptos infrastructure you already know.

Events and stateas the chain commits them
  • v20,670,490Soldoak chair225
  • v20,670,453Soldiron kettle912
  • v20,670,416Soldwool rug719
  • v20,670,379Soldbrass lamp526
Your reducermarket.nineveh.ts
on(sold, (s) => {
const row = sellers.row(s.seller)
row.sold += 1
row.revenue += s.price - s.fee
})

225 - 5 → revenue 220

Application statesellers · key seller
sellersoldrevenue
0x7a3f…c41d1515
0x1e87…8d2a1705
0x9b02…4f771894
0x6146…e5541220

One row per seller, queryable over REST.

Every sale the contract emits, the four lines that fold it, and the table your app queries.
#problem

Aptos gives you the primitives. The application backend is still yours.

Aptos already provides the infrastructure for reading and indexing on-chain data: RPC for point reads, the hosted Indexer for common assets, the Transaction Stream for everything a transaction touched.

Turning that into the backend your frontend needs is still application logic, state management, database work and server code. And a contract announces less than it stores. It emits events for what it expected you to want, and keeps the rest in storage because storage is what it needs to work: the SmartTable of open positions, the running sum it keeps for itself, the vote tally no event is fired for.

Read only the events and that half is invisible — not missing, invisible, which is worse, because what you do get still looks plausible.

So it gets assembled the same way in every project: a processor, a database, a server, a deploy pipeline. A week of work, and something to maintain forever. Nineveh simplifies the layer in the middle.

  • Move contracts
  • Events and state changes
  • RPC · Indexer · Transaction Stream
NINEVEH
  • Application logic
  • Database
  • API
  • Your frontend
Nineveh simplifies the layer in the middle.

#layers

Built on Aptos. Not around it.

Aptos already provides the infrastructure for accessing chain data and building custom indexing pipelines. Nineveh sits one layer above it. Instead of every application developer assembling the same backend logic around those primitives, Nineveh gives you a simpler way to define and serve application-specific state.

Your applicationweb, mobile, agents
Ninevehapplication backend
Aptos data infrastructureRPCIndexerTransaction Stream
Aptosconsensus, finality, storage
Move contractsyour source of truth
Nineveh adds a layer. It doesn't replace the layers underneath it.

A familiar backend experience for Aptos.

Web developers have familiar abstractions for building application backends. Nineveh brings that simplicity to Aptos applications, while keeping Aptos as the source of truth.

#how

From on-chain state to application state.

Your contract is the source of truth. A reducer says what to do when something arrives: when this event lands or this resource changes, that row changes. No processor to write, no migrations, no schema to keep in step.

market.nineveh.ts
export const sellers = table({
key: { seller: address },
columns: { sold: u64.default(0), revenue: u64.default(0) },
})
on(sold, (s) => {
const row = sellers.row(s.seller)
row.sold += 1
row.revenue += s.price - s.fee
})

That is the whole language: on, a row, an assignment, delete, if and return. It reads like TypeScript, but nothing is executed: it compiles to a fold that replays the same way every time.

your API, a second later
GET /v1/tables/sellers?order=revenue.desc&limit=2
{ "rows": [
{ "seller": "0x7a3f…c41d",
"sold": "128",
"revenue": "94210" },
{ "seller": "0x1e87…8d2a",
"sold": "91",
"revenue": "63880" }
],
"count": 4 }

Wide integers come back as strings, because a u128 doesn't fit a JavaScript number. Every table gets the same treatment.

Ask how things are. Get told when they change.

A query answers the first question, and REST over your tables is how you ask it. But a frontend that has to stay current can only keep asking: on a timer, diffing what comes back, guessing at the interval.

The change feed turns that around. Every row a reducer writes leaves the same commit as an event, in commit order, down a connection you already have open. Your app finds out because something happened, not because it checked.

  • One commit. Rows and the feed are written in a single transaction, so a change can't be announced that isn't in the table, and a row can't land without saying so.
  • Resumable. Each event's id is its version and sequence. A browser that reconnects sends Last-Event-ID and carries on. Nothing missed, nothing twice.
  • Or pushed to your server. The same changes as signed webhooks, each endpoint with its own secret and its own cursor, retried until they land.
GET /v1/changes?tables=sellers
id: 20670379.0sellersupdate
id: 20670416.0sellersupdate
id: 20670453.0sellersupdate
id: 20670490.0
event: change
data: { "version": "20670490", "seq": 0
"table": "sellers", "op": "update"
"key": { "seller": "0x6146…e554" }
"row": { "sold": "1",
"revenue": "220" } }

Describe the data you want. Get the API.

  1. 1

    Define your application state

    Describe the tables your product needs, with their keys, columns and types, rather than building an indexing pipeline from scratch.

  2. 2

    Follow everything the chain wrote

    Not just events: resource changes and the entries inside a table, read from the write set — including the state your contract never emitted an event about.

  3. 3

    Derive useful state

    Fold that activity into application-level state: counters, totals, relationships, computed columns.

  4. 4

    Give your frontend a backend

    Query the result over REST, subscribe to changes as they commit, or receive signed webhooks.

Change a rule. Keep serving.

Edit a reducer and the table it writes is wrong by definition: every row was folded by the old rule. Nineveh rebuilds it beside the live one and swaps the two when the new one catches up.

The rebuild replays Nineveh's own record log rather than the chain, so most rule changes don't mean buying the same history twice. The old table answers reads throughout, frozen at the version it had reached, and starts advancing again once the new one is in.

sellers v1
serving, not advancing
sellers v2
folding your record log
v2 swaps in
Nobody is locked out and the history isn't re-streamed. The served table does go stale while the rebuild runs, and its status says so.

Your data. Your logic. Your backend.

INPUT
Aptos' Transaction Stream: every transaction in commit order, with its events and its write set, the exact storage slots it changed. Nothing to poll, nothing to schedule.
PROCESSING
A deterministic fold. Same records in, same rows out, so replaying your history produces exactly the state you have now.
STORAGE
PostgreSQL, a schema per build, written only by your own rules. Move's wide integers are kept exact.
OUTPUT
REST over every table, a change feed over SSE that resumes from where you left it, and signed webhooks.
In order

A balance that goes 5 → 12 → 7 lands as 7, never as 12.

Exactly once

Rows and cursor commit together, or neither does.

Crash-safe

A restart resumes from the cursor. Nothing counted twice.

Exact arithmetic

Move's integers, checked. A bad rule halts; it never corrupts.

#model

State is derived, not written.

One rule holds the whole thing up: a reducer is the only thing that ever writes a table. Not your API, not a script, not a migration. Everything below is a consequence of it, and none of it is available to a backend that lets anything else write.

  1. your contract

    Events and write sets. The source of truth, on chain.

  2. records

    What arrived, in version order, kept so it can be replayed.

  3. reducers

    Your fold. The only thing that writes a table.

  4. state tables

    Rows, typed and queryable. Derived, never written to.

  5. your app

    REST, a live feed, signed webhooks. Reads only.

The two ends are yours. Everything between them is the part you would otherwise build.

A wrong number has one cause
There is no POST /rows. Every row exists because a reducer put it there, so a figure that looks wrong is a rule that is wrong. Nothing else can have touched it, and there is no other place to look.
Replay is exact
No clock, no randomness, no network call inside a fold. The same records always produce the same tables — which is what makes a rebuild a fact rather than a hope.
Changes announce themselves
A row changes in exactly one place, so that place is where the change is published. Rows and the feed leave in one commit: nothing is announced that isn't in the table, and nothing lands without saying so.
Rules are cheap to change
Edit a fold and every row it wrote is wrong by definition. Nineveh replays its own stored records — not the chain — into a new table beside the live one, and swaps them when it catches up.
#studio

And here it is, running.

Studio creates the project, then shows you what it built: the tables you chose, the ones you folded yourself, every change as it commits, and whether the whole thing is keeping up. These are real screens against a contract running on devnet, not mockups — the rows in them came off the chain.

Nineveh Studio's new project page, having read a contract and listing the events, resources and tables it could follow
Paste a contract address and Nineveh reads it off the chain: every event it emits, every resource it stores, every table inside them. Tick what you want followed, name it, and the backend exists.
#built-for

Built for Aptos application developers.

DeFi, marketplaces, games, social and consumer apps, agents, dashboards. The shapes differ; the need doesn't. Each one wants application state derived from what its contracts do, queryable the way the product actually asks for it.

Leaderboards

Rank every player, live — from the events your game emits and the state it only stores.

10x7a3f…c41d1,284
20x1e87…8d2a1,109
30x9b02…4f77973
GETplayers?order=wins.desc

Marketplaces

What's for sale right now, and what each seller has made.

brass lamp694
oak chair309
wool rug803
GETlistings?order=price.asc

Token & points apps

Every holder, sortable, with the history behind each balance.

0x7a3f…c41d
0x1e87…8d2a
0x9b02…4f77
GETholders?order=balance.desc

Feeds and activity

Append-only history you can page through, by author or by time.

Posted0x7a3f…c41d2m
Replied0x7a3f…c41d9m
Posted0x7a3f…c41d31m
GETposts?author=0x7a3f…

Protocol dashboards

Volume, fees and exposure: totals the contract never stored.

GETdaily_volume?order=day.desc

Whatever your contract emits, you can fold it into a table shaped like the question you actually ask. None of these need a contract change.


#pricing

Free to build on. Always.

Not a trial. The free tier doesn't expire, doesn't need a card, and stays free when paid plans arrive. Testnet and devnet for as long as you want them. The limits are real numbers Nineveh enforces, and when you reach one it tells you which and what it means.

Free

Always free
$0per month, every account
Start building
  • Live tables built from your contract's events, resources and tables
  • REST over every table: filters, sorting, paging, exact counts
  • A live change feed over SSE, resumable from any position
  • Signed webhooks, each endpoint with its own secret and cursor
  • Studio: build a project from an address and watch it fill
  • Rule changes that replay your own history instead of the chain
  • Backfills, cursors, retries and crash recovery
Projects
2
Networks
testnet, devnet
History per project
1 GB
Change feed
7 days
Backfill
6 hours

More

Coming soon

On paid plans

  • Mainnet
  • More than two projects
  • History deeper than six hours

Billing isn't built yet. When it is, everything in the free tier stays free. Paid is what you add, not what you move to.

Build the backend. Build the app.

Start with Aptos. Let Nineveh handle the application layer. Backfills, cursors, retries and crash recovery included.

  1. 01
    Point it at your contract

    Nineveh reads the modules off the chain. Tick the events, resources and tables to follow.

  2. 02
    Say what each record does

    A few lines fold them into rows: counters, totals, relationships, whatever your product needs.

  3. 03
    Query it

    REST and a live change feed over tables you designed.

Join us on Telegram

t.me/+kVwq6suLvZNlNGE0