auradefi
Open-source, multi-tenant crypto data aggregator. It takes the tenancy model from Vezgo, the DeFi position depth from DeBank and the transaction decomposition from Zerion, then emits Plaid's wire format, so crypto lands in the same downstream schema as bank and exchange data.
It is a library first and a service second. A Python host imports auradefi
directly and pays no serialisation or network cost. The HTTP API is a thin
shell over the same importable core.
Status: alpha, and 0.1.2 is the release to use. It is 3,426 tests green offline on a fresh clone with no API keys, its notebooks execute clean, and every example under
examples/runs against the published wheel. Work towards 0.2.0 is onmainahead of that release: the EVM node path landed first (a JSON-RPC client, a Multicall3 batcher, a log scanner and a concrete on-chain reader), so this page describes a tree that is not yet a release.STATUS.mdcarries the live gate state, including which gates are red today.Do not use 0.1.0. A separate adversarial review of it found nineteen verified defects, of which five were security and four were silent data loss. None of them failed a test.
docs/internal/RELEASE_0.1.1.mdis the full accounting. 0.1.1 fixes all nineteen and deliberately breaks one id derivation, so read Upgrading inCHANGELOG.mdbefore you move library-ingested data across.Alpha means the gaps in What is not there are real; read that section before you budget work against this.
STATUS.mdcarries the live gate state, anddocs/internal/SPEC.mdis the design contract.
Documentation site → carries the examples, the thirteen executable notebooks and the full reference, rendered with every example's real output.
Install
pip install auradefi # core; httpx is the only dependency pip install 'auradefi[sql]' # + the SQLModel ledger backend pip install 'auradefi[api]' # + the FastAPI HTTP surface
From a clone (if your system python has no pip, scripts/bootstrap.sh
handles it):
git clone https://github.com/auracarehq/auradefi cd auradefi && bash scripts/bootstrap.sh .venv/bin/pytest # the whole suite, offline, no keys .venv/bin/python examples/quickstart.py # every capability, end to end bash scripts/run_examples.sh # all thirteen examples
Examples
examples/ holds one file per question. Each one is
self-contained, reads nothing from this repository, and runs offline without
API keys. Each asserts its own output, and CI executes all of them, so a
stale example fails the build.
| Example | What it answers |
|---|---|
quickstart.py |
the whole library in one file; start here |
01_holdings_for_an_address.py |
a priced portfolio, exactly, with unpriced assets named |
02_embed_in_your_backend.py |
your ports, your tick, your database, and restart resume |
03_write_a_source_adapter.py |
point it at your own chain data (two methods) |
04_persist_to_your_database.py |
host-owned DDL, a resumable cursor feed, a reorg |
05_serve_the_http_api.py |
Plaid's envelope over HTTP, and batch partial success |
06_isolate_two_tenants.py |
one deployment, many customers, attacked four ways |
07_read_defi_positions.py |
an LP and a loan that still add up to net worth |
08_report_cost_basis_and_pnl.py |
FIFO/LIFO/HIFO/ACB, any instant, Plaid tax_lots[] |
09_deliver_signed_webhooks.py |
signing, the pinned retry schedule, replay |
10_scan_bitcoin_and_solana.py |
an xpub that never leaves the process, and Token-2022 |
11_provoke_every_error.py |
every error on purpose, so you can test your handler |
12_read_a_contract_from_a_node.py |
eth_call, a batch, five reads in one round trip, and logs |
examples/README.md annotates the index, and the
documentation site renders
every example with its real output.
Using it
As a library, where the host owns storage, transport, prices and the tick:
from auradefi import Auradefi auradefi = Auradefi( ledger=SqlModelLedger(session_factory=my_session_factory), # your database source=MySource(), # your transport: .balances() + .fetch_txlist() prices=MyPrices(), # your price feed: .usd_prices() ) user = auradefi.user("opaque-host-user-id") # get-or-create, id is derived user.connect_address("eip155:1", "0x…") # validated now, not on a later tick report = auradefi.sync(budget=5) # budgeted, resumable, self-throttling holdings, metrics = auradefi.holdings(), auradefi.scalar_metrics()
As a service, where create_app takes ports you already built:
from auradefi.api.app import create_app from auradefi.api.deps import Deps app = create_app(Deps(tenancy=…, keys=…, ledger=…, webhooks=…, clock=…)) # POST /auth/token, POST /connections, GET /crypto/sync (Plaid's envelope) # GET /coverage, POST /webhooks/endpoints, POST /webhooks/…/replay
docs/books/ walks both surfaces executably and offline.
What works today
Coverage is published as data (rule #10). Every row below has an executable
notebook under docs/books/ that runs offline and asserts its
own outputs, plus a gate test under tests/.
| Capability | Limits, and what proves it |
|---|---|
Quantity/Money: exact at 10^77, four-field wire form, raw always a JSON string, strict wire grammar |
02_money |
CAIP-2/CAIP-19 parse + canonicalize, deterministic ast_… ids, both-ways asset registry |
5 seed chains (Ethereum, Polygon, Base, Bitcoin, Solana); 03_assets_chains |
Asset groups (decimals-equality law, single fallback) + additive spam scoring (score + numbers, caller threshold) |
03_assets_chains |
Ledger port: idempotent upsert, cursor sync with has_more paging, reorg as removed + re-added, resurrection, tenant isolation |
memory and SQLModel backends; 04_ledger |
Cassette replay harness (CassetteMissError offline guarantee) |
01_foundation |
Style gates: size, structure, placement, layering (tests/style) |
no allowlist |
EVM balances to holdings, exact-Decimal USD totals, unpriced assets named |
Etherscan V2 source + DefiLlama prices; 05_holdings |
Tenancy: org/project/end-user, scoped adk_ keys, authEndpoint JWT mint, three-window quota, audit log |
the isolation gate actively tries to leak; 06_tenancy |
Rich transactions: parts[]/acts[], fees as siblings carrying borne_by, derived type, ledger bridge, reorg + resurrection |
EVM only, one act per transaction; 07_transactions |
| DeFi positions: adapter protocol, drill-down, group totals + health factor, signed synthetic-Holdings projection | Uniswap v2/v3, Aave v3, Lido/Rocket Pool; 08_positions |
EVM node path: keccak256, a static ABI codec, JSON-RPC single calls and id-matched batches, Multicall3 aggregate3 with per-call failure declared, chunked eth_getLogs, and EvmContractReader as the adapters' seam |
Ethereum-shaped chains, static ABI types only, no async; 13_evm_node |
Embedding: from auradefi import Auradefi, host-owned session, budgeted two-phase sync, 26-metric scalar projection |
chain-scoped connection ids, restart resume enumerated from the state port, one connection's failure contained to its own row (0.1.1 #18/#21/#24/#26); 09_embedding, 02_embed_in_your_backend.py |
| Bitcoin: pure-Python BIP32 xpub derivation, gap-20 scan, confirmed-only UTXO balances | p2wpkh + Esplora only; the extended key never reaches HTTP; 10_bitcoin_solana |
| Solana: SPL + Token-2022 balances, ScaledUiAmount carried both ways, signature history | balances only, no decode; 10_bitcoin_solana |
HTTP API: Plaid /crypto/sync envelope, connections, /coverage generated as data, nine quota headers, batch holdings |
12_http_api |
| Webhooks: HMAC-SHA256 signed, durable over a pinned retry schedule, dead letter + replay | 12_http_api |
Accounting: lot ledger, FIFO/LIFO/HIFO/ACB, realised + unrealised PnL, arbitrary-date PnL, Plaid tax_lots |
50,000-event gate; 11_accounting |
What is not there
Rule #10 applies to the absences too.
- There are no live network adapters beyond what the cassettes cover. Every I/O path is exercised against committed recordings. Pointing a source at the real Etherscan, Esplora or Solana RPC needs your own keys and endpoints, and CI has not reconciled the output against an incumbent.
- The EVM node path has never met a real node.
EvmContractReader,EvmRpc,Multicall3andscan_logsship and the five position adapters resolve through them, but every vector that proves it is a hand-authored cassette: the block-20,450,000 words were packed from the integers the existing goldens pin, not recorded from an archive node, andtests/golden/test_phase11_reader.pysays so in its own docstring. What is proven is the selector derivation, the word packing, the tuple decode, the block pin and the JSON-RPC path end to end. Agreement with mainnet state at that block is not proven by anything here. - The codec is static types only:
uint<N>,int<N>,addressandbool, plus Multicall3's own two dynamic shapes as named special cases. Nostring, nobytes, no arrays and no nested tuples, and it raises on them instead of guessing. Every function the shipped adapters call fits, and a contract of yours that does not will be refused rather than mis-encoded. - Nothing in the package constructs an
EvmContractReaderfor you. It is wired by a host, and the block aResolveContextnames is not threaded into the reader: the reader carries its own pin, chosen at construction, so a host that wants a report at block N builds the reader at block N. - One price oracle (DefiLlama), current prices only. There is no fallback
feed and no historical price service:
prices/historian.pyandprices/store.pyare declared in the module layout and absent, as are thecoingecko,manualandonchain_ammoracles, so accounting marks are the caller's. - No scheduler and no background worker. The host owns every tick, and a
backfill is a
sync()budget you spend yourself. This is the design, not a gap: 0.2.0 removedjobs/from the declared layout after finding four of its five modules already shipped under other names, and the fifth was a cron evaluator wearing a domain's clothes. What is genuinely missing is a reprocess path for re-decoding stored rows after the decoder improves; thedecoder_versionit would select on is already persisted. - Five of the nine declared
api/routes/modules are absent:accounts.py,holdings.py,positions.py,transactions.pyandwebhooks.py. What ships isauth,connections,syncandadmin, so holdings and positions have no HTTP surface of their own, and the webhook admin routes live inadmin.py. project/ships onlyscalar.py.project/plaid.pyandproject/native.pyare declared and absent. The Plaid envelope is projected inapi/wire.pyinstead, so that projection is not reusable outside the HTTP shell the way the layer contract intends.- Two ledger backends: in-memory and SQLModel/sqlite. Postgres should work through the same port; only sqlite is exercised. The tenancy, keys, quota, audit and webhook stores are in-memory only.
- Cosmos is absent, as is every EVM chain the registry does not seed. So are
exchange connections, NFTs and protocol-specific decoders (
acts[]is always one act, andprotocolis alwaysNone). - Solana transaction decode is not implemented. Balances and signature history only.
- No async surface, no background worker and no scheduler. The host owns the tick.
- There is no migration for the 0.1.1 embed id break. Library-ingested embed
connection ids, and every
transaction_idhashed over them, re-derive in 0.1.1, so 0.1.0 rows written throughAuradefistop matching. A host either re-derives them itself or accepts the old rows as orphaned history (CHANGELOG.md, Upgrading). Data written through the HTTP API is unaffected. SyncStatePortis a five-method Protocol in 0.1.1 (tenants()was added). A host store written against the 0.1.0 four-method shape is refused at bind time, so it cannot silently sync nothing.
The rules the code lives by
- Money is a tagged decimal string, and a raw amount is never a JSON integer.
- Asset ids are deterministic CAIP-19 and permanently stable.
- Every movement is a
part[]; fees are siblings of movements. - Multi-tenancy is designed in. Two tenants can never see each other's data.
pytestpasses on a fresh clone with no API keys, because the cassettes are committed.- Files cap at 400 lines with no allowlist, and
tests/style/enforces the layer contract.
Docker
docker compose run --rm test # full offline suite in a network-less container docker compose run --rm demo # quickstart against the installed wheel
Docs
auradefi.info is built from this repository, with every example executed at build time and every signature generated from the code.
Start here:
- Quickstart: five lines, no credentials
- Authentication & keys: what you need before mainnet (at most one key, and it is optional)
- Bring your own: your API, your database, your prices, with every port and its methods
- Guides:
examples/, thirteen single files that run offline - API reference: signatures, parameters, return fields, exceptions
- HTTP API: Plaid's wire
format, plus
openapi.json - Errors: every exception and its HTTP status
- Build with an LLM: a prompt to paste into a model, plus llms.txt and the whole documentation as one file
Also in the repository:
docs/books/holds thirteen executable notebooks, run headlessly in CI.CHANGELOG.mdrecords what changed per release, including the 0.1.1 upgrade note.docs/internal/covers how this was designed and built rather than how to use it: the design contract, the pinned algorithms, build status, the 0.1.0 defect accounting, the release procedure and the agent loop that wrote most of it.
Licence
Apache-2.0. See LICENSE.