Built for Aptos, on the transaction stream

A live backend for your Aptos contract

Point Nineveh at your contract's address. Get a database and an API that stay in sync with the chain — sorted, filtered, aggregated, live. No indexer to write, nothing to run.

From the chainevents, as they commit
  • v20,670,490Soldoak chair225
  • v20,670,453Soldiron kettle912
  • v20,670,416Soldwool rug719
  • v20,670,379Soldbrass lamp526
Your tablesellers · key seller
sellersoldrevenue
0x7a3f…c41d1515
0x1e87…8d2a1705
0x9b02…4f771894
0x6146…e5541220

revenue = price − fee, folded from every sale

Every sale the contract emits, folded into the table your app queries.
#problem

The chain answers one kind of question

What is X right now? One account's balance. One listing by id. It can't sort, total, join or give you a feed — and the data your app needs isn't even in storage. It lives in events and write sets, because keeping totals on-chain costs gas on every transaction.

So every team writes an indexer: a processor, a database, a server, a deploy pipeline. A week of work, and something to maintain forever. Nineveh is that week, done.

Show me all of them, sorted
Top players. Cheapest listings. Biggest holders.
What happened?
A feed. A history. This user's last twenty actions.
How much, in total?
Revenue per seller. Volume per day. Count per account.
#how

Describe the table. Get the API.

No processor to write, no migrations, no schema to keep in step. Change a rule and Nineveh rebuilds the table from history in the background, then swaps it in — the old data keeps serving the whole time.

nineveh.yaml
# nineveh.yaml — the whole backend
sources:
sold: { event: "0x…::market::Sold" }
state:
sellers:
key: [seller]
columns:
seller: address
sold: { type: u64, default: 0 }
revenue: { type: u64, default: 0 }
reduce:
- on: sold
set:
sold: "sold + 1"
revenue: "revenue + price - fee"
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, plus a live change feed and signed webhooks.

Four steps, and none of them are yours

  1. 1

    The chain pushes

    Nineveh sits on Aptos' transaction firehose. Every transaction arrives in order as it commits — no polling, no cron, nothing to schedule.

  2. 2

    It decodes what you follow

    Each transaction carries its events and its write set — the exact storage slots it changed. That's why resources and tables work, not just events.

  3. 3

    It folds

    Records run through your rules. Everything from one transaction lands in a single database write, with a bookmark saying how far it got.

  4. 4

    You query it

    REST over every table, with filters, sorting and paging, plus a live change feed 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.

#studio

You get a dashboard too

Studio builds the project, then shows you what it built: the tables, the changes arriving, and whether the whole thing is keeping up with the chain.

Nineveh Studio showing a log table of 836,565 rows with typed, filterable columns
836,565 orders off a testnet perps DEX, typed by the config and filterable by any column.
#questions

Questions your contract already answers, but can't be asked

None of these need a contract change. The data is already on-chain; it simply isn't queryable.

Leaderboards

Rank every player, live, from the events your game already emits.

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.


#pricing

Free while Nineveh is in alpha

One plan, no card. The limits are real numbers the backend enforces, not a trial that expires — when you reach one, it tells you which and what it means.

Free

Available now
$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
  • Mainnet
  • More than two projects
  • History deeper than six hours
  • GraphQL

No billing yet, so nothing to buy.

You deployed the contract. The backend is the easy part now.

Backfills, cursors, retries, crash recovery — Nineveh's problem, not yours.

  1. 01
    Paste your address

    Nineveh reads the contract's modules off the chain.

  2. 02
    Tick what to follow

    Events, resources and tables become tables of your own.

  3. 03
    Query it

    REST, a change feed and webhooks, seconds later.