auradefi 0.1.1
PyPI GitHub

Database schema

There are two tables. Here they are as plain SQL, ready to paste into your own migration:

Both are generated from the same metadata the library uses, and a style gate regenerates and diffs them, so they cannot drift from the code. Regenerate locally with python scripts/emit_schema.py.

auradefi never emits DDL and never opens a connection you did not hand it. The schema is yours: apply it with Alembic, Flyway, Liquibase, Rails, Prisma, psql -f, or whatever reviews your migrations. If you would rather not, one call does it for you:

from auradefi.ledger.backends.models import metadata
metadata.create_all(engine)          # fine for a script; read the warning below

auradefi_ledger_transactions

One row per transaction, per tenant.

Column Type Null Meaning
tenant_id VARCHAR no PK part 1. The usr_… id. Every query is scoped by it.
id VARCHAR no PK part 2. The derived txn_… id, stable across runs and backends.
chain_id VARCHAR no CAIP-2, e.g. eip155:1.
tx_hash VARCHAR no On-chain hash. Not unique on its own: one hash can touch several accounts.
account_id VARCHAR no Which connection this row was ingested for.
block_number BIGINT yes NULL while pending.
initiated_at BIGINT no Millisecond epoch.
confirmed_at BIGINT yes Millisecond epoch; NULL until confirmed.
entries_json VARCHAR no The movements, as canonical JSON. See below.
removed BOOLEAN no Reorg tombstone. A removed row is kept, never deleted.
last_modified_seq BIGINT no Cursor ordering. Indexed with tenant_id.

Index: ix_auradefi_ledger_transactions_tenant_seq (tenant_id, last_modified_seq). sync() filters and orders on exactly that pair, so this index is what makes the cursor feed cheap. Keep it.

entries_json

Canonical JSON, sorted keys, no whitespace. Each entry looks like this:

[{"asset_id":"eip155:1/slip44:60","decimals":18,"direction":"in","raw":"1000000000000000000"}]

raw is a decimal-int string and never a JSON number. That choice is load-bearing: json.loads("1e77") yields a float that is wrong by about 10^60. Because these rows live in your database, something other than auradefi may write them, so a numeric raw is rejected on read instead of being coerced. An error is easier to deal with than a plausible-looking wrong amount. direction is in, out or self.

auradefi_ledger_seqs

Column Type Null Meaning
tenant_id VARCHAR no PK.
seq BIGINT no Monotonic counter for that tenant; first value is 1.

The cursor counter lives in the database rather than in the process, so a restart cannot hand out a sequence number twice. Allocation is documented single-writer: run one ingest worker per tenant, or hold a lock, until Postgres hardening lands.

Two hazards

Every numeric column is BIGINT, and it has to be. Python int maps to SQLAlchemy Integer, which is int4 on PostgreSQL, and a millisecond epoch (1_754_000_000_000) overflows int4 by 816 times. Until 0.1.2 these columns were INTEGER, which meant the SQL ledger could not work on PostgreSQL at all: the first insert would fail with integer out of range. SQLite never noticed, because its INTEGER affinity is already 8 bytes, and that is how a fully green test suite hid the bug. If you hand-write this schema, use 64-bit integers.

metadata is the global SQLModel.metadata. If your application also uses SQLModel, its tables sit in the same registry, so:

metadata.create_all(engine)     # creates OUR two tables AND all of yours

That is rarely what you want against a production database, and it is the best reason to apply the .sql files instead, since they create exactly two tables and touch nothing else. The auradefi_ prefix on every table name exists for the same reason: these objects land in your database, beside your own.

What is not here

Only the ledger persists. Tenancy, API keys, quota counters, the audit log, webhook endpoints and deliveries, and embed sync-state are all in-memory in this release. They have no tables, and a restart forgets them.

That is a real limitation of the release, not an omission from this page. If you need any of it durable, the ports are there: bind your own sync_state (Bring your own), and keep tenancy in your own schema until a SQL backend for it ships. The audit log in particular is security-relevant and in-memory, so treat it accordingly.