ODATANOODATANOAPIpreprod
Quickstart · Midnight with NIGHTGATE

First anchor on Midnight

Anchor a hash under your own identity. You build and prove the transaction on your machine, a sponsor pays the fee. You need Node, a key and a seed: no NIGHT, no DUST, no indexer, no proof server.

Before you start

  • Node22 or newer
  • Keyan oda_… key: free with a wallet sign-in (300 calls), or bought with tADA. It goes in Authorization: Bearer.
  • Seed128 hex characters, a 64-byte seed. It is your attester identity: keep it, it never leaves your machine and needs no funds.
  • Vaultthe shared sandbox vault … takes anchors from every key on this network. Each record is keyed by your attester id: nobody else can take it over or retract it. Everything on it is public test data.
  • Costabout 10 calls per anchor: the chain reads for the build, the sponsored submit and the job status. Verify and /zk-config are free.
shellnpm i @odatano/nightgate-tx @midnight-ntwrk/compact-runtime

# a fresh seed: 64 bytes, 128 hex characters
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

# your environment
ODATANO_KEY=oda_…
SEED_HEX=<128 hex>
VAULT=<64 hex vault address>

Five steps

  • 1Build locally. createTxBuilder with api reads contract state through this gateway. The prover keys come from /zk-config on the first run and stay cached.
  • 2Prove locally. buildSponsorable proves in-process, about one to two minutes on a laptop. Only about 5 KB leave the machine.
  • 3Hand it to the sponsor. sponsorUnbound submits the job and waits for the chain. The sponsor pays the dust; the anchor carries your attester id.
  • 4Find it on chain. waitLanded resolves the job's identifier to the block and the transaction hash explorers show.
  • 5Verify, keyless. Anyone checks the anchor against live contract state with a plain GET, no wallet and no key.
anchor.mjsimport { createHash } from 'node:crypto';
import { connect, createTxBuilder } from '@odatano/nightgate-tx';
import { waitLanded } from '@odatano/nightgate-tx/txbuilder';
import { prepareAttest } from '@odatano/nightgate-tx/calls';
import { Contract } from '@odatano/nightgate-tx/attestation-vault-32';

const BASE = 'https://api.preprod.odatano.dev';
const api = { baseUrl: BASE, token: process.env.ODATANO_KEY };
const builder = await createTxBuilder({
  seedHex: process.env.SEED_HEX, networkId: 'preprod',
  api, walletSync: false,   // chain reads through the gateway
  zkConfigBaseUrl: `${BASE}/zk-config/attestation-vault-32`,
  contractClass: Contract, contractName: 'attestation-vault-32',
});

const sha = (s) => createHash('sha256').update(s).digest('hex');
const payloadHash = sha('my document');   // only the hash goes on chain
const call = prepareAttest({
  payloadHash, metadataHash: sha('{}'),
  attestationSecret: builder.attestationSecret,
});
const { unboundTxB64 } = await builder.buildSponsorable({
  contractAddress: process.env.VAULT, call, bind: false,
});

const job = await connect(api).sponsorUnbound({
  unboundTxB64, idempotencyKey: `anchor-${payloadHash}`,
});
const landed = await waitLanded(job.txHash, { api, timeoutMs: 120_000 });
console.log({
  attesterId: builder.attesterId, payloadHash,
  identifier: job.txHash, explorerHash: landed?.txHash,
});
await builder.close();

Verify and look it up

  • Verify/odata/v4/midnight-verify takes no key and charges nothing. The same function under /odata/v4/midnight needs a key.
  • Recorda record is keyed by attester id and payload hash. Check the attester id against attesters you trust.
  • Identifierthe job's txHash is the ledger identifier. It exists as soon as you build and stays the same when the sponsor adds its fee. Use it to track the job.
  • Explorer hashthe transaction hash explorers show exists only once the sponsor completed the transaction. waitLanded returns it as txHash, Transactions/byIdentifier as ledgerTxHash.
http# keyless, no wallet
GET https://api.preprod.odatano.dev/odata/v4/midnight-verify/verifyAttestationState(
      contractAddress='<vault>',attesterId='<attester id>',
      payloadHash='<sha256>',compiledArtifactRef='attestation-vault-32')
→ 200  { "verified": true, "attested": true, … }

# the preprod explorer
https://preprod.midnightexplorer.com/transactions/0x<explorer hash>

Your own vault

  • Whenfor production use, or for the document-id registry, which only the vault's registrar can run
  • Granta key with a deploy grant may sponsor one vault deploy: ask in a NIGHTGATE issue with network, use case and expected volume. The landed address becomes sponsorable under that key.
  • Registrarthe vault's registrar is your attester id, derived from your seed.
  • Dry runbuild and prove the deploy without sending it first. A dry run leaves the grant's deploy unused.
  • Scriptreferences/deploy-vault.mjs in the AGENT_MS repo, the same builder settings as above
shellgit clone https://github.com/maxalexweber1/AGENT_MS
cd AGENT_MS && npm ci
cp .env.example .env   # NIGHTGATE_TOKEN, NIGHTGATE_SEED_HEX

node references/deploy-vault.mjs --dry-run   # nothing is sent
node references/deploy-vault.mjs             # prints the vault address

When something fails

MessageWhat it means
seedHex must be 128 hex charsthe seed is 64 bytes, written as 128 hex characters
401 a gateway key is requiredthe call needs a key. Verify works without one under /odata/v4/midnight-verify.
… blocks behind, allowed 60the server's contract states trail the chain right now. Retry in a minute.
not sponsorablethe sponsor does not accept this contract or circuit for your key. Use the sandbox vault, or deploy your own with a grant.
already attestedyour attester id has anchored this payload hash before. A record is written once; retract it first to change its metadata.
429rate limit or used-up calls. Your account shows the calls left.
404 on /zk-config/<contract>expected for the bare path. The keys live below it, the builder fetches them itself.

A working example