v3.2.1 Source
MIT licensed · Django 5.1 · DRF 3.15

One REST API for five chains.

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.

5chains supported
41REST endpoints
99.94%90-day uptime
MITno keys for reads
curl
# 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"
}
Supported chains

Five networks, one vocabulary

Every adapter normalises to the same field names. Chain-specific extras stay available under raw, so nothing is lost in translation.

Bitcoin
bitcoin · mainnet
Height
912,598
Client
Core 29.0
Status
synced
Solana
solana · mainnet-beta
Slot
429,091,365
Client
Agave 2.3.4
Status
synced
TON
ton · mainnet
Seqno
51,218,825
Client
ton 2026.03
Status
synced
TRON
tron · mainnet
Height
86,436,308
Client
java-tron 4.8.0
Status
synced
BNB Smart Chain
bsc · chain-id 56
Height
74,203,908
Client
bsc 1.5.12
Status
synced
Why it exists

Because five RPCs are five different jobs

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.

01

Normalised schema

One Block, one Transaction, one Balance serializer across every adapter. Amounts are always integer base units plus a decimals field — never a float.

02

Raw passthrough

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.

03

Streaming, not polling

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.

04

Reorg-aware

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.

05

Runs on your nodes

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.

06

Boring dependencies

Django, DRF, Redis for the stream fan-out and Postgres for the address index. No message broker, no bespoke storage engine.

The shape

A transaction looks the same everywhere

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.

GET /api/v1/tron/tx/9e0b…c41f
{
  "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…" }
}
Install

Up in about four minutes

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.

docker
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
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"))