Ksana
中文

Build

JSON-RPC API

Every method of a Ksana node’s JSON-RPC interface and of the indexer’s, with the JSON conventions, the errors, and real calls to the public signet.

On this page

A Ksana node answers JSON-RPC 2.0 over HTTP and WebSocket on one port. The indexer answers two methods of its own. Wallets, apps, ksana-cli and the TypeScript SDK all use this interface.

Every example on this page is a real call to the public signet, made on 14 October 2026. Mainnet has not launched.

Endpoints

Service Default address Public signet
Node, HTTP 127.0.0.1:8545 (ksana-node --rpc-addr) https://signet.ksana.network/rpc
Node, WebSocket the same port wss://signet.ksana.network/rpc
Indexer, HTTP 127.0.0.1:8546 (ksana-indexer --rpc-addr) https://signet.ksana.network/indexer

Calls take positional parameters: params is a JSON array, in the order the sections below list them. Send content-type: application/json.

Calls from web pages

Browsers call another origin only when the server allows it (CORS). A node allows none by default; start it with --rpc-cors '*' to allow every origin, or with a comma-separated list of origins. Command-line tools and servers are not affected.

On the public signet, the node’s RPC allows every origin and the indexer allows none. A preflight request shows the difference:

Shell
curl -sS -i -X OPTIONS https://signet.ksana.network/rpc -H 'origin: https://example.org' \
  -H 'access-control-request-method: POST' -H 'access-control-request-headers: content-type' \
  | grep -i -E '^(HTTP/2|access-control)'
Output
HTTP/2 200
access-control-allow-headers: content-type
access-control-allow-methods: POST,GET,OPTIONS
access-control-allow-origin: *

The same request to https://signet.ksana.network/indexer gets HTTP/2 405. Call the indexer from a server or a command line.

A first call

Ask the node for its chain tip:

Shell
curl -sS https://signet.ksana.network/rpc -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"ksana_getChainInfo","params":[]}' | jq
Output
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "chain_id": 1002,
    "tip": {
      "height": "286",
      "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
    },
    "daa_score": "12452",
    "protocol_version": 2,
    "next_upgrade": null,
    "max_known_version": 2,
    "finalized": {
      "height": "246",
      "hash": "c45e4b58173b529c164e66c61219befe4bf7b3e93c319f9c80dbd98982793071"
    }
  }
}

The other examples use a small shell function and print only result. It needs curl and jq:

Shell
NODE=https://signet.ksana.network/rpc
INDEXER=https://signet.ksana.network/indexer
rpc() {  # rpc <url> <method> [params as a JSON array]
  curl -sS "$1" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$2\",\"params\":${3:-[]}}"
}
ACCOUNT=d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf
KSA=0000000000000000000000000000000000000000000000000000000000000000

ACCOUNT is the account of the token and pool tutorial, and KSA is the asset ID of KSA.

JSON conventions

  • Bytes (hashes, accounts, asset and contract IDs, Borsh encodings) are lowercase hex strings without 0x.
  • u64 and u128 values (heights, nonces, amounts, gas) are decimal strings, because JavaScript numbers hold only 53 bits. u8 and u32 values (decimals, BTC heights, counts) are JSON numbers.
  • Block identifiers are "latest", a decimal height such as "268", or a 64-digit block hash. A height names a block of the main chain; a hash can name any block the node knows.
  • Bitcoin IDs (txid, BTC block hashes) are in Bitcoin’s internal byte order, the reverse of what explorers show. Fields that end in _rpc give the explorer order.
  • Protocol types (block headers, entries, VmOps) use the JSON form of the repository’s test vectors: an enum is an object with one key, its variant, such as {"Token": {"amount": "1"}}.

Methods at a glance

Method Parameters Result
ksana_getChainInfo none chain ID, tip, DAA score, versions, finalized tip
ksana_estimateFee none suggested path B fee and the node’s BTC fee rate
ksana_getAccount account nonce, balances, locked fees
ksana_getAsset asset ID name, decimals, total supply, issuer
ksana_submitOp VmOp (Borsh, hex) op ID
ksana_getOpStatus op ID status, block, receipt
ksana_getAccountOps account, before, limit a page of an account’s ops
ksana_getLatestOps before, limit a page of the chain’s ops
ksana_getBlock block identifier header, entries, receipts
ksana_getSettlement block identifier the settlement and its Bitcoin status
ksana_call call request receipt of a read-only call
ksana_subscribeBlocks none (WebSocket) a notification per new block
indexer_status none the indexer’s verified tip
indexer_stateRoot block identifier a verified block’s state root

Chain and fees

ksana_getChainInfo

No parameters. The result, shown in A first call:

Field Meaning
chain_id 1002 on the signet, 1001 on devnet, 1000 on mainnet
tip the main chain’s tip, { "height", "hash" }
daa_score the tip’s DAA score; each block adds its weight (an empty block weighs more)
protocol_version the tip’s block header version
next_upgrade the next scheduled upgrade, { "version", "daa_score" }, or null
max_known_version the highest protocol version this node’s software supports
finalized the finalized tip: the main chain must pass through it, and only a Bitcoin reorganization can take it back

When the network’s protocol_version passes max_known_version, the node’s software needs an upgrade. Before any block is final, finalized is the genesis block.

ksana_estimateFee

No parameters. It suggests fees; it does not change what the node accepts.

Shell
rpc $NODE ksana_estimateFee | jq .result
Output
{
  "feerate_sat_vb": "2",
  "path_b": {
    "amount": "1",
    "per_kib": "1"
  },
  "at": {
    "height": "286",
    "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
  }
}
  • feerate_sat_vb is the fee rate, in sat/vB, at which this node pays for its settlements now. Use it as a reference for path C transactions and top-ups.
  • path_b.per_kib is a suggested fee rate for path B: base units of KSA per KiB of cost. The cost of an op is the larger of its encoded size and its max_gas converted to bytes (max_gas × 81,920 / 100,000,000). The node takes the median, over its last 20 main-chain blocks, of each block’s lowest path B fee rate, and never goes below its own --min-fee-per-kib.
  • path_b.amount is the suggested fee of an ordinary KSA transfer at that rate, never below the node’s --min-token-fee. For another op, multiply its cost by per_kib.
  • at is the tip the estimate was read at.

Operations and fees explains the fee rules.

Accounts and assets

ksana_getAccount

Parameter: the account, as 64 hex digits (the Taproot output key, not the tb1p address).

Shell
rpc $NODE ksana_getAccount "[\"$ACCOUNT\"]" | jq .result
Output
{
  "account": "d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf",
  "nonce": "10",
  "balances": [
    {
      "asset": "0000000000000000000000000000000000000000000000000000000000000000",
      "amount": "7475669406"
    }
  ],
  "locked_fees": [],
  "at": {
    "height": "286",
    "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
  }
}
  • nonce is the account’s next nonce on the main chain. Ops still waiting in the entry pool are not counted.
  • balances lists the available balance of each built-in asset, in base units. A token kept in a contract’s storage, such as the token example, does not appear here: ask the contract.
  • locked_fees lists what the account is owed by blocks that are not unlocked yet, one entry per block, [{ "block_hash", "amount" }]: its reward and path B fees as the block’s producer, its part as the block’s carrier, and the block share for the block share account. It is empty for an account owed nothing.
  • at is the block the answer was read at.

ksana_getAsset

Parameter: the asset ID. KSA’s ID is 64 zeros.

Shell
rpc $NODE ksana_getAsset "[\"$KSA\"]" | jq .result
Output
{
  "asset": "0000000000000000000000000000000000000000000000000000000000000000",
  "name": "KSA",
  "decimals": 8,
  "total_supply": "11011956230000000",
  "issuer": "0000000000000000000000000000000000000000000000000000000000000000",
  "at": {
    "height": "286",
    "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
  }
}

total_supply is in base units: the genesis allocation plus the block rewards issued so far, minus burns. issuer is the contract that issued the asset, or zeros for KSA. An unknown asset returns -32004.

Operations

ksana_submitOp

Parameter: a signed path B VmOp, Borsh-encoded, in hex. Result: { "op_id" }.

The node checks the op against the consensus rules (version, chain ID, size, signature, a nonce not below the account’s) and against its own policy (minimum fee, capacity). A producing node answers once the op is in its next block template, or after 5 seconds at most.

  • A path C op does not go through this method: it travels in a Bitcoin transaction, and ksana_submitOp refuses it with -32010.
  • An op that breaks a consensus rule returns -32010; an op the node’s policy refuses (fee too low, pool full, a replacement without a higher fee) returns -32011.
  • Bytes that do not decode return -32602:
Shell
rpc $NODE ksana_submitOp '["00"]' | jq .error
Output
{
  "code": -32602,
  "message": "VmOp decode failed: Borsh decode failed: Unexpected length of input"
}

The TypeScript SDK and ksana-cli build, sign and encode ops for you.

ksana_getOpStatus

Parameter: the op ID. The example asks for a faucet transfer:

Shell
OP=cdfbd418ad44c47c8fe5a31ba885e9bf1de6c13b5a467e79f4b2b735fc54be97
rpc $NODE ksana_getOpStatus "[\"$OP\"]" | jq .result
Output
{
  "status": "fast_confirmed",
  "block": {
    "height": "261",
    "hash": "81a22b58a74d0d6223cfb8c3c53a3ae21d13f0311c5d155f84863b5ba7be8639"
  },
  "index": 0,
  "receipt": {
    "op_id": "cdfbd418ad44c47c8fe5a31ba885e9bf1de6c13b5a467e79f4b2b735fc54be97",
    "success": true,
    "error": null,
    "gas_used": "10000",
    "output": "",
    "events": []
  }
}
status Meaning
pending in the entry pool, not yet in a block
fast_confirmed in a block of the main chain and executed successfully
failed in a block, but its execution failed: the state is rolled back and the fee is charged
skipped included only by a merged block and could not be executed; no fee is charged, and the op can return to the entry pool
btc_final executed successfully, and the block that accepted it is BTC-final

block.hash is the block that included the op. block.height is the height of the main-chain block that accepted it: an op in a merged block takes effect at the height of the block that merges it. index is the entry’s place in its block. An unknown op returns -32004, and Confirmations explains the marks behind these statuses.

ksana_getAccountOps

Parameters: account, before (optional), limit (optional). It lists the ops of one account on the main chain, newest first: the ops it signed, the transfers to it, and the calls to it as a contract.

Shell
rpc $NODE ksana_getAccountOps "[\"$ACCOUNT\", null, 2]" | jq .result
Output
{
  "ops": [
    {
      "op_id": "2f9117f181b5ea9b3269d91503bb564e77bd4eae54d8a6f48f4d6c77f1e1cc3d",
      "status": "fast_confirmed",
      "block": {
        "height": "274",
        "hash": "f337432055aaa71728d34ec078cc9b426654829790d8d39011e0ebd131b86a9a"
      },
      "index": 0,
      "position": "274:0:0"
    },
    {
      "op_id": "7e3c309278bbad5cc0f0c0dfcb55bbd3689cdced9d4410c704e2f09c89330026",
      "status": "fast_confirmed",
      "block": {
        "height": "273",
        "hash": "2b6f9218d05b361a38093e0425fdf4da8222fb0d8e93d8f8696ed8c4a192f60d"
      },
      "index": 0,
      "position": "273:0:0"
    }
  ],
  "next": "273:0:0",
  "at": {
    "height": "286",
    "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
  }
}
  • position is an op’s place on the main chain, "height:slot:entry": the height of the main-chain block that accepted it, the place of its block among the blocks that one accepted, and the entry’s index. Listings run from the highest position down.
  • before is a position: the page lists the ops below it. Pass the previous page’s next to continue, and null (or nothing) for the newest ops.
  • limit is 1 to 500, 50 by default.
  • next is null once the listing reaches the genesis block. A page can hold fewer ops than limit, even none, while next is not null: keep following next.

The next page continues below 273:0:0:

Shell
rpc $NODE ksana_getAccountOps "[\"$ACCOUNT\", \"273:0:0\", 2]" \
  | jq -c '.result.ops[].position, .result.next'
Output
"272:0:0"
"271:0:0"
"271:0:0"

Listings include path B and path C ops, not unlock entries. A node from before these methods existed, or one whose op index is not built, returns -32601. A malformed account or position, or a limit out of range, returns -32602.

ksana_getLatestOps

Parameters: before (optional), limit (optional). The same listing as ksana_getAccountOps, for every account:

Shell
rpc $NODE ksana_getLatestOps '[null, 1]' | jq .result
Output
{
  "ops": [
    {
      "op_id": "2f9117f181b5ea9b3269d91503bb564e77bd4eae54d8a6f48f4d6c77f1e1cc3d",
      "status": "fast_confirmed",
      "block": {
        "height": "274",
        "hash": "f337432055aaa71728d34ec078cc9b426654829790d8d39011e0ebd131b86a9a"
      },
      "index": 0,
      "position": "274:0:0"
    }
  ],
  "next": "274:0:0",
  "at": {
    "height": "286",
    "hash": "2c1363cf56e91f58e8bd2167b74625120771ad3b0956ea215de540faa33df3ad"
  }
}

Blocks and settlements

ksana_getBlock

Parameter: a block identifier. The result holds hash, header, entries, settlement, receipts, accepted_height and merged. The example leaves out entries and receipts to stay short:

Shell
rpc $NODE ksana_getBlock '["268"]' | jq '.result | del(.entries, .receipts)'
Output
{
  "hash": "571cef33ce79209d2064448531fb7759be191e5357039859445fef9154682502",
  "header": {
    "version": 2,
    "height": "268",
    "parents": [
      "5f2117f25c23819c29a9be702b45c72d2d48a71d6d72094d4f58d6ee20fd3816"
    ],
    "btc_anchor": {
      "height": 326320,
      "hash": "eae6f2a86febdd8ae21bee2da3902a07cb324648485c58944c708ddb0f000000"
    },
    "entries_root": "e4087b3dca5eef67e42073d133ab3969fb5c4306046870811059a0972567b793",
    "state_root": "bd06368cf82d7ed80c172f1b4c283993941eaeaf2a10f20b05113564c567f77f",
    "producer": "ccc06c3ce8d176b3c10138dd4e70873939a9c6dc05597f45aace147ef2c07d3d",
    "timestamp": "1791980287",
    "bits": 520436435,
    "nonce": "638"
  },
  "settlement": null,
  "accepted_height": "268",
  "merged": []
}
  • header.parents lists the block’s parents in the block DAG; btc_anchor is the Bitcoin block it anchors to; producer is the account that made it.
  • entries lists the block’s entries, each an object with one key: Sponsored (a path B op), Forced (a path C op with the btc_txid that carried it) or Unlock.
  • receipts holds one receipt per entry, from when the main chain accepted the block. An entry whose error starts with skipped was not executed.
  • settlement holds the block’s own commit and reveal transactions, { "commit", "reveal" }, as hex, or null when it carries none.
  • accepted_height is the height of the main-chain block that accepted this one: its own height on the main chain, the merging block’s height for a merged block, null while no main-chain block has accepted it.
  • merged lists the blocks it merges, in the order their entries run.

The entry of block 268, without its signature:

Shell
rpc $NODE ksana_getBlock '["268"]' | jq '.result.entries[0].Sponsored.op | del(.sig)'
Output
{
  "version": 2,
  "chain_id": 1002,
  "account": "d08a82dc475034a5f18bb589be8343472e67996b830764889bcbed12e24510bf",
  "nonce": "3",
  "fee": {
    "Token": {
      "amount": "1"
    }
  },
  "max_gas": "1000000",
  "action": {
    "Call": {
      "contract": "2d6e5edc46352b2931657479fce65fdbf01aafc5acc3de7c27660ce19dda8171",
      "method": "add_liquidity",
      "args": "00a0724e18090000000000000000000000f2052a010000000000000000000000",
      "funds": [
        {
          "asset": "0000000000000000000000000000000000000000000000000000000000000000",
          "amount": "5000000000"
        }
      ]
    }
  }
}

sig is {"Bip340": "<64 bytes>"} for a key signature or {"Bip322Simple": "<bytes>"} for a wallet signature. fee is {"Token": {"amount"}} for path B and "BtcNative" for path C. action is Deploy, Call or AssetTransfer.

ksana_getSettlement

Parameter: a block identifier. It tells you whether the block’s data is on Bitcoin. The example leaves out txs, the two raw transactions ({ "commit", "reveal" } in hex):

Shell
rpc $NODE ksana_getSettlement '["264"]' | jq '.result | del(.txs)'
Output
{
  "block": {
    "height": "264",
    "hash": "429f785a8e71515aed13acad24285fa441a9975f929a98c89ea30b0939e25bd2"
  },
  "txid": "a7cae8b07c1c5c8fe1258dd89205d75b5d4c81219d9843bfb4f35508b9bc4ae8",
  "txid_rpc": "e84abcb90855f3b4bf43989d21814c5d5bd70592d88d25e18f5c1c7cb0e8caa7",
  "commit_txid": "ce91d3090919756230a46bcaf9195d375890e82b9229f8fb01f4014b7ea5c8ee",
  "commit_txid_rpc": "eec8a57e4b01f401fbf829922be89058375d19f9ca6ba4306275190909d391ce",
  "carrier_account": "ccc06c3ce8d176b3c10138dd4e70873939a9c6dc05597f45aace147ef2c07d3d",
  "carrier": null,
  "status": "confirmed",
  "confirmed": {
    "txid": "a7cae8b07c1c5c8fe1258dd89205d75b5d4c81219d9843bfb4f35508b9bc4ae8",
    "height": 326322,
    "block": "c48a4f4f1f7ce1e696becf01bbc5f15fdbf8ba7145b9badff31c7e7711000000",
    "confirmations": 3
  },
  "settled": true,
  "btc_final": false
}
  • txid is the reveal transaction, which carries the payload in its Taproot witness; commit_txid is the commit transaction it spends. The _rpc fields give them in explorer order.
  • status is pending (not broadcast yet: a settlement waits until the block has 2 fast-lane confirmations), broadcast, or confirmed.
  • confirmed describes the first confirmed Bitcoin transaction that put the block’s data on Bitcoin, { "txid", "height", "block", "confirmations" }. That can be the block’s own settlement, a later block’s, or a republish.
  • settled is true once the data of the block that accepted it, and of every main-chain block before that one, is on Bitcoin; btc_final is true once the block that accepted it is final.
  • carrier_account is the account paid as the block’s carrier, or null when the producer keeps the reward. It is answered from the current Bitcoin chain and main chain.

A block can have its body carried by a later block’s settlement (batching). Its carrier then names that block, and the transaction fields describe that block’s settlement; while no block carries it yet, they are null and status is pending. An empty block, or one holding only path C operations, has no body to land, so it returns -32004 when it carries no settlement itself.

Read-only calls

ksana_call

Parameter: one call request object. It runs a contract method on the latest state without a fee, without a nonce, and without writing anything:

Field Required Meaning
contract yes the contract ID
method yes the method name
args yes the arguments, Borsh-encoded, in hex ("" for none)
funds no attached assets, [{ "asset", "amount" }], moved from caller in the simulation
caller no the simulated caller, 32 zero bytes by default
max_gas no a decimal string, at most BLOCK_GAS_LIMIT (100,000,000, also the default)

Read a pool’s reserves (the tutorial’s pool, (u128, u128) little-endian):

Shell
POOL=2d6e5edc46352b2931657479fce65fdbf01aafc5acc3de7c27660ce19dda8171
rpc $NODE ksana_call "[{\"contract\": \"$POOL\", \"method\": \"reserves\", \"args\": \"\"}]" \
  | jq .result
Output
{
  "op_id": null,
  "success": true,
  "error": null,
  "gas_used": "45885",
  "output": "be310d09810400000000000000000000583a7696000000000000000000000000",
  "events": []
}

Attached funds come out of caller’s balance, so a simulation that attaches funds needs a funded caller. Here a quote for swapping 1 KSA in that pool, without caller, fails, and a failed call reports all of max_gas as used:

Shell
ARGS=${KSA}00e1f50500000000000000000000000001000000000000000000000000000000
rpc $NODE ksana_call "[{\"contract\": \"$POOL\", \"method\": \"swap\", \"args\": \"$ARGS\",
  \"funds\": [{\"asset\": \"$KSA\", \"amount\": \"100000000\"}], \"max_gas\": \"500000\"}]" \
  | jq .result
Output
{
  "op_id": null,
  "success": false,
  "error": "attached funds transfer failed: insufficient balance",
  "gas_used": "500000",
  "output": "",
  "events": []
}

With "caller": "$ACCOUNT" added, the call succeeds and returns the quote:

Output
{
  "op_id": null,
  "success": true,
  "error": null,
  "gas_used": "144458",
  "output": "f4513fcf2b0000000000000000000000"
}

(That output leaves out events.) The receipt is { "op_id", "success", "error", "gas_used", "output", "events" }, the same shape as an op’s receipt with op_id set to null. output is the hex of the bytes the method returned, and each event is { "contract", "data" }.

The call sees the environment of the next block: its height is the tip’s plus one, with the tip’s timestamp and Bitcoin anchor. native_tx() returns nothing. A max_gas above the limit returns -32602. A node runs at most 4 calls at a time; further calls return -32009 until one finishes.

Subscriptions

ksana_subscribeBlocks

Over WebSocket only. The answer is a subscription ID. Then every block that joins the main chain, including the blocks a reorganization connects, arrives as a ksana_block notification. To stop, call ksana_unsubscribeBlocks with the subscription ID.

This JavaScript runs in a browser or in Node.js 22 and later (node subscribe.mjs). It subscribes, prints the first notification, and unsubscribes:

subscribe.mjs
const ws = new WebSocket("wss://signet.ksana.network/rpc");
const send = (id, method, params) =>
  ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
ws.onopen = () => send(1, "ksana_subscribeBlocks", []);
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log(JSON.stringify(message, null, 2));
  if (message.method === "ksana_block") {
    // One block is enough here: unsubscribe, then close once the node confirms
    send(2, "ksana_unsubscribeBlocks", [message.params.subscription]);
  }
  if (message.id === 2) ws.close();
};
ws.onclose = (event) => console.log("closed", event.code);
Output
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": 2924681207070251
}
{
  "jsonrpc": "2.0",
  "method": "ksana_block",
  "params": {
    "subscription": 2924681207070251,
    "result": {
      "height": "287",
      "hash": "881c3cbbe76d3f2f9ac4f35a8400a38e0d3bb9cded775b4d1e4acf844be386e0",
      "entries": 1,
      "timestamp": "1791982019"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": true
}
closed 1005

Each notification is { "height", "hash", "entries", "timestamp" }: entries is the block’s number of entries and timestamp is in seconds.

A subscriber that falls behind skips the notifications it missed and continues with later blocks. Nothing reconnects for you: when the connection closes, open a new one, subscribe again, and read the blocks you missed with ksana_getBlock. On an idle signet, blocks can be minutes apart.

Indexer methods

The indexer rebuilds the chain from bitcoind alone and checks every state root. Its two methods follow the same conventions.

indexer_status

No parameters.

Shell
rpc $INDEXER indexer_status | jq .result
Output
{
  "height": "276",
  "hash": "e23875efc23a5c7807a8ebc424e9f74a292fbfdfbe5c6b0bc505ddb8719a373c",
  "state_root": "7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d",
  "btc_height": 326324,
  "finalized": {
    "height": "246",
    "hash": "c45e4b58173b529c164e66c61219befe4bf7b3e93c319f9c80dbd98982793071"
  },
  "halted": null
}
  • height, hash and state_root describe the highest block the indexer has executed and verified. It verifies a block once the block’s data is on Bitcoin and the block has 2 confirmations on the indexer’s main chain, or once it is BTC-final, so it trails the node.
  • btc_height is the Bitcoin tip it has scanned, null before it has synced with bitcoind.
  • finalized is the finalized tip, decided by the same rule as the node’s.
  • halted is null in normal operation. The indexer stops, and halted gives the reason, only when a block that Bitcoin made final turns out invalid; an invalid block that is not final is left out, and the indexer chooses its main chain again (alarms).

indexer_stateRoot

Parameter: a block identifier. It answers only for blocks the indexer has verified; others return -32004, and a malformed identifier returns -32602.

Shell
rpc $INDEXER indexer_stateRoot '["276"]' | jq .result
rpc $NODE ksana_getBlock '["276"]' | jq -r .result.header.state_root
Output
{
  "height": "276",
  "hash": "e23875efc23a5c7807a8ebc424e9f74a292fbfdfbe5c6b0bc505ddb8719a373c",
  "state_root": "7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d"
}
7f10d03286185198f17e2c585fb698a9323f51511d8c2b2e8abb12b1d6f65e5d

The two state roots match: the node’s block 276 is the block the indexer rebuilt from Bitcoin alone.

Errors

An error answer has an error object instead of result, with code and message, sometimes data:

Shell
rpc $NODE ksana_getOpStatus "[\"$KSA\"]" | jq .error
rpc $NODE ksana_getBlock '["0x10"]' | jq .error
rpc $NODE ksana_getBlockCount | jq .error
Output
{
  "code": -32004,
  "message": "unknown op"
}
{
  "code": -32602,
  "message": "block identifier must be \"latest\", a decimal height or a 64-digit hex hash"
}
{
  "code": -32601,
  "message": "Method not found"
}
Code Meaning
-32601 no such method, or not available on this node (the op listings without an op index)
-32602 a malformed parameter: hex, decoding, length, range
-32010 the op breaks a consensus rule, or is a path C op
-32011 the node’s policy refuses the op: fee too low, pool full, replacement fee not higher
-32004 the object does not exist: op, block, asset, settlement, unverified block
-32009 the node is busy: too many ksana_call requests at once; try again later
-32603 an internal error of the node