$ blocknet API v0.22.0

Network upgrade notice: Blocknet plans a network upgrade around 2027-02-16. After it, you will have 1 year to move your coins to the new format, until about 2028-02-16. Coins not moved by then cannot be spent. The wallet will guide you. JSON API for the Blocknet privacy blockchain daemon. The API starts when the core runs with `--api <addr>`. Private endpoints require Bearer token authentication with the token in `<dataDir>/api.cookie`, written on startup. Public blockchain routes (`GET /api/health`, `/api/status`, `/api/block/{id}`, `/api/tx/{hash}`, `/api/mempool`, `/api/mempool/txs`, `/api/peers`, `/api/peers/banned`, `/api/stats`, and `POST /api/verify`, `/api/verify-proof`) need no token. Node settings are read and changed with `GET` and `PATCH /api/settings`.

# authentication

Token read from `<dataDir>/api.cookie`. Generated fresh on every daemon startup using `crypto/rand` (32 bytes, hex-encoded = 64 chars). File has 0600 permissions. Deleted on graceful shutdown.

curl -H "Authorization: Bearer $(cat data/api.cookie)" http://127.0.0.1:8332/api/status
Node Blockchain Network Wallet Mining Events Admin

# node

Daemon and node status

GET/api/settings

Returns the settings saved in <dataDir>/settings.json and the saved settings that wait for a restart. Requires Bearer token.

Response fields

settings Settings
settings.listen string or null
P2P listen multiaddr. Applies after restart.
settings.seed boolean or null
Seed mode with a fixed identity. Applies after restart.
settings.full_sync boolean or null
Ignore checkpoints and verify the whole chain. Applies after restart. Always on for testnet.
settings.save_checkpoints boolean or null
Append a checkpoint every 100 finalized blocks. Applies now.
settings.explorer_addr string or null
Explorer HTTP address. Empty turns the explorer off. Applies now.
settings.wallet_file string or null
Default wallet file. Its folder holds every wallet the API loads, creates or imports. Applies now.
settings.p2p_max_inbound integer or null
Max inbound peers, 0 = default. Applies after restart.
settings.p2p_max_outbound integer or null
Max outbound peers, 0 = default. Applies after restart.
settings.p2p_whitelist array of string or null
Peer IDs that are never banned. Applies now.
settings.extra_peers array of string or null
Extra peer multiaddrs to dial at startup. Applies after restart.
settings.identity_key_path string or null
Path of a fixed P2P identity key. Empty uses the seed default or an ephemeral identity. Applies after restart.
settings.chain_cache_cap integer or null
Blocks kept in memory, 0 = default. Applies after restart.
restart_required array of string
Saved settings that take effect only after a restart

Errors

401 unauthorized
503 { "error": "settings unavailable" }
PATCH/api/settings

Changes only the fields in the body and saves them to <dataDir>/settings.json. A field set to null goes back to its default. Live fields apply at once. The others are listed in restart_required. Unknown fields are rejected. Requires Bearer token.

Request

{
  "explorer_addr": "127.0.0.1:8080",
  "save_checkpoints": true
}
listen string or null
P2P listen multiaddr. Applies after restart.
seed boolean or null
Seed mode with a fixed identity. Applies after restart.
full_sync boolean or null
Ignore checkpoints and verify the whole chain. Applies after restart. Always on for testnet.
save_checkpoints boolean or null
Append a checkpoint every 100 finalized blocks. Applies now.
explorer_addr string or null
Explorer HTTP address. Empty turns the explorer off. Applies now.
wallet_file string or null
Default wallet file. Its folder holds every wallet the API loads, creates or imports. Applies now.
p2p_max_inbound integer or null
Max inbound peers, 0 = default. Applies after restart.
p2p_max_outbound integer or null
Max outbound peers, 0 = default. Applies after restart.
p2p_whitelist array of string or null
Peer IDs that are never banned. Applies now.
extra_peers array of string or null
Extra peer multiaddrs to dial at startup. Applies after restart.
identity_key_path string or null
Path of a fixed P2P identity key. Empty uses the seed default or an ephemeral identity. Applies after restart.
chain_cache_cap integer or null
Blocks kept in memory, 0 = default. Applies after restart.

Response fields

settings Settings
settings.listen string or null
P2P listen multiaddr. Applies after restart.
settings.seed boolean or null
Seed mode with a fixed identity. Applies after restart.
settings.full_sync boolean or null
Ignore checkpoints and verify the whole chain. Applies after restart. Always on for testnet.
settings.save_checkpoints boolean or null
Append a checkpoint every 100 finalized blocks. Applies now.
settings.explorer_addr string or null
Explorer HTTP address. Empty turns the explorer off. Applies now.
settings.wallet_file string or null
Default wallet file. Its folder holds every wallet the API loads, creates or imports. Applies now.
settings.p2p_max_inbound integer or null
Max inbound peers, 0 = default. Applies after restart.
settings.p2p_max_outbound integer or null
Max outbound peers, 0 = default. Applies after restart.
settings.p2p_whitelist array of string or null
Peer IDs that are never banned. Applies now.
settings.extra_peers array of string or null
Extra peer multiaddrs to dial at startup. Applies after restart.
settings.identity_key_path string or null
Path of a fixed P2P identity key. Empty uses the seed default or an ephemeral identity. Applies after restart.
settings.chain_cache_cap integer or null
Blocks kept in memory, 0 = default. Applies after restart.
restart_required array of string
Saved settings that take effect only after a restart

Errors

400 Invalid settings
401 unauthorized
500 { "error": "internal error" }
503 { "error": "settings unavailable" }
GET/api/health public, no token

Lightweight liveness probe. Returns 200 with the current server timestamp. Does not require authentication.

Response 200

{
  "status": "ok",
  "timestamp": "2026-03-11T18:30:00Z"
}

Response fields

status required string
timestamp required string
GET/api/status public, no token

Returns node and chain status including peer count, chain height, sync state, mempool summary, and wallet diagnostics (if unlocked).

Response 200

{
  "pid": 4182,
  "peer_id": "12D3KooWBLUPjZgAoizTkbNJvHxgHkb2tF1saUhmKBzFfo8zZLfj",
  "peers": 8,
  "chain_height": 14207,
  "best_hash": "0000c3a5b7e2d1f4",
  "total_work": 14208,
  "mempool_size": 3,
  "mempool_bytes": 4821,
  "syncing": false,
  "identity_age": "2h34m12s",
  "peer_addrs": [
    "/ip4/203.0.113.7/tcp/28080/p2p/12D3KooWBLUPjZgAoizTkbNJvHxgHkb2tF1saUhmKBzFfo8zZLfj"
  ],
  "wallet": {
    "data_version": 1,
    "enc_format": "v1",
    "kdf_version": 1,
    "kdf_memory_mib": 256,
    "kdf_iterations": 3,
    "kdf_threads": 4,
    "created_at": 1710000000,
    "view_only": false,
    "has_mnemonic": true,
    "address_format": "checksummed",
    "file_size_bytes": 2048,
    "synced_height": 14200
  },
  "mempool_generation": 17,
  "network_upgrade": {
    "date": "2027-02-16",
    "migration_deadline": "2028-02-16",
    "notice": "Blocknet plans a network upgrade around 2027-02-16. After it, you will have 1 year to move your coins to the new format, until about 2028-02-16. Coins not moved by then cannot be spent. The wallet will guide you."
  }
}

Response fields

pid integer
Process ID of the core
peer_id string
This node's libp2p peer ID
peers integer
Currently connected peer count
chain_height integer
Current chain tip height
best_hash string
Best block hash (first 8 bytes, hex-encoded)
total_work integer
Cumulative chain work (sum of difficulties)
mempool_size integer
Number of unconfirmed transactions
mempool_bytes integer
Total mempool size in bytes
mempool_generation integer
Monotonic mempool content generation used by mining clients to detect template changes
syncing boolean
True if the node is currently syncing blocks from peers
sync_progress integer
Blocks synced so far in the current sync. Only present while syncing.
sync_target integer
Target height of the current sync. Only present while syncing.
sync_percent string
Sync progress as a percent string such as "12.3%". Only present while syncing.
identity_age string
Duration since last P2P identity rotation (e.g. "2h34m12s")
peer_addrs array of string
This node's own non-loopback multiaddrs, each ending in /p2p/<peer_id>. Other nodes can connect with these.
network_upgrade object
Planned network upgrade. Clients should show the notice to users.
network_upgrade.date required string
Planned upgrade date (approximate)
network_upgrade.migration_deadline required string
About 1 year after the upgrade. Coins not moved to the new format by then cannot be spent.
network_upgrade.notice required string
Short notice text for users
wallet WalletDiagnostics
wallet.data_version integer
Wallet data schema version
wallet.enc_format string
Encryption envelope format (legacy = no header, v1 = BLKNTWLT header)
wallet.kdf_version integer
KDF parameter profile version (0 = legacy 64 MiB, 1 = current 256 MiB)
wallet.kdf_memory_mib integer
Argon2id memory cost in MiB
wallet.kdf_iterations integer
Argon2id time cost (iterations)
wallet.kdf_threads integer
Argon2id parallelism (threads)
wallet.created_at integer
Unix timestamp of wallet creation (0 if predates this field)
wallet.view_only boolean
True if view-only wallet
wallet.has_mnemonic boolean
True if the wallet contains a BIP39 mnemonic
wallet.address_format string
Address encoding format (legacy = 64-byte no checksum, checksummed = 68-byte with checksum)
wallet.file_size_bytes integer
Encrypted wallet file size in bytes
wallet.synced_height integer
Last scanned block height
POST/api/verify public, no token

Verifies a Schnorr signature (Ristretto255) against a Blocknet stealth address. Does not require a wallet — uses only stateless cryptographic verification. Useful for 'login with Blocknet' flows where any node can validate a signature produced by `POST /api/wallet/sign`. Rate-limited per IP.

Request

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200",
  "signature": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}
address required string
Blocknet stealth address (base58-encoded)
message required string
The message that was signed (max 1024 bytes of UTF-8)
signature required string
Hex-encoded 64-byte Schnorr signature over Ristretto255 (128 hex chars), as returned by `POST /api/wallet/sign`

Response 200 — Valid signature

{
  "valid": true
}

Response 200 — Invalid signature

{
  "valid": false
}

Response fields

valid boolean
True if the signature is valid for the given address and message

Errors

400 { "error": "invalid address" }
400 { "error": "invalid signature: must be 64 bytes hex-encoded" }
400 { "error": "message is required" }
429 { "error": "verify rate limit exceeded" }
GET/api/stats public, no token

Returns chain, network, and emission/economics statistics as numeric JSON. All amounts are in atomic units (smallest units). Values are a deterministic function of the chain tip and require no wallet.

Response 200

{
  "height": 5000,
  "difficulty": 150000,
  "network_hashrate": 500.0,
  "avg_block_time": 300.0,
  "block_reward": 72325093035,
  "emitted": 361625465175000,
  "remaining": 9638374534825000,
  "target_supply": 10000000000000000,
  "pct_emitted": 3.61625465175,
  "tail_emission": 200000000,
  "months_to_tail": 48,
  "blocks_per_month": 8640,
  "block_interval_sec": 300
}

Response fields

height integer
Current chain height
difficulty integer
Difficulty target for the next block (LWMA)
network_hashrate number
Estimated network hashrate (hashes/sec) over recent blocks; 0 with insufficient history
avg_block_time number
Average block time (seconds) over recent blocks; 0 with insufficient history
block_reward integer
Coinbase reward at the current height (atomic units)
emitted integer
Cumulative coins emitted through the current height (atomic units)
remaining integer
Coins remaining toward target_supply; 0 once tail emission is reached (atomic units)
target_supply integer
Approximate emission before tail emission begins (~100M coins, atomic units)
pct_emitted number
Percent of target_supply emitted (0-100)
tail_emission integer
Perpetual per-block tail emission (atomic units)
months_to_tail integer
Months of decay before tail emission begins
blocks_per_month integer
Blocks per month at the target interval
block_interval_sec integer
Target seconds between blocks
POST/api/verify-proof public, no token

Verifies a sender's payment proof. Given a transaction id, the deterministic transaction private key (`tx_key` from `POST /api/wallet/prove`), and a recipient address, confirms the `tx_key` genuinely belongs to the transaction (`valid`) and reveals which of its outputs that address received, with amounts (atomic units) and any memo. Stateless — no wallet required. Rate-limited per IP.

Request

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "tx_key": "f0e1d2c3b4a5968778695a4b3c2d1e0ff0e1d2c3b4a5968778695a4b3c2d1e0f",
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF"
}
txid required string
Transaction hash (64 hex characters)
tx_key required string
Deterministic transaction private key from POST /api/wallet/prove (64 hex characters)
address required string
Recipient's Blocknet stealth address (base58-encoded)

Response 200 — Valid proof, one output received by the address

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "valid": true,
  "total_matched": 250000000,
  "outputs": [
    {
      "index": 0,
      "match": true,
      "amount": 250000000,
      "memo": "invoice 4821"
    },
    {
      "index": 1,
      "match": false
    }
  ]
}

Response 200 — tx_key does not match the transaction

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "valid": false,
  "total_matched": 0,
  "outputs": []
}

Response fields

txid string
Transaction hash (echoed back)
valid boolean
True if tx_key corresponds to the transaction's public key
total_matched integer
Sum of amounts the address received in this transaction (atomic units)
outputs array of ProofOutput
Per-output match results
outputs[].index integer
Output index within the transaction
outputs[].match boolean
True if this output was received by the address
outputs[].amount integer
Amount received (atomic units); present only when match is true
outputs[].memo string
Decrypted memo, if any; present only when match is true

Errors

400 { "error": "txid must be 64 hex characters" }
400 { "error": "invalid address" }
404 { "error": "transaction not found" }
429 { "error": "verify-proof rate limit exceeded" }

# blockchain

Block, transaction, and mempool queries

GET/api/block/{id} public, no token

Returns a block by numeric height or 64-character hex hash. Includes transaction summaries and confirmation count.

Parameters

id required path · string
Block height (integer) or block hash (64 hex characters)

Response 200

{
  "height": 14207,
  "hash": "0000c3a5b7e2d1f489a1bc37e5d204f8c612aa9b33e7f04d12b8a9e6c7f50321",
  "prev_hash": "0000a1f38e2bc9d7a436ef21c87b3d4e59a0f1b267e3c9d58a2b41f6e7083c19",
  "merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "timestamp": 1738800120,
  "difficulty": 100000,
  "nonce": 847291,
  "tx_count": 2,
  "transactions": [
    {
      "hash": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "is_coinbase": true,
      "inputs": 0,
      "outputs": 1,
      "fee": 0
    },
    {
      "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "is_coinbase": false,
      "inputs": 1,
      "outputs": 2,
      "fee": 15000
    }
  ],
  "confirmations": 1,
  "reward": 72325093035
}

Response fields

height integer
hash string
Full block hash (64 hex chars)
prev_hash string
Previous block hash (64 hex chars)
merkle_root string
Merkle root of transactions (64 hex chars)
timestamp integer
Block timestamp (Unix seconds)
difficulty integer
Block difficulty target
nonce integer
PoW nonce
tx_count integer
Number of transactions in the block
transactions array of TxSummary
Transaction summaries (first tx is coinbase for height > 0)
transactions[].hash string
Transaction hash (64 hex chars)
transactions[].is_coinbase boolean
True for block reward transaction
transactions[].inputs integer
Number of inputs (0 for coinbase)
transactions[].outputs integer
Number of outputs
transactions[].fee integer
Fee in atomic units (0 for coinbase)
confirmations integer
Confirmations since this block (chain_height - block_height + 1)
reward integer
Block reward in atomic units (smooth exponential decay to 2.0 BNT tail)

Errors

400 { "error": "id must be a height or 64-char hex hash" }
404 { "error": "block not found" }
GET/api/tx/{hash} public, no token

Searches mempool first (fast), then the transaction index (a chain scan is used only while the index is still building). Returns the transaction with confirmation count and mempool status.

Parameters

hash required path · string (^[a-f0-9]{64}$)
Transaction hash (64 hex characters)

Response 200 — Confirmed transaction (in blockchain)

{
  "tx": {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "range_proof": "UTfDE/BxZuuznHRyDGLMqI4jjrPMqQ47hVuHEzfesKDfO8VhghbfAGS63COpoD+ZntGnzpdBYtfCWZrPAJuSa9yk7uLibfJWK5GrL3iec2VLDBd98yXp1GPE/cx8SwI22XBa7Rl/PulE7aLi2uRR8+aEfo34eozhJ5J4i6ujKUZNdsRObSDU0Knu1B9p18cKwvQDtJjH1nD5cIvf+A7HrM9U70ENyQ0q20XsXRmFwqds6Keswo7XgSnwCRqzciMUD35mCk56QPI6b+6DvFU6U583DZ/Ay2UmfDSaPRWx270jrgbX+jbduetO3lqK9+7fiaV9LI7mfO3CrA79pl35bLWEro+NBWEre9D6e/P75QgvlnHPfJy88rDZqbToipyAdj1ioT1eYm73jZAzY5d0uFuaB0CMFxuVQPs0BpHw9eGuXhqB9DohzfslG01Mmyt/PNVzwubimNucHjJqbIcpUHpYJlAB0ebwlRB2k5DoJHeHZdk6c0yISCQeVJ2T4D/vm86L/OApFN2lgA0udQqJFFnw4o5c3/su8LLRqqQ1UqjS/ZPNEugtoYGlO84A7NMbYLn/4hpoiEOT4Pg+DnpRnwfQL3M67DxO/5WL1PfxfOlKxGFFI43UrogBkJj6TOT3sKrB6aRgesR30hai8sPFTf0SQKkz4TPpB0nRTybwh63LKajCovkSI3iTdC7eMjPjVZkOF6Yclre/3Ep90lxXWSjDe/5JduyC64IE7pNQJeKwmdmA6ZplxPc2ecO3l5cLyowEGf6SdbRwYYBGMRSe4RG6Qy6Xp9RZZkO7i1SD9petOu8mSHPLuy7KB4c/6LyGw743d/EMp3Eg7ZrRO0cXE5v8OzF4RcbovdZP1DL60I8QvW/j43i5Mry3H8uNYT7o",
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 15000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  },
  "block_height": 14205,
  "confirmations": 3,
  "in_mempool": false
}

Response 200 — Unconfirmed transaction (in mempool)

{
  "tx": {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "range_proof": "UTfDE/BxZuuznHRyDGLMqI4jjrPMqQ47hVuHEzfesKDfO8VhghbfAGS63COpoD+ZntGnzpdBYtfCWZrPAJuSa9yk7uLibfJWK5GrL3iec2VLDBd98yXp1GPE/cx8SwI22XBa7Rl/PulE7aLi2uRR8+aEfo34eozhJ5J4i6ujKUZNdsRObSDU0Knu1B9p18cKwvQDtJjH1nD5cIvf+A7HrM9U70ENyQ0q20XsXRmFwqds6Keswo7XgSnwCRqzciMUD35mCk56QPI6b+6DvFU6U583DZ/Ay2UmfDSaPRWx270jrgbX+jbduetO3lqK9+7fiaV9LI7mfO3CrA79pl35bLWEro+NBWEre9D6e/P75QgvlnHPfJy88rDZqbToipyAdj1ioT1eYm73jZAzY5d0uFuaB0CMFxuVQPs0BpHw9eGuXhqB9DohzfslG01Mmyt/PNVzwubimNucHjJqbIcpUHpYJlAB0ebwlRB2k5DoJHeHZdk6c0yISCQeVJ2T4D/vm86L/OApFN2lgA0udQqJFFnw4o5c3/su8LLRqqQ1UqjS/ZPNEugtoYGlO84A7NMbYLn/4hpoiEOT4Pg+DnpRnwfQL3M67DxO/5WL1PfxfOlKxGFFI43UrogBkJj6TOT3sKrB6aRgesR30hai8sPFTf0SQKkz4TPpB0nRTybwh63LKajCovkSI3iTdC7eMjPjVZkOF6Yclre/3Ep90lxXWSjDe/5JduyC64IE7pNQJeKwmdmA6ZplxPc2ecO3l5cLyowEGf6SdbRwYYBGMRSe4RG6Qy6Xp9RZZkO7i1SD9petOu8mSHPLuy7KB4c/6LyGw743d/EMp3Eg7ZrRO0cXE5v8OzF4RcbovdZP1DL60I8QvW/j43i5Mry3H8uNYT7o",
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 12000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  },
  "confirmations": 0,
  "in_mempool": true
}

Response fields

tx Transaction
Full transaction object. NOTE: byte arrays are encoded as JSON arrays of integers (Go default encoding).
tx.version integer (0 to 255)
tx.inputs array of TxInput or null
Transaction inputs. null for a coinbase transaction read from the chain.
tx.inputs[].key_image array of 32 bytes
tx.inputs[].ring_members array of array of 32 bytes
Ring member one-time public keys (real + decoys)
tx.inputs[].ring_commitments array of array of 32 bytes
Pedersen commitments for each ring member
tx.inputs[].pseudo_output array of 32 bytes
tx.inputs[].ring_signature string
Base64-encoded ring signature bytes
tx.outputs array of TxOutput
tx.outputs[].commitment array of 32 bytes
tx.outputs[].public_key array of 32 bytes
tx.outputs[].range_proof string
Base64-encoded Bulletproof range proof bytes (omitted in some contexts)
tx.outputs[].encrypted_amount array of 8 bytes
tx.outputs[].encrypted_memo array of 128 bytes
tx.fee integer
Fee in atomic units
tx.tx_public_key array of 32 bytes
block_height integer
Block height containing the tx (absent when in_mempool is true)
confirmations integer
Number of confirmations (0 when in mempool)
in_mempool boolean
True if the tx is unconfirmed in the mempool

Errors

400 { "error": "hash must be 64 hex characters" }
404 { "error": "transaction not found" }
GET/api/mempool public, no token

Returns mempool transaction count, total size in bytes, and fee statistics (min, max, average) in atomic units.

Response 200

{
  "count": 5,
  "size_bytes": 8420,
  "generation": 42,
  "min_fee": 10000,
  "max_fee": 25000,
  "avg_fee": 14600.0
}

Response fields

count integer
Number of unconfirmed transactions
size_bytes integer
Total serialized size in bytes
generation integer
Monotonic content generation; changes whenever transactions are added or removed
min_fee integer
Lowest fee in the mempool (atomic units, 0 when empty)
max_fee integer
Highest fee in the mempool (atomic units, 0 when empty)
avg_fee number
Average fee (atomic units, 0 when empty)
GET/api/mempool/txs public, no token

Returns all currently unconfirmed mempool transactions as full transaction objects in a JSON array. Each item matches the same `Transaction` schema returned by `GET /api/tx/{hash}`. Returns an empty array when the mempool is empty.

Response 200 — Mempool has transactions

[
  {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 12000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  }
]

Response 200 — Mempool is empty

[]
POST/api/checkpoints/save

Appends checkpoints (block height + hash, every 100 blocks) to `<dataDir>/checkpoints.dat`, resuming after the last one already written. Only finalized heights are written: up to the chain tip minus 100 blocks, rounded down to a multiple of 100. If no height is final yet, it writes nothing and returns `message: "chain is empty, nothing to save"`. Checkpoints let nodes fast-sync by skipping proof-of-work re-verification at or below the checkpointed height. Requires Bearer token.

Response 200

{
  "written": 3,
  "chain_height": 14207,
  "last_checkpoint": 14200,
  "path": "/home/user/.blocknet/checkpoints.dat"
}

Response fields

written integer
Number of checkpoints appended this call
chain_height integer
Current chain tip height
last_checkpoint integer
Height of the highest checkpoint on disk
path string
Path to the checkpoints file
message string
Present when nothing was written (empty chain or already up to date)

Errors

401 unauthorized
500 { "error": "failed to create data dir" }
500 { "error": "failed to open checkpoints file" }
500 { "error": "failed to write checkpoint" }
POST/api/checkpoints/load

Downloads the checkpoints file if not present, loads it, and registers the checkpoints as trusted so blocks at or below the maximum checkpoint height can skip proof-of-work re-verification during sync. Requires Bearer token.

Response 200

{
  "downloaded": true,
  "loaded": 142,
  "max_height": 14200,
  "chain_height": 5000,
  "fast_sync": true
}

Response fields

downloaded boolean
True if the file was fetched from the remote this call
loaded integer
Number of checkpoints loaded
max_height integer
Highest checkpoint height
chain_height integer
Current chain tip height
fast_sync boolean
True if the chain is below max_height and can fast-sync
message string
Present when no checkpoints were found

Errors

401 unauthorized
500 { "error": "failed to load checkpoints" }
502 { "error": "failed to fetch checkpoints" }
GET/api/certify

Walks the entire chain and checks every block's difficulty (LWMA), timestamp rules, and prev-hash linkage. This is an arithmetic-only check (no Argon2id PoW re-hashing), so it completes in seconds. Returns all violations found, or confirms the chain is clean.

Response 200 — Chain is clean

{
  "height": 14207,
  "clean": true
}

Response 200 — Violations found

{
  "height": 14207,
  "clean": false,
  "violations": [
    {
      "height": 8042,
      "message": "difficulty mismatch: stored 5678, expected 1234"
    },
    {
      "height": 8043,
      "message": "timestamp 1708531200 <= median 1708531260"
    }
  ]
}

Response fields

height integer
Number of blocks checked
clean boolean
True if no violations were found
violations array of object
List of violations. Omitted when the chain is clean.
violations[].height integer
Block height where the violation was detected
violations[].message string
Description of the violation

Errors

401 unauthorized

# network

Peer connectivity and ban management

GET/api/peers public, no token

Returns the list of currently connected libp2p peers with their peer IDs and remote multiaddrs.

Response 200

{
  "count": 3,
  "peers": [
    {
      "peer_id": "12D3KooWBLUPjZgAoizTkbNJvHxgHkb2tF1saUhmKBzFfo8zZLfj",
      "addrs": [
        "/ip4/203.0.113.10/tcp/28080"
      ]
    },
    {
      "peer_id": "12D3KooWN7z3vEqr4C8pMk2axGfRbLVJzqPEasiY4L9DMqFjVnGq",
      "addrs": [
        "/ip4/198.51.100.22/tcp/28080"
      ]
    },
    {
      "peer_id": "12D3KooWQe1RkTvGvbFYm6G3bNzdpJRcmAQA4f9bUDkYcFWLdM4P",
      "addrs": [
        "/ip4/192.0.2.5/tcp/28080"
      ]
    }
  ]
}

Response fields

count integer
peers array of PeerEntry
peers[].peer_id string
libp2p peer ID
peers[].addrs array of string
Remote multiaddrs of active connections to this peer
GET/api/peers/banned public, no token

Returns the list of banned peers with ban reason, count, permanence, and expiry time.

Response 200

{
  "count": 2,
  "banned": [
    {
      "peer_id": "12D3KooWMalicious111111111111111111111111111111111111",
      "addrs": [
        "/ip4/203.0.113.50/tcp/28080"
      ],
      "reason": "sent invalid block",
      "ban_count": 3,
      "permanent": false,
      "expires_at": "2026-02-06T19:30:00Z"
    },
    {
      "peer_id": "12D3KooWMalicious222222222222222222222222222222222222",
      "addrs": [
        "/ip4/198.51.100.99/tcp/28080"
      ],
      "reason": "excessive invalid transactions",
      "ban_count": 5,
      "permanent": true
    }
  ]
}

Response fields

count integer
banned array of BanEntry
banned[].peer_id string
libp2p peer ID
banned[].addrs array of string
Last known multiaddrs at ban time
banned[].reason string
Human-readable ban reason
banned[].ban_count integer
Number of times this peer has been banned
banned[].permanent boolean
True if ban does not expire
banned[].expires_at string
RFC3339 expiry timestamp (omitted when permanent)

# wallet

Balance, address, history, and sending. The node always starts without a wallet. Call `POST /api/wallet/load`, `/api/wallet/create` or `/api/wallet/import` first. Wallet files live in the folder of the `wallet_file` setting. Most other wallet endpoints need a loaded, unlocked wallet. `lock`, `unlock`, `send/status` and `send/advanced/status` do not need it unlocked.

POST/api/wallet/load

Loads an existing wallet from disk, decrypting with the given password. The node always starts without a wallet, so a client calls this (or create/import) first. If `filepath` is provided, only the basename is used and the file is resolved in the folder of the `wallet_file` setting; if omitted, `wallet_file` itself is loaded. After loading, the wallet is unlocked, all wallet endpoints become available, and miner reward keys are pointed at this wallet. If blocks arrived while the wallet was offline, a background scan is started automatically — check `synced_height` vs `chain_height` in the response or the balance endpoint to track progress. Returns 409 while a wallet is loaded or another load is running. After `POST /api/wallet/unload` it can be called again.

Request

{
  "password": "my-wallet-passphrase",
  "filepath": "mywallet.dat"
}
password required string, min length 3
Wallet encryption password (min 3 characters). Used to decrypt the wallet file.
filepath string
Optional wallet file name. Only the base name is used: any folder part is dropped. The file goes in the folder of the `wallet_file` setting (see `PATCH /api/settings`). Defaults to the `wallet_file` setting.

Response 200

{
  "loaded": true,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "mywallet.dat",
  "synced_height": 4200,
  "chain_height": 9800
}

Response fields

loaded boolean
address string
Wallet stealth address (base58-encoded)
filename string
Basename of the loaded wallet file
synced_height integer
Wallet's last scanned block height
chain_height integer
Current chain tip height. If synced_height < chain_height, a background scan is in progress.

Errors

400 { "error": "invalid JSON body" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filepath" }
401 { "error": "wrong password" }
401 unauthorized
403 { "error": "wallet file not readable (permission denied)" }
404 { "error": "wallet file not found" }
409 { "error": "wallet already loaded" }
422 { "error": "wallet file is corrupted" }
500 { "error": "internal error" }
POST/api/wallet/create

Generates a new wallet with a fresh BIP39 seed, encrypts it with the given password, and saves it to disk. The wallet is loaded and unlocked in the running daemon. Since this is a brand-new wallet, no chain scanning is needed — the synced height is set to the current chain tip. Can only be called when no wallet is currently loaded (returns 409 otherwise). If `filename` is provided, only the basename is used and the file is placed in the folder of the `wallet_file` setting; if omitted, `wallet_file` itself is used. Will not overwrite an existing file.

Request

{
  "password": "my-wallet-passphrase"
}
password required string, min length 3
Encryption password for the wallet file (min 3 characters).
filename string
Optional wallet file name. Only the base name is used: any folder part is dropped. The file goes in the folder of the `wallet_file` setting (see `PATCH /api/settings`). Defaults to the `wallet_file` setting.

Response 200

{
  "created": true,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "wallet.dat"
}

Response fields

created boolean
address string
Wallet stealth address (base58-encoded)
filename string
Basename of the created wallet file

Errors

400 { "error": "invalid JSON body" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filename" }
401 unauthorized
409 { "error": "wallet already loaded" }
409 { "error": "wallet file already exists: wallet.dat" }
500 { "error": "internal error" }
POST/api/wallet/unload

Locks and unloads the currently loaded wallet, releasing all in-memory key material. After this call the node returns to the same state as before load was called — wallet endpoints that need a wallet return 503 until one is loaded again with load, create or import. Mining reward keys are cleared. No request body required.

Response 200

{
  "unloaded": true
}

Response fields

unloaded boolean

Errors

401 unauthorized
503 { "error": "no wallet loaded" }
POST/api/wallet/import

Creates a new wallet from exactly one source: a BIP39 12-word recovery seed (`mnemonic`), hex private keys (`spend_priv` + `view_priv`), or a view-only key pair (`spend_pub` + `view_priv`, which makes a wallet that can watch but not spend). It encrypts it with the given password, and saves it to disk. The wallet is loaded and unlocked in the running daemon and a background scan of the entire blockchain is started to find outputs belonging to this seed. Check `synced_height` vs `chain_height` in the response or the balance endpoint to track progress. Can only be called when no wallet is currently loaded (returns 409 otherwise). If `filename` is provided, only the basename is used and the file is placed in the same directory as the folder of the `wallet_file` setting; if omitted, the default wallet path is used. Will not overwrite an existing file.

Request

{
  "mnemonic": "abandon ability able about above absent absorb abstract absurd abuse access accident",
  "password": "my-wallet-passphrase",
  "filename": "restored.dat"
}
mnemonic string
BIP39 12-word recovery phrase
spend_priv string
Spend private key, 64 hex chars. Use with view_priv.
view_priv string
View private key, 64 hex chars. Use with spend_priv, or with spend_pub for a view-only wallet.
spend_pub string
Spend public key, 64 hex chars. Use with view_priv for a view-only wallet.
password required string, min length 3
Encryption password for the wallet file (min 3 characters)
filename string
Optional wallet file name. Only the base name is used: any folder part is dropped. The file goes in the folder of the `wallet_file` setting (see `PATCH /api/settings`). Defaults to the `wallet_file` setting.

Response 200

{
  "imported": true,
  "view_only": false,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "restored.dat",
  "synced_height": 0,
  "chain_height": 9800
}

Response fields

imported boolean
view_only boolean
True when the wallet was imported from spend_pub + view_priv
address string
Wallet stealth address (base58-encoded)
filename string
Basename of the created wallet file
synced_height integer
Wallet's last scanned block height (0 for fresh import)
chain_height integer
Current chain tip height. A background scan is in progress until synced_height catches up.

Errors

400 { "error": "invalid JSON body" }
400 { "error": "provide exactly one of: mnemonic, spend_priv + view_priv, or spend_pub + view_priv (view-only)" }
400 { "error": "invalid mnemonic phrase" }
400 { "error": "invalid spend_priv: expected 64 hex chars" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filename" }
401 unauthorized
409 { "error": "wallet already loaded" }
409 { "error": "wallet file already exists: restored.dat" }
500 { "error": "internal error" }
GET/api/wallet/balance

Returns spendable, pending, and total confirmed balance in atomic units (1 BNT = 100,000,000 atomic units). Spendable counts only mature outputs: coinbase outputs need coinbase maturity, and regular outputs need 10 confirmations. Also includes an optional UX-only `pending_unconfirmed` amount (typically change from your most recent send) plus an estimated unlock ETA in seconds, assuming ~5 minute blocks. If the wallet is more than 100 blocks behind the chain (background scan in progress), balance numbers are omitted and `scanning: true` is returned with progress info instead. Requires unlocked wallet.

Response 200

{
  "spendable": 1250000000,
  "pending": 72325093035,
  "pending_unconfirmed": 0,
  "pending_unconfirmed_eta": 0,
  "total": 73575093035,
  "outputs_total": 14,
  "outputs_unspent": 9,
  "chain_height": 14207,
  "scanning": false,
  "synced_height": 14207,
  "memo_decrypt_failures": 0,
  "memo_decrypt_last_height": 0
}

Response fields

scanning boolean
True if wallet is more than 100 blocks behind the chain (background scan in progress)
synced_height integer
Wallet's last scanned block height
chain_height integer
Current chain tip height
spendable integer
Mature, spendable balance in atomic units (omitted while scanning)
pending integer
Unspent outputs that are not mature yet: coinbase outputs before maturity and regular outputs with fewer than 10 confirmations (atomic units, omitted while scanning)
pending_unconfirmed integer
Unconfirmed expected credits not yet scanned from a block (typically change from your most recent send). UX-only; not part of confirmed balance totals (atomic units). Omitted while scanning.
pending_unconfirmed_eta integer
Estimated seconds until `pending_unconfirmed` becomes spendable, assuming ~5 minute blocks and requiring next block + 10 confirmations. 0 when no pending_unconfirmed. Omitted while scanning.
total integer
Total unspent balance: spendable + pending (atomic units, omitted while scanning)
outputs_total integer
Total output count (including spent, omitted while scanning)
outputs_unspent integer
Currently unspent output count (omitted while scanning)
memo_decrypt_failures integer
Number of outputs where memo decryption failed (omitted while scanning)
memo_decrypt_last_height integer
Block height of the most recent memo decryption failure (omitted while scanning)

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/address

Returns the wallet's public stealth address (base58 of spend_pub || view_pub || 4-byte checksum, 68 bytes). Senders derive unique one-time addresses from this. Requires unlocked wallet.

Response 200

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "view_only": false
}

Response fields

address string
Base58-encoded stealth address (spend_pub || view_pub || 4-byte checksum, 68 raw bytes)
view_only boolean
True if this wallet can only scan for incoming funds, not spend

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/history

Returns every wallet output, spent and immature ones included, with amounts, block heights, coinbase flag, and spent status. Requires unlocked wallet.

Response 200

{
  "count": 3,
  "outputs": [
    {
      "txid": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "output_index": 0,
      "amount": 72325093035,
      "block_height": 14200,
      "is_coinbase": true,
      "spent": false
    },
    {
      "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "output_index": 0,
      "amount": 500000000,
      "block_height": 14100,
      "is_coinbase": false,
      "spent": false
    },
    {
      "txid": "f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
      "output_index": 1,
      "amount": 750000000,
      "block_height": 13990,
      "is_coinbase": false,
      "spent": true,
      "spent_height": 14050
    }
  ]
}

Response fields

count integer
outputs array of OutputEntry
outputs[].txid string
Transaction hash (hex)
outputs[].output_index integer
Output position within the transaction
outputs[].amount integer
Amount in atomic units
outputs[].block_height integer
Block height where this output was confirmed
outputs[].is_coinbase boolean
True if this is a mining reward output
outputs[].spent boolean
True if already used as an input
outputs[].spent_height integer
Block height where spent (omitted if unspent, 0 means unspent)
outputs[].memo_hex string, max length 248
Hex-encoded decrypted memo payload (present only when the output carried a non-empty memo). Max 248 hex chars (124 bytes).

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/history/page

Same wallet outputs as GET /api/wallet/history, but returned newest-first in deterministic pages so busy wallets do not have to ship their entire output set at once. Ordering is stable across pages: pending outputs (block_height 0) first, then descending block height, then txid, then output index. Page with limit/offset and use the total field to know when to stop. The unpaginated /api/wallet/history endpoint remains available and unchanged. Requires unlocked wallet.

Parameters

limit query · integer (0 to 500)
Maximum number of outputs to return. Values above 500 are clamped to 500.
offset query · integer ≥ 0
Number of sorted outputs to skip.
order query · string
Sort direction. desc is newest-first (pending, then descending block height); asc reverses it. Ties break by txid then output index.

Response 200

{
  "count": 2,
  "total": 4213,
  "limit": 20,
  "offset": 0,
  "order": "desc",
  "outputs": [
    {
      "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "output_index": 0,
      "amount": 500000000,
      "block_height": 14100,
      "is_coinbase": false,
      "spent": false
    },
    {
      "txid": "f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
      "output_index": 1,
      "amount": 750000000,
      "block_height": 13990,
      "is_coinbase": false,
      "spent": true,
      "spent_height": 14050
    }
  ]
}

Response fields

count integer
Number of outputs returned in this response
total integer
Total wallet outputs available before pagination
limit integer
Effective page size
offset integer
Requested page offset
order string
Effective sort order
outputs array of OutputEntry
outputs[].txid string
Transaction hash (hex)
outputs[].output_index integer
Output position within the transaction
outputs[].amount integer
Amount in atomic units
outputs[].block_height integer
Block height where this output was confirmed
outputs[].is_coinbase boolean
True if this is a mining reward output
outputs[].spent boolean
True if already used as an input
outputs[].spent_height integer
Block height where spent (omitted if unspent, 0 means unspent)
outputs[].memo_hex string, max length 248
Hex-encoded decrypted memo payload (present only when the output carried a non-empty memo). Max 248 hex chars (124 bytes).

Errors

400 { "error": "order must be asc or desc" }
401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/sends

Returns outbound payments recorded by the loaded wallet. This is wallet-local send metadata: recipient addresses and memos are available because the wallet recorded them at send time. Chain lookup is used only to annotate each tx as confirmed, mempool, or not_found. Requires unlocked wallet.

Parameters

limit query · integer (0 to 500)
Maximum number of sends to return. Values above 500 are clamped to 500.
offset query · integer ≥ 0
Number of sorted send records to skip.
order query · string
Sort direction by wallet-recorded send timestamp. Ties are sorted by txid.

Response 200

{
  "count": 1,
  "total": 18,
  "limit": 50,
  "offset": 0,
  "order": "desc",
  "sends": [
    {
      "txid": "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4",
      "timestamp": 1778085481,
      "recorded_height": 21091,
      "chain_state": "confirmed",
      "confirmations": 8,
      "in_mempool": false,
      "fee": 5698010,
      "total_amount": 3250000000000,
      "recipients": [
        {
          "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
          "amount": 3250000000000,
          "memo_hex": "696e766f696365202338343231"
        }
      ]
    }
  ]
}

Response fields

count integer
Number of sends returned in this response
total integer
Total wallet send records available before pagination
limit integer
Effective page size
offset integer
Requested page offset
order string
Effective sort order
sends array of WalletSendEntry
sends[].txid string
Transaction hash (64 hex chars)
sends[].timestamp integer
Unix timestamp recorded by the wallet when the send was created
sends[].recorded_height integer
Chain height recorded by the wallet at send time. This is not necessarily the confirmation block height.
sends[].chain_state string
Current daemon lookup state for the transaction hash
sends[].confirmations integer
Confirmation count when chain_state=confirmed; otherwise 0
sends[].in_mempool boolean
True when the transaction is currently in the daemon mempool
sends[].fee integer
Fee recorded by the wallet in atomic units
sends[].total_amount integer
Sum of recorded recipient amounts in atomic units
sends[].recipients array of WalletSendHistoryRecipient
Recipient metadata recorded locally by the sending wallet
sends[].recipients[].address string
Recipient address recorded by the sending wallet
sends[].recipients[].amount integer
Recipient amount in atomic units
sends[].recipients[].memo_hex string, max length 248
Hex-encoded memo recorded for this recipient, if any

Errors

400 { "error": "order must be asc or desc" }
401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/outputs

Returns every output owned by the wallet with computed status (spent/unspent/pending), confirmations, type, cryptographic keys, and summary counts. Sorted ascending by block height, then txid, then output index. Requires unlocked wallet.

Response 200

{
  "chain_height": 14207,
  "synced_height": 14207,
  "total": 3,
  "spent": 1,
  "unspent": 1,
  "pending": 1,
  "outputs": [
    {
      "txid": "f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
      "output_index": 1,
      "amount": 750000000,
      "status": "spent",
      "type": "regular",
      "confirmations": 217,
      "block_height": 13990,
      "spent_height": 14050,
      "one_time_pub": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "commitment": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "output_index": 0,
      "amount": 500000000,
      "status": "unspent",
      "type": "regular",
      "confirmations": 107,
      "block_height": 14100,
      "one_time_pub": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5",
      "commitment": "e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6"
    },
    {
      "txid": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "output_index": 0,
      "amount": 72325093035,
      "status": "pending",
      "type": "coinbase",
      "confirmations": 7,
      "block_height": 14200,
      "one_time_pub": "b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9",
      "commitment": "f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
    }
  ]
}

Response fields

chain_height integer
Chain height used for confirmations/maturity
synced_height integer
Last block height scanned by the wallet
total integer
Total output count
spent integer
Number of spent outputs
unspent integer
Number of mature unspent outputs
pending integer
Number of immature (not yet spendable) outputs
outputs array of OutputDetail
outputs[].txid string
Transaction hash (hex)
outputs[].output_index integer
Output position within the transaction
outputs[].amount integer
Amount in atomic units
outputs[].status string
Computed status: spent (already used), unspent (mature, spendable), pending (immature, awaiting confirmations)
outputs[].type string
Output type: coinbase (mining reward) or regular (transfer)
outputs[].confirmations integer
Number of confirmations (chain_height - block_height)
outputs[].block_height integer
Block height where this output was confirmed
outputs[].spent_height integer
Block height where spent (omitted if unspent)
outputs[].one_time_pub string
One-time public key (64 hex chars)
outputs[].commitment string
Pedersen commitment (64 hex chars)
outputs[].memo_hex string, max length 248
Hex-encoded decrypted memo payload (present only when the output carried a non-empty memo). Max 248 hex chars (124 bytes).

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
POST/api/wallet/send

Builds a privacy-preserving transaction with one or more recipients using RingCT (Pedersen commitments, Bulletproofs range proofs, CLSAG ring signatures with fixed ring size 16), submits to mempool, and broadcasts via Dandelion++. Fee is calculated as max(min_fee, fee_per_byte * tx_size). Note: multi-recipient transactions link all recipients within the same transaction from a graph-analysis perspective. Requires unlocked full wallet (not view-only).

Parameters

Idempotency-Key header · string, max length 128
Optional idempotency key for retry-safe client behavior (max 128 chars). A repeat with the same key and the same JSON body replays the stored response, success or error, for 30 days (up to 4096 keys). 429 responses are not stored. Reusing a key with a different body returns 409. If the node stopped while the request was running, its outcome is unknown: the key shows state `accepted` in the status route and returns 409 with an interrupted message until it expires.

Request

{
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}
recipients required array of SendRecipient
One or more recipients. Total amount must not exceed spendable balance.
recipients[].address required string
Recipient stealth address (base58-encoded spend_pub || view_pub) or Blocknet ID handle
recipients[].amount required integer ≥ 1
Amount in atomic units (1 BNT = 100,000,000)
recipients[].memo_text string, max length 124
Optional UTF-8 memo text. Mutually exclusive with memo_hex. Server enforces max 124 BYTES after UTF-8 encoding.
recipients[].memo_hex string, max length 248
Optional memo payload in hex. Mutually exclusive with memo_text. Max 248 hex chars (124 bytes).
dry_run boolean
If true, estimate the fee and return a preview without broadcasting. No transaction is created.

Response 200

{
  "txid": "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4",
  "fee": 15000,
  "change": 734850000,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ],
  "dry_run": false
}

Response fields

txid string
Transaction hash (64 hex chars). Absent when dry_run is true.
fee integer
Fee paid (or estimated) in atomic units
change integer
Change returned to wallet in atomic units
dry_run boolean
Whether this was a dry run (fee preview only)
input_total integer
Total value of selected inputs in atomic units. Present when dry_run is true.
input_count integer
Number of inputs selected. Present when dry_run is true.
recipients array of SendRecipientResult
Per-recipient results including any handle resolution info and memos
recipients[].address string
Address as provided in the request
recipients[].amount integer
Amount sent in atomic units
recipients[].memo_hex string
Hex-encoded memo (if provided)
recipients[].resolved_handle string
Handle that was resolved (if address was a Blocknet ID)
recipients[].resolved_address string
Address the handle resolved to
recipients[].resolver_verified boolean
Whether the handle is verified

Errors

400 { "error": "recipient 0: invalid address" }
400 { "error": "recipient 0: amount must be greater than 0" }
400 { "error": "insufficient spendable balance: have 2.5 BNT, need 5 BNT" }
400 { "error": "insufficient spendable balance: have 2.5 BNT spendable + 3 BNT pending (confirming), need 5 BNT" }
400 { "error": "idempotency key too long" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot send" }
409 { "error": "idempotency key reuse with different request" }
409 { "error": "idempotency key in progress" }
409 { "error": "mempool rejected: double-spend: key image already in mempool (tx 1a2b3c4d5e6f7a8b...)" }
409 { "error": "request was interrupted by a node restart and its outcome is unknown; check GET /api/wallet/sends before sending again with a new idempotency key" }
422 { "error": "mempool rejected: mempool full" }
429 { "error": "send rate limit exceeded" }
429 { "error": "send busy, retry later" }
500 { "error": "failed to select ring members: not enough outputs for ring (need 15, have 9)" }
503 { "error": "no wallet loaded" }
GET/api/wallet/send/status

Returns the persisted status of a previous POST /api/wallet/send request for the provided idempotency key. Unresolved accepted requests survive daemon restart and return state=accepted instead of silently disappearing.

Parameters

idempotency_key required query · string, max length 128
The exact Idempotency-Key header value used on the original POST /api/wallet/send request.

Response fields

idempotency_key string
The Idempotency-Key value being queried.
state string
Persisted send state. accepted means the daemon durably recorded the request but does not have a terminal result yet.
created_at string
When the daemon first accepted the idempotent request.
updated_at string
When the daemon last updated the persisted state.
original_status integer
HTTP status returned by the original send request when a terminal result exists.
error string
Terminal error message when state=failed.
result SendResponse
result.txid string
Transaction hash (64 hex chars). Absent when dry_run is true.
result.fee integer
Fee paid (or estimated) in atomic units
result.change integer
Change returned to wallet in atomic units
result.dry_run boolean
Whether this was a dry run (fee preview only)
result.input_total integer
Total value of selected inputs in atomic units. Present when dry_run is true.
result.input_count integer
Number of inputs selected. Present when dry_run is true.
result.recipients array of SendRecipientResult
Per-recipient results including any handle resolution info and memos
result.recipients[].address string
Address as provided in the request
result.recipients[].amount integer
Amount sent in atomic units
result.recipients[].memo_hex string
Hex-encoded memo (if provided)
result.recipients[].resolved_handle string
Handle that was resolved (if address was a Blocknet ID)
result.recipients[].resolved_address string
Address the handle resolved to
result.recipients[].resolver_verified boolean
Whether the handle is verified

Errors

400 { "error": "idempotency_key is required" }
401 unauthorized
POST/api/wallet/send/advanced

Builds a transaction with one or more recipients using caller-specified inputs (coin control). The caller selects which outputs to spend by referencing txid + output_index pairs from GET /api/wallet/outputs. No automatic input selection is performed. Supports dry_run mode to preview fee and change before broadcasting. Optional change_split (1-4) distributes change across multiple outputs for improved back-to-back send concurrency at the cost of reduced privacy. Same fee calculation, ring signatures, and Dandelion++ broadcast as the standard send endpoint.

Parameters

Idempotency-Key header · string, max length 128
Optional idempotency key for retry-safe client behavior (max 128 chars). A repeat with the same key and the same JSON body replays the stored response, success or error, for 30 days (up to 4096 keys). 429 responses are not stored. Reusing a key with a different body returns 409. If the node stopped while the request was running, its outcome is unknown: the key shows state `accepted` in the status route and returns 409 with an interrupted message until it expires.

Request

recipients required array of SendRecipient
One or more recipients. The sum of specified inputs must cover total amount plus the computed fee.
recipients[].address required string
Recipient stealth address (base58-encoded spend_pub || view_pub) or Blocknet ID handle
recipients[].amount required integer ≥ 1
Amount in atomic units (1 BNT = 100,000,000)
recipients[].memo_text string, max length 124
Optional UTF-8 memo text. Mutually exclusive with memo_hex. Server enforces max 124 BYTES after UTF-8 encoding.
recipients[].memo_hex string, max length 248
Optional memo payload in hex. Mutually exclusive with memo_text. Max 248 hex chars (124 bytes).
inputs required array of object
Specific outputs to use as transaction inputs. No automatic selection is performed. Each output must be unspent, mature, unreserved, and not have its key image in the mempool.
inputs[].txid required string
Transaction ID of the output to spend (64 hex chars). Obtain from GET /api/wallet/outputs.
inputs[].output_index required integer ≥ 0
Output index within the transaction. Obtain from GET /api/wallet/outputs.
dry_run boolean
If true, validate inputs and compute fee/change without building or broadcasting the transaction. The reserved inputs are released immediately.
change_split integer (1 to 4)
Number of change outputs to create (1-4). The core treats 0 or less as 1. Higher values improve back-to-back send concurrency at the cost of reduced privacy (deviates from the standard 2-output transaction fingerprint). The actual count is min(change_split, change in atomic units), and 0 when there is no change.

Response 200 — Transaction broadcast

{
  "txid": "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
  "fee": 15000,
  "change": 234850000,
  "change_split": 1,
  "input_total": 735000000,
  "input_count": 2,
  "dry_run": false,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}

Response 200 — Dry-run preview

{
  "fee": 15000,
  "change": 234850000,
  "change_split": 1,
  "input_total": 735000000,
  "input_count": 1,
  "dry_run": true,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}

Response fields

txid string
Transaction hash (64 hex chars). Absent in dry_run mode.
fee integer
Fee in atomic units (computed as max(min_fee, fee_per_byte * estimated_size))
change integer
Change returned to wallet in atomic units
change_split integer
Number of change outputs: min(change_split, change), and 0 when there is no change. For a dry run, the number that would be created.
input_total integer
Sum of all specified input amounts in atomic units
input_count integer
Number of inputs used
dry_run boolean
True if this was a dry-run preview (no transaction was broadcast)
recipients array of SendRecipientResult
Per-recipient results including any handle resolution info and memos
recipients[].address string
Address as provided in the request
recipients[].amount integer
Amount sent in atomic units
recipients[].memo_hex string
Hex-encoded memo (if provided)
recipients[].resolved_handle string
Handle that was resolved (if address was a Blocknet ID)
recipients[].resolved_address string
Address the handle resolved to
recipients[].resolver_verified boolean
Whether the handle is verified

Errors

400 { "error": "inputs array is required for coin control" }
400 { "error": "input 0: output not found (txid b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4, index 0)" }
400 { "error": "input 1: immature, has 3 confirmations, needs 10 (txid b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4, index 0)" }
400 { "error": "input 0: key image already in mempool (txid b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4, index 0)" }
400 { "error": "input 2: duplicate of input 0 (txid b3c4d5...a2b3c4, index 0)" }
400 { "error": "insufficient input amount: inputs total 0.001 BNT, need at least 5.00015 BNT (amount 5 BNT + fee 0.00015 BNT)" }
400 { "error": "insufficient funds after fee adjustment: have 0.001 BNT need 5.00015 BNT" }
400 { "error": "too many inputs (max 256)" }
400 { "error": "input 0: txid must be 64 hex characters" }
400 { "error": "input 0: output already spent (txid b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4, index 0)" }
400 { "error": "change_split must be 1-4" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot send" }
409 { "error": "idempotency key reuse with different request" }
409 { "error": "idempotency key in progress" }
409 { "error": "mempool rejected: double-spend: key image already in mempool (tx 1a2b3c4d5e6f7a8b...)" }
409 { "error": "request was interrupted by a node restart and its outcome is unknown; check GET /api/wallet/sends before sending again with a new idempotency key" }
422 { "error": "mempool rejected: mempool full" }
429 { "error": "send rate limit exceeded" }
429 { "error": "send busy, retry later" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
GET/api/wallet/send/advanced/status

Returns the persisted status of a previous POST /api/wallet/send/advanced request for the provided idempotency key. This endpoint is intended for payout systems that need to recover the txid after a slow or timed-out send call.

Parameters

idempotency_key required query · string, max length 128
The exact Idempotency-Key header value used on the original POST /api/wallet/send/advanced request.

Response fields

idempotency_key string
The Idempotency-Key value being queried.
state string
Persisted send state. accepted means the daemon durably recorded the request but does not have a terminal result yet.
created_at string
When the daemon first accepted the idempotent request.
updated_at string
When the daemon last updated the persisted state.
original_status integer
HTTP status returned by the original send request when a terminal result exists.
error string
Terminal error message when state=failed.
result SendAdvancedResponse
result.txid string
Transaction hash (64 hex chars). Absent in dry_run mode.
result.fee integer
Fee in atomic units (computed as max(min_fee, fee_per_byte * estimated_size))
result.change integer
Change returned to wallet in atomic units
result.change_split integer
Number of change outputs: min(change_split, change), and 0 when there is no change. For a dry run, the number that would be created.
result.input_total integer
Sum of all specified input amounts in atomic units
result.input_count integer
Number of inputs used
result.dry_run boolean
True if this was a dry-run preview (no transaction was broadcast)
result.recipients array of SendRecipientResult
Per-recipient results including any handle resolution info and memos
result.recipients[].address string
Address as provided in the request
result.recipients[].amount integer
Amount sent in atomic units
result.recipients[].memo_hex string
Hex-encoded memo (if provided)
result.recipients[].resolved_handle string
Handle that was resolved (if address was a Blocknet ID)
result.recipients[].resolved_address string
Address the handle resolved to
result.recipients[].resolver_verified boolean
Whether the handle is verified

Errors

400 { "error": "idempotency_key is required" }
401 unauthorized
POST/api/wallet/sign

Signs an arbitrary message with the wallet's spend private key (Schnorr over Ristretto255). Useful for proving address ownership to external services ('login with Blocknet'). Response includes `Cache-Control: no-store`. Requires unlocked full wallet (not view-only).

Request

{
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200"
}
message required string, max length 1024
The message to sign (max 1024 bytes)

Response 200

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "signature": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200"
}

Response fields

address string
Wallet stealth address (base58-encoded)
signature string
Hex-encoded 64-byte Schnorr signature over Ristretto255 (128 hex chars)
message string
Echo of the signed message

Errors

400 { "error": "message is required" }
400 { "error": "message must be <= 1024 bytes" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot sign" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
POST/api/wallet/lock

Locks the wallet. All wallet endpoints (balance, address, history, send) will return 403 until unlocked. No request body required.

Response 200

{
  "locked": true
}

Response fields

locked boolean

Errors

401 unauthorized
503 { "error": "no wallet loaded" }
POST/api/wallet/unlock

Unlocks the wallet with the wallet password. Uses constant-time comparison (`crypto/subtle.ConstantTimeCompare`) to prevent timing attacks.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password

Response 200

{
  "locked": false
}

Response fields

locked boolean

Errors

400 { "error": "invalid JSON body" }
401 { "error": "wrong password" }
401 unauthorized
429 { "error": "too many unlock attempts; try again later" }
429 { "error": "unlock backoff active; retry later" }
503 { "error": "no wallet loaded" }
503 { "error": "unlock unavailable: password state not initialized" }
POST/api/wallet/seed

Returns the wallet's 12-word BIP39 recovery phrase. Requires Bearer token and wallet password confirmation. Response includes Cache-Control: no-store.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password

Response 200

{
  "mnemonic": "abandon ability able about above absent absorb abstract absurd abuse access accident",
  "words": [
    "abandon",
    "ability",
    "able",
    "about",
    "above",
    "absent",
    "absorb",
    "abstract",
    "absurd",
    "abuse",
    "access",
    "accident"
  ]
}

Response fields

mnemonic string
words array of string

Errors

400 { "error": "invalid JSON body" }
401 { "error": "incorrect password" }
403 { "error": "wallet is locked" }
404 { "error": "no recovery seed available" }
429 { "error": "too many attempts; try again later" }
429 { "error": "attempt backoff active; retry later" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
503 { "error": "seed unavailable: password state not initialized" }
POST/api/wallet/sync

Triggers a background rescan from the wallet's last synced height to the chain tip. Returns immediately with the current sync state. Check the balance endpoint to track progress. Requires loaded, unlocked wallet.

Response 200 — Background scan started

{
  "status": "scanning",
  "synced_height": 4200,
  "chain_height": 9800
}

Response 200 — Wallet already at chain tip

{
  "status": "already synced",
  "synced_height": 14207,
  "chain_height": 14207
}

Response fields

status string
'scanning' or 'already synced'
synced_height integer
Wallet's current synced height
chain_height integer
Current chain tip height

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
POST/api/wallet/prove

Derives the deterministic transaction private key to prove a transaction was sent by this wallet. The tx key can be shared with a recipient or third party to verify payment. Works on both full and view-only wallets (uses the view private key). Requires loaded, unlocked wallet. Response includes `Cache-Control: no-store`.

Request

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}
txid required string, min length 64, max length 64
Transaction hash (64 characters). Only the length is checked; a value that is not hex returns 404.

Response 200

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "tx_key": "f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1"
}

Response fields

txid string
Transaction hash (echoed back)
tx_key string
Transaction private key (64 hex characters). Share this to prove payment.

Errors

400 { "error": "txid must be 64 hex characters" }
400 { "error": "coinbase transactions have no sender proof" }
401 unauthorized
403 { "error": "wallet is locked" }
404 { "error": "transaction not found on chain" }
409 { "error": "transaction was not sent by this wallet" }
500 { "error": "failed to derive tx key" }
503 { "error": "no wallet loaded" }
POST/api/wallet/audit

Scans all wallet outputs and groups them by key image. Outputs that share a key image are duplicates caused by a historical self-send key derivation bug — only one output per group can ever be spent, the rest are permanently unspendable (burned). No request body required. Requires loaded, unlocked wallet.

Response 200 — No burned funds detected

{
  "total_outputs": 42,
  "unique_key_images": 42,
  "failed_key_images": 0,
  "duplicate_groups": null,
  "total_burned": 0,
  "burned_outputs": 0
}

Response 200 — Duplicate key images found

{
  "total_outputs": 42,
  "unique_key_images": 40,
  "failed_key_images": 0,
  "duplicate_groups": [
    {
      "key_image": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "outputs": [
        {
          "txid": "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4",
          "output_index": 0,
          "amount": 500000000,
          "spent": false,
          "block_height": 1200
        },
        {
          "txid": "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
          "output_index": 1,
          "amount": 100000000,
          "spent": false,
          "block_height": 1201
        }
      ],
      "burned_amount": 100000000,
      "unspendable_count": 1
    }
  ],
  "total_burned": 100000000,
  "burned_outputs": 1
}

Response fields

total_outputs integer
Total number of outputs in the wallet
unique_key_images integer
Number of unique key images derived
failed_key_images integer
Number of outputs where key image derivation failed
duplicate_groups array of object or null
Groups of outputs sharing a key image. null when none are found.
duplicate_groups[].key_image string
Shared key image (64 hex characters)
duplicate_groups[].outputs array of object
duplicate_groups[].outputs[].txid string
duplicate_groups[].outputs[].output_index integer
duplicate_groups[].outputs[].amount integer
Amount in atomic units
duplicate_groups[].outputs[].spent boolean
duplicate_groups[].outputs[].block_height integer
duplicate_groups[].burned_amount integer
Total burned amount in atomic units for this group
duplicate_groups[].unspendable_count integer
Number of unspendable outputs in this group
total_burned integer
Total burned amount across all duplicate groups (atomic units)
burned_outputs integer
Total number of unspendable outputs across all groups

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
POST/api/wallet/viewkeys

Returns the wallet's view-only key material (spend public key, view private key, view public key). These keys allow monitoring incoming funds and transaction history without spending ability. Requires Bearer token and wallet password confirmation. Response includes Cache-Control: no-store. Not available on view-only wallets.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password
create_file boolean
If true, also write a loadable view-only wallet file
file_password string
Password for the new view-only wallet file (min 3 characters, required when create_file is true). It must differ from the main wallet password.
filename string
Optional file name for the view-only wallet. `.wallet.dat` is added when missing. Only the base name is used: any folder part is dropped. The file goes in the folder of the `wallet_file` setting (see `PATCH /api/settings`). Defaults to the main wallet name with `.view` added, for example `main.view.wallet.dat`.

Response 200

{
  "spend_pub": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "view_priv": "f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1",
  "view_pub": "d4c3b2a1e5f6d7c8b9a0e1f2d3c4b5a6e7f8d9c0b1a2e3f4d5c6b7a8e9f0d1c2"
}

Response fields

spend_pub string
Spend public key (64 hex characters)
view_priv string
View private key (64 hex characters). This is the sensitive key — it grants read access to all transactions.
view_pub string
View public key (64 hex characters)
created boolean
True when a view-only wallet file was written (create_file)
filename string
Basename of the created view-only wallet file
path string
Full path of the created view-only wallet file
address string
Address of the created view-only wallet

Errors

400 { "error": "invalid JSON body" }
400 { "error": "file_password must be at least 3 characters" }
400 { "error": "invalid filename" }
400 { "error": "file_password must differ from the wallet password" }
401 { "error": "incorrect password" }
403 { "error": "wallet is locked" }
403 { "error": "this is already a view-only wallet" }
409 { "error": "wallet file already exists: main.view.wallet.dat" }
429 { "error": "too many attempts; try again later" }
429 { "error": "attempt backoff active; retry later" }
500 { "error": "failed to create view-only wallet" }
503 { "error": "no wallet loaded" }
503 { "error": "viewkeys unavailable: password state not initialized" }

# mining

Argon2id PoW miner control (~2GB RAM per thread)

GET/api/mining

Returns mining state, thread count, and stats (hashrate, hash count, blocks found, start time) when running. Hashrate and stat fields are omitted when the miner is stopped.

Response 200 — Miner is active

{
  "running": true,
  "threads": 4,
  "hashrate": 2.37,
  "hash_count": 8532,
  "blocks_found": 12,
  "started_at": "2026-02-06T14:00:00Z"
}

Response 200 — Miner is stopped

{
  "running": false,
  "threads": 4
}

Response fields

running boolean
Whether the miner is currently active
threads integer
Number of mining threads (always present)
hashrate number
Hashes per second (only present when running)
hash_count integer
Total hashes computed this session (only present when running)
blocks_found integer
Blocks mined this session (only present when running)
started_at string
RFC3339 miner start time (only present when running)

Errors

401 unauthorized
POST/api/mining/start

Starts the Argon2id proof-of-work miner. Load a wallet first: the miner pays the wallet that was loaded when the core started mining, and with no wallet the coinbase pays throwaway keys and the reward is lost. The miner waits until it has at least one peer. Each thread allocates ~2GB RAM. No request body required.

Response 200

{
  "running": true
}

Response fields

running boolean

Errors

401 unauthorized
409 { "error": "mining already running" }
POST/api/mining/stop

Stops the miner. No request body required.

Response 200

{
  "running": false
}

Response fields

running boolean

Errors

401 unauthorized
409 { "error": "mining not running" }
POST/api/mining/threads

Sets the number of mining threads. Each thread uses ~2GB RAM for Argon2id. Takes effect immediately if the miner is running.

Request

{
  "threads": 4
}
threads required integer ≥ 1
Number of mining threads, from 1 to the number of CPU cores. A larger value returns 400 "threads must be <= N".

Response 200

{
  "threads": 4
}

Response fields

threads integer

Errors

400 { "error": "threads must be >= 1" }
401 unauthorized
GET/api/mining/blocktemplate

Returns a complete block template ready for proof-of-work solving. Includes a pre-built coinbase transaction (paying to the loaded wallet by default, or to the optional `address` override), selected mempool transactions, and the computed merkle root. The `target` field (hex) is the PoW target that the Argon2id hash must be less than. The `header_base` field (hex) is the 92-byte serialized header without the nonce. PoW hash = Argon2id(password = nonce as 8 bytes little-endian, salt = header_base bytes), 32-byte output. The `block` field is the raw consensus form (PascalCase header keys, byte arrays as integer arrays), not the `GET /api/block` summary. The `template_id` field identifies this template and can be used with compact submit payloads (`template_id` + `nonce`) via `POST /api/mining/submitblock`. Its compact-submission lease expires at `template_expires_at_unix_ms`; clients may extend a live, current-tip lease with `POST /api/mining/renewtemplate`. The daemon retains at most 512 live leases for one tip and returns 503 rather than evicting a lease before its advertised expiry. Requires a loaded wallet (for coinbase keys) but does not require it to be unlocked.

Parameters

address query · string
Optional reward address override. If provided, the coinbase output pays to this stealth address instead of the loaded wallet address.

Response 200

{
  "block": {
    "header": {
      "Version": 1,
      "Height": 14208,
      "PrevHash": [0,0,195,165,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
      "MerkleRoot": [171,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
      "Timestamp": 1708531200,
      "Difficulty": 14208,
      "Nonce": 0
    },
    "transactions": [
      {
        "version": 1,
        "inputs": [],
        "outputs": [
          {
            "commitment": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            "public_key": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
            "range_proof": "",
            "encrypted_amount": [0,0,0,0,0,0,0,0],
            "encrypted_memo": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
          }
        ],
        "fee": 0,
        "tx_public_key": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
      }
    ]
  },
  "target": "0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
  "header_base": "0100000080370000...",
  "reward_address_used": "3wZc3y1Qw4eYpZtGJxkqkV6xG9c2oXHqfJjQ6b3sR5hYhJ8tZz1qvJ1mY1a9o7hV4pYwqH7xG5d5d8cJ8p4",
  "template_id": "7b3d2f4a9d11c8ef",
  "template_expires_at_unix_ms": 1738800840000,
  "mempool_generation": 17
}

Response fields

block required RawBlock
Complete block with nonce=0, in the raw consensus JSON form (not the `GET /api/block` summary). Submit back with a valid nonce via POST /api/mining/submitblock, or send only `template_id` + `nonce`.
block.header required RawBlockHeader
block.header.Version required integer
block.header.Height required integer
block.header.PrevHash required array of 32 bytes
block.header.MerkleRoot required array of 32 bytes
block.header.Timestamp required integer
Unix seconds
block.header.Difficulty required integer
block.header.Nonce required integer
PoW nonce, an unsigned 64-bit integer
block.transactions required array of Transaction
Coinbase first, then the selected mempool transactions
block.transactions[].version integer (0 to 255)
block.transactions[].inputs array of TxInput or null
Transaction inputs. null for a coinbase transaction read from the chain.
block.transactions[].outputs array of TxOutput
block.transactions[].fee integer
Fee in atomic units
block.transactions[].tx_public_key array of 32 bytes
target required string
Hex-encoded 32-byte PoW target (64 hex chars). The Argon2id hash for a nonce must be less than this value, compared as a big-endian number.
header_base required string
Hex-encoded 92-byte serialized header without the nonce. PoW = Argon2id(password = nonce as 8 bytes little-endian, salt = header_base bytes), 32-byte output. The nonce is not appended to header_base.
reward_address_used required string
The stealth address used for the coinbase output in this template. Mirrors the `address` query parameter if provided; otherwise it is the loaded wallet address.
template_id required string
Opaque template identifier used for compact submit payloads (`template_id` + `nonce`).
template_expires_at_unix_ms required integer
Unix timestamp in milliseconds when the compact-submission lease expires. Renew with POST /api/mining/renewtemplate before this instant to keep using the same template ID.
mempool_generation required integer
Exact mempool generation represented by the template transaction selection. Compare with /api/status mempool_generation to detect same-tip content changes.

Errors

400 { "error": "invalid address" }
401 unauthorized
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
503 { "error": "node is syncing" }
503 { "code": "mining_template_tip_changed", "error": "chain tip changed while building template, retry" }
503 { "code": "mining_template_cache_full", "error": "mining template cache full, retry after a lease expires or the chain tip changes" }
POST/api/mining/renewtemplate

Extends the compact-submission lease of an unexpired cached template. Renewal succeeds only while the template still builds directly on the current canonical chain tip. This does not rebuild or otherwise change the template.

Request

{
  "template_id": "7b3d2f4a9d11c8ef"
}
template_id required string
Unexpired template ID returned by GET /api/mining/blocktemplate.

Response 200

{
  "template_id": "7b3d2f4a9d11c8ef",
  "template_expires_at_unix_ms": 1738801140000
}

Response fields

template_id required string
The renewed template ID.
template_expires_at_unix_ms required integer
New compact-submission lease expiry as a Unix timestamp in milliseconds.

Errors

400 { "error": "invalid JSON body" }
401 unauthorized
404 { "error": "unknown or expired template_id" }
409 { "error": "template no longer builds on current tip" }
500 { "error": "internal error" }
POST/api/mining/submitblock

Accepts a solved block (from pool mining or an external miner). Send either the full block in the raw consensus form (see `RawBlock`, as returned in `GET /api/mining/blocktemplate`) or a compact `template_id` + `nonce`. The block must pass full validation: correct height, prev hash linking to the current tip, valid difficulty, valid Argon2id proof-of-work, valid merkle root, and valid transactions. On success the block is broadcast to peers and `mined_block` subscribers are notified. `accepted: true` is also returned when the block is valid but lands on a side chain. Rate limit: 2 requests/s per IP (burst 4), 2 at a time.

Request

Either a complete block object with a valid nonce, or a compact payload containing `template_id` and `nonce`.

Option: RawBlock

header required RawBlockHeader
header.Version required integer
header.Height required integer
header.PrevHash required array of 32 bytes
header.MerkleRoot required array of 32 bytes
header.Timestamp required integer
Unix seconds
header.Difficulty required integer
header.Nonce required integer
PoW nonce, an unsigned 64-bit integer
transactions required array of Transaction
Coinbase first, then the selected mempool transactions
transactions[].version integer (0 to 255)
transactions[].inputs array of TxInput or null
Transaction inputs. null for a coinbase transaction read from the chain.
transactions[].inputs[].key_image array of 32 bytes
transactions[].inputs[].ring_members array of array of 32 bytes
Ring member one-time public keys (real + decoys)
transactions[].inputs[].ring_commitments array of array of 32 bytes
Pedersen commitments for each ring member
transactions[].inputs[].pseudo_output array of 32 bytes
transactions[].inputs[].ring_signature string
Base64-encoded ring signature bytes
transactions[].outputs array of TxOutput
transactions[].outputs[].commitment array of 32 bytes
transactions[].outputs[].public_key array of 32 bytes
transactions[].outputs[].range_proof string
Base64-encoded Bulletproof range proof bytes (omitted in some contexts)
transactions[].outputs[].encrypted_amount array of 8 bytes
transactions[].outputs[].encrypted_memo array of 128 bytes
transactions[].fee integer
Fee in atomic units
transactions[].tx_public_key array of 32 bytes

Option: CompactSubmitBlock

template_id required string
Template ID returned by GET /api/mining/blocktemplate.
nonce required integer
PoW nonce found for the referenced template.

Response 200

{
  "accepted": true,
  "hash": "0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4",
  "height": 14208
}

Response fields

accepted boolean
hash string
Block hash (64 hex chars)
height integer
Block height

Errors

400 { "error": "block rejected" }
400 { "error": "block rejected as stale" }
400 { "error": "invalid JSON body" }
400 { "error": "nonce is required with template_id" }
400 { "error": "unknown or expired template_id" }
401 unauthorized
429 { "error": "submitblock rate limit exceeded" }
429 { "error": "submitblock busy, retry later" }

# events

Real-time Server-Sent Events stream for new blocks and mining notifications

GET/api/events

Server-Sent Events stream for real-time updates. On connection, emits a `connected` event with current chain state. Then streams `new_block` for every new block added to the chain and `mined_block` for every block accepted through `POST /api/mining/submitblock` or the built-in miner. Sends SSE comment keepalives every 30s. Write timeout is disabled for this long-lived connection.

Event: connected

Sent immediately on connection

event: connected
data: {"chain_height":14207,"syncing":false}

Event: new_block

New block added to chain

event: new_block
data: {"height":14208,"hash":"0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4","timestamp":1738800240,"tx_count":3}

Event: mined_block

Block mined by this node

event: mined_block
data: {"height":14208,"hash":"0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4","reward":72325093035}

Event: keepalive

SSE comment sent every 30s to keep connection alive

: keepalive

Example

curl -N -H "Authorization: Bearer $(cat data/api.cookie)" http://127.0.0.1:8332/api/events

Errors

401 unauthorized
500 { "error": "streaming not supported" }
500 { "error": "failed to initialize SSE stream" }

# admin

Dangerous administrative operations (data purge, etc.). These are irreversible and require explicit confirmation.

POST/api/purge

Deletes all blockchain data from disk. Requires explicit confirmation. Stops the daemon, removes the data directory (chain database, checkpoints file, send idempotency records and the API cookie), keeps `settings.json`, then shuts down the API server. A restart is required after this operation. This is irreversible.

Request

{
  "confirm": true
}
confirm required boolean
Must be true to confirm the destructive operation

Response 200

{
  "success": true,
  "message": "blockchain data purged successfully, restart required"
}

Response fields

success boolean
message string

Errors

400 { "error": "confirmation required (set confirm: true)" }
401 unauthorized
500 { "error": "internal error" }

# common errors

401 Missing or invalid `Authorization: Bearer <token>` header unauthorized
400 Invalid request parameters or malformed JSON { "error": "invalid JSON body" }
404 Requested resource does not exist { "error": "block not found" }
403 Wallet is locked — call `POST /api/wallet/unlock` first { "error": "wallet is locked" }
503 No wallet loaded on this node. Call `POST /api/wallet/load`, `/api/wallet/create` or `/api/wallet/import` first. { "error": "no wallet loaded" }

# OpenAPI spec

Machine-readable OpenAPI 3.1 spec available at:
api_openapi.json