@odatano/core
v2.0.0-rc.39
Published
SAP CAP plugin for Cardano blockchain OData V4 integration
Maintainers
Readme
Enterprise OData Services for the Cardano Blockchain
Funded by Cardano Catalyst Fund 14 all four milestones completed and approved (April 2026).
ODATANO is an SAP CAP–based service that exposes the Cardano blockchain via a standardized OData V4 interface, enabling enterprise-grade read and write access with native transaction building.
Quick Start
As CAP Plugin (recommended)
Add ODATANO to any existing SAP CAP project:
npm install @odatano/core @cap-js/sqlite{
"cds": {
"requires": {
"db": { "kind": "sqlite" },
"odatano-core": {
"network": "preview",
"backends": ["blockfrost"]
}
}
}
}Put your API key in a .env file in the project root (loaded automatically by cds serve / cds watch) — don't commit it to package.json:
# .env
BLOCKFROST_API_KEY=preview_your_api_key_herecds deploy --to sqlite
cds serveConfig in
cds.requires.odatano-coretakes precedence over environment variables; ablockfrostApiKeyset there would override the.envvalue.
Self-hosted Blockfrost-compatible node? (Dolos MiniBF, Demeter Self-Hosted) Set
blockfrostCustomBackendto the node's base URL (e.g.http://localhost:3010/api/v0). When set,blockfrostApiKeybecomes optional. See the Backend Configuration Guide.
Standalone
Run ODATANO as a self-contained service (no host CAP project needed):
git clone https://github.com/ODATANO/ODATANO && cd ODATANO
npm ci
cp .env.example .env # set BLOCKFROST_API_KEY, NETWORK, BACKENDS
cds deploy --to sqlite
cds serveOr with Docker — this brings up a local cardano-node + Ogmios + ODATANO stack (Preview), with the service pre-wired to the local Ogmios backend:
docker compose up -d # full stack
docker compose up -d cardano-node ogmios # just the chain backend (pre-sync)
docker compose logs -f ogmios # watch sync (/health: 202 syncing, 200 near tip)First sync from genesis takes hours; the node DB persists in a Docker volume so restarts resume. Until Ogmios catches up, historical queries fall back to Blockfrost (set
BLOCKFROST_API_KEY).
Service available at http://localhost:4004. See the Quick Start Guide for the local Ogmios stack and full details.
Dev Mode: Mocked Authentication
Heads-up.
cds serveuses CAP's mocked auth:@requires: 'authenticated-user'accepts any Basic-Auth header against a mock user (alice,bob, …) — passwords are not checked. Anonymous requests get 401, but anyone reaching the port can authenticate.Production needs the
[production]profile (NODE_ENV=production), which switches toauth: xsuaa. Don't expose a dev-mode instance. See the Security Guide.The Docker image runs
NODE_ENV=productionwith HTTP basic auth via@odatano/cap-auth(ODATANO_HTTP_PASSWORDrequired, userODATANO_HTTP_USERdefaultodatano, rolesODATANO_HTTP_ROLESdefaultAdmin; 20 failed attempts per 15 min per client address and user, then 429).ODATANO_AUTH=dummydisables authentication for local testing only.getLiveness()andVerifyDataSignaturestay anonymous.Database of the image:
ODATANO_DB_URL=postgres://user:pw@host:5432/dbselects PostgreSQL (schema deployed on every boot); otherwise SQLite atODATANO_DB_PATH(default/data/db.sqlite).docker compose run --rm --no-deps odatano migrate --from /data/db.sqlitecopies an existing SQLite file into a freshly deployed PostgreSQL.
Services
| Service | Path | Purpose |
|---|---|---|
| CardanoODataService | /odata/v4/cardano-odata/ | Read blockchain data (29 entities, 19 actions) |
| CardanoTransactionService | /odata/v4/cardano-transaction/ | Build & submit transactions (13 actions) |
| CardanoSignService | /odata/v4/cardano-sign/ | External signing + HSM (9 actions) |
| CardanoIndexerService | /odata/v4/cardano-indexer/ | Chain crawler / pre-sync control (v2.0, off by default) |
| CardanoWorkerService | /odata/v4/cardano-worker/ | Asynchronous wallet jobs (v2.0, off by default) |
| CardanoAgentService | /odata/v4/cardano-agent/ | Agent grants: scoped bearer tokens for agents (v2.0, off by default) |
Both v2.0 services also publish CAP events, so a consumer can subscribe instead of polling — in-process, no broker required:
(await cds.connect.to('CardanoWorkerService')).on('jobConfirmed', ({ data }) => …)
(await cds.connect.to('CardanoIndexerService')).on('blockIndexed', ({ data }) => …)Agent grants (v2.0, off by default)
Hand an agent a scoped, budgeted token instead of a user.
AGENT_GRANTS_ENABLED=true # or cds.requires.odatano-core.agentGrants.enabled
AGENT_GRANTS_DELEGATE=mocked # CAP auth kind that keeps authenticating non-token requests (default: configured kind)
AGENT_GRANT_ADMIN_RATE_LIMIT=10 # grant administration calls per principal per hour (or agentGrants.adminRateLimit)
AGENT_TOKEN_CACHE_MS=10000 # a resolved token is reused this long before the row is read again; 0 = off (or agentGrants.tokenCacheMs)- An Admin issues a grant (
POST /odata/v4/cardano-agent/CreateAgentGrant): an allow list of actions, optionally a workerwalletId(required forSubmitWalletJob/CancelJob), allowed job kinds, a daily budget and an expiry. The response carries the tokenodat_…once; the server stores only its SHA-256. - The agent sends
x-agent-token: odat_…on any of the six service paths. It runs as the principalagent:<grantId>with the single roleagent-grant, never as the operator: every@requires: 'Admin'surface (pause/resume, HSM signing, grant administration) refuses it by construction. Reads and compute-only actions are always allowed; allow-listed actions each cost one budget unit per UTC day (a build is the service an agent buys); everything else is 403. Wallet jobs are pinned to the grant's wallet and visible only to the grant that queued them. GET /odata/v4/cardano-agent/GetGrantStatus()tells the agent what it may do and how much budget is left;RevokeAgentGrantturns the token into an unknown token immediately.- Lifecycle (Admin; the same semantics as the gateway's peer services, so one client drives
them all):
UpdateAgentGrantchanges label, allow list, job kinds, daily budget or expiry (absent = untouched, explicitnull= cleared; the wallet binding is immutable, 409GRANT_REVOKEDon a revoked grant);RotateAgentGrantTokenreturns a fresh token once, the old one is unknown from the next request;GetGrantUsage(grantId, since, until)lists admitted calls per service and action over up to 366 days (an Admin for any grant, a token for its own). All administration calls share the per-principal hourly rate limit above.
Grantable: Build*, SetCollateral, CreateSigningRequest, VerifySignature, Submit*,
CheckSubmissionStatus, SubmitWalletJob, CancelJob. Never: SignWithHsm*, Pause*/Resume*,
pauseCrawler/resumeCrawler, grant administration.
Programmatic seams for other packages (@odatano/x402 sells grants against a 402 payment):
issueAgentGrant(), updateAgentGrant(), rotateAgentGrantToken(), revokeAgentGrantById(),
getGrantUsage(), registerTransportLane() from @odatano/core.
Liveness probe
GET /odata/v4/cardano-indexer/getLiveness() answers 200 { status: "alive", timestamp, uptime,
version, network } without credentials (@requires: 'any') as long as the process runs — no
backend or DB probe, no secrets. The Docker HEALTHCHECK, compose and upstream probes use it
instead of the service document, so a wrong operator password no longer reads as "ODATANO down".
Readiness (crawler, worker, backends) stays authenticated: getStatus(), GetWorkerStatus(),
GetNetworkInformation.
Requirements
- Node.js >= 22.5 and @sap/cds >= 10 (peer dependency). CAP 9 hosts cannot load
@odatano/core@2. - Upgrading a consumer from 1.x: run
cds deploy. 2.0 adds six tables (CardanoSyncState,CardanoReorgLog,CardanoWorkerWallets,CardanoWalletJobs,CardanoAgentGrants,CardanoAgentGrantUsage) plus adedupKeycolumn; without the redeploy the new services answerno such tablewhile the old ones keep working.
Documentation
| Guide | Description | |-------|-------------| | Quick Start | Get running in 5 minutes | | User Guide | API usage, entities, and examples | | Developer Guide | Architecture and development | | Transaction Workflow | Build → Sign → Submit flow | | Security Guide | Authentication, signing security, HSM | | Docker Deployment | Container deployment |
License
Apache License 2.0 - see LICENSE
Support
- Issues: GitHub Issues
- Project Lead: Max Weber
- Contact: [email protected]
