SAP CAP plugin: Midnight blockchain indexer + transaction submission, exposed as OData V4.
@odatano/nightgate connects SAP CAP to the Midnight blockchain. A crawler indexes blocks from a Substrate RPC node into CAP entities; a wallet stack in a worker thread submits transactions (Compact deploys and calls, NIGHT and custom-token transfers, dust generation, fee sponsoring). Everything is exposed as OData V4.
┌──────────────────────────────────────┐
│ Midnight Preview / Preprod │
│ Substrate Node GraphQL Indexer │
└──────────────┬──────────────┬────────┘
│ │
wss:// │ Substrate │ GraphQL
JSON-RPC │ RPC │ HTTP + WS
▼ ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ NIGHTGATE │
│ │
│ Main thread Worker thread │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ Crawler │ │ Wallet SDK │ │
│ │ - BlockProcessor │ │ │ │
│ │ - reorg detection │ │ - facade.start (sync) │ │
│ └─────────┬────────────┘ │ - transferTransaction │ │
│ │ atomic writes │ - finalize (ZK prove) │ │
│ ▼ │ - registerForDustGen │ │
│ ┌──────────────────────┐ │ - deployContract │ │
│ │ CAP DB │◄────state-save──┤ - submitContractCall │ │
│ │ (SQLite / HANA) │ periodic save │ │ │
│ └─────────┬────────────┘ │ - private-state-rpc │ │
│ │ OData V4 └──────────┬───────────────┘ │
│ ▼ │ │
│ 5 services on /api/v1/{nightgate, indexer, analytics, admin, verify} │
└───────────────────────────────────────────────────────────────────────────┘
The wallet SDK runs in a worker thread because its sync saturates the microtask queue; the CAP request pipeline stays responsive.
npm ci
npm run dev Or standalone with Docker (docs/docker.md):
docker pull ghcr.io/odatano/nightgate:latest
docker run -d -p 4004:4004 \
-e ENCRYPTION_KEY=$(openssl rand -hex 32) \
-e NIGHTGATE_HTTP_PASSWORD=change-me \
-v nightgate-data:/data \
ghcr.io/odatano/nightgate:latest.env (see .env.example):
NIGHTGATE_NETWORK=preprod
NIGHTGATE_NODE_URL=wss://rpc.preprod.midnight.network/
# GraphQL indexer, HTTP; the WS URL is derived
NIGHTGATE_INDEXER_HTTP_URL=https://indexer.preprod.midnight.network/api/v4/graphql
# Unset = in-process wasm proving. Setting it selects server proving (production).
# NIGHTGATE_PROOF_SERVER_URL=http://localhost:6300
NIGHTGATE_CRAWLER_ENABLED=false
ENCRYPTION_KEY=<random secret>First sync walkthrough: docs/quickstart.md.
| Service | Path | Content |
|---|---|---|
NightgateService |
/api/v1/nightgate |
chain data, wallet sessions, token / contract / attestation actions |
NightgateIndexerService |
/api/v1/indexer |
sync state, health, metrics, crawler control |
NightgateAnalyticsService |
/api/v1/analytics |
aggregate counts |
NightgateAdminService |
/api/v1/admin |
sessions, contract registration, diagnostics |
NightgateVerifyService |
/api/v1/verify |
unauthenticated state verification (NIGHTGATE_PUBLIC_VERIFY=true) |
Submit actions are async: they return { jobId, status }; poll getJobStatus(jobId, sessionId). Signatures, error codes and examples: docs/actions.md.
| Capability | Surface |
|---|---|
| Block indexing | Crawler with reorg detection; OData queries on Blocks, Transactions, ContractActions, UnshieldedUtxos, NightBalances |
| Wallet sessions | connectWallet (viewing key, read-only), connectWalletForSigning (BIP39 mnemonic, Lace-compatible HD derivation); AES-256-GCM at rest, bound to the requesting user |
| Token ops | sendNight (ledger from the receiver address; tokenTypeHex for custom tokens), registerForDustGeneration / deregisterFromDustGeneration |
| Fee sponsoring | Dust generation delegation (dustReceiverAddress) and per-tx sponsorSessionId on submit actions (platform sponsors: NIGHTGATE_FEE_SPONSOR_SESSION) |
| Pre-flight | getWalletBalance, estimateSendNightFee, deriveWalletInfo |
| Compact contracts | deployContract, submitContractCall, submitContractCallBatch on registered artifacts |
| Proving | wasm in-process (default without a proof server) or server (selected by a proof-server URL; production). Override: NIGHTGATE_PROVING_MODE |
| Document anchoring | anchorDocument / verifyDocument: hash on chain, storage stays with the caller |
| Document ingestion | prepareDocumentProof: canonical JSON -> payloadHash + salted content root with per-field inclusion paths (16 slots, 32 on attestation-vault-32); prepareMembershipSet: canonical allow-list root. Compute-only |
| Agent access | createAgentGrant / updateAgentGrant / rotateAgentGrantToken / revokeAgentGrant: scoped bearer tokens (x-agent-token) with action allow-list, budgets, pinned session and sponsor; attestAgentOutput: verifiable agent-output provenance. MCP server: @odatano/nightgate-mcp |
| Field proofs | issueFieldPredicateAttestation (value <= / >= threshold), issueFieldEqualityAttestation (value behind a public digest), issueFieldMembershipAttestation (one of up to 64 allowed values); issueFieldPredicateAttestationBatch: up to 8 mixed claims in one tx. Values stay hidden |
| Cross-document proofs | issueDocumentIntegrityAttestation (B differs from A only in a slot mask), issueDocumentDiffAttestation (at least k slots differ); same width only |
| Crawler-free verification | verifyAttestationState, verifyPredicateState, reindexDisclosures: live contract state from the public indexer, optional network override |
| Tiered disclosure | grantDisclosure / revokeDisclosure / registerGranteeIdentity, DisclosureGrants index, AttestationService with three tiers |
| Local tx building | @odatano/nightgate/txbuilder or @odatano/nightgate-tx: build, prove and sign with your own key, then sponsorFinalizedTransaction / sponsorUnboundTransaction pays and submits. The sponsor sees no key, witness or preimage |
| Browser | @odatano/nightgate/browser + GET /zk-config/<contract>/… + GET /contract-manifest: wallet-driven dApps without Compact toolchain or managed/ artifacts |
| Operations | Health, liveness, readiness, Prometheus metrics, crawler pause / resume / reindex, offline start |
- Quickstart: first wallet-signed transaction
- Actions: every action and function with examples
- Architecture: worker thread, submission flow, persistence
- Operations: scripts, local indexer, troubleshooting
- Docker: standalone container, configuration, schema upgrades
- Transaction builder: build sponsorable transactions without a server
- Reference: configuration and project structure
- Changelog
cd my-cap-app
npm install @odatano/nightgate @cap-js/sqlite{
"cds": {
"requires": {
"db": { "kind": "sqlite" },
"nightgate": { "network": "preprod" }
}
}
}Then cds watch. network is the only required key; the defaults are the public RPC and indexer and wasm proving. Configuration: docs/reference.md#configuration.
npm run dev # cds watch, 12 GB heap
npm run serve:sync # cds-serve, 12 GB heap; for long syncs and e2e runs
npm run sync:start # create a wallet session on the running server
npm run typecheck
npm run lint
npm test # Vitest with coverage
npm run build # CDS types + TypeScript
# Real SDK, no chain access
npm run smoke:sdk
npm run integration:providers # also: wallet-keys, wallet-facade, contract-registry,
# connector-routes, attestation-vault, derive-wallet-info
# Live e2e on preprod (funded wallet)
npm run deploy:e2e # more lanes: docs/operations.md