fullnode sits in front of the full nodes you already run and gives all of them the same schema. Blocks, transactions, balances and mempool state come back in one shape whether they came from Bitcoin Core, Agave, a TON liteserver, java-tron or a BSC archive node.
# the same call shape on every chain curl https://full-node.online/api/v1/bitcoin/status curl https://full-node.online/api/v1/solana/status { "chain": "bitcoin", "network": "mainnet", "synced": true, "height": 912480, "header_height": 912480, "peers": 96, "client": "Bitcoin Core 29.0", "node": "s2" }
Every adapter normalises to the same field names. Chain-specific extras
stay available under raw, so nothing is lost in translation.
Bitcoin speaks JSON-RPC over HTTP, Solana speaks its own JSON-RPC with commitment levels, TON wants an ADNL liteserver, TRON has a REST API that is almost but not quite Ethereum's, and BSC is geth. Writing an application against all five means writing five clients.
One Block, one Transaction, one
Balance serializer across every adapter. Amounts are
always integer base units plus a decimals field — never a float.
Anything the normalised shape does not carry is still reachable:
POST /api/v1/<chain>/rpc forwards a method call to
the underlying node verbatim and returns its reply untouched.
New blocks, mempool entries and address deltas arrive over a long-lived
connection on the /api/v3/ stream protocol. No polling
loop, no missed reorg.
Every block carries a confirmations count and every
stream emits block.reverted before it emits the
replacement. Chain state is a log, not a snapshot.
The gateway holds no keys and stores no chain data. Point the adapters at your own full nodes, or at the public ones during development.
Django, DRF, Redis for the stream fan-out and Postgres for the address index. No message broker, no bespoke storage engine.
The adapter maps chain concepts onto the common fields and leaves the
original document under raw. This is a TRON transfer; a
Bitcoin one differs only in raw and in having several
inputs.
{
"chain": "tron",
"hash": "9e0bd41a7f3c88b0a1e6d9c2f04b7a5e8c3d1096b2f7e4a5d8c0b3a6e91fc41f",
"block_height": 86412188,
"block_time": "2026-08-12T09:14:03Z",
"confirmations": 512,
"status": "confirmed",
"fee": { "amount": "1100000", "decimals": 6, "symbol": "TRX" },
"transfers": [
{
"from": "TQ5N…8fRk",
"to": "TW9x…2bLp",
"amount": "250000000",
"decimals": 6,
"symbol": "TRX",
"asset": null
}
],
"raw": { "…the node's own document, unmodified…" }
}
The compose file brings up the gateway, Postgres and Redis, and points every adapter at the public nodes so you can see it work before you wire in your own.
git clone https://github.com/fullnode-api/fullnode cd fullnode cp .env.example .env docker compose up -d # the API is on :8000, the browsable one too curl localhost:8000/api/v1/chains
pip install fullnode-gateway # settings.py INSTALLED_APPS += ["fullnode"] FULLNODE_ADAPTERS = { "bitcoin": "http://127.0.0.1:8332", "solana": "http://127.0.0.1:8899", "bsc": "http://127.0.0.1:8545", } # urls.py path("api/", include("fullnode.urls"))