XChain Platform Hub: API Reference
All methods are called via HTTP POST with JSON-RPC 2.0 format:
curl -X POST http://localhost:10000 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"ping","id":1}'
Config Management
ping
Health check.
Request:
{"jsonrpc":"2.0","method":"ping","id":1}
Response:
{"status":"success","db":true}
health
Detailed health check. Unlike ping (which only confirms the HTTP server is up and the DB pool answers a probe query), health also reports the DB circuit-breaker state and (on oracle-running (P2P-enabled) hubs) oracle round freshness, so an operator can distinguish a healthy hub from one that is up but stalled on a tripped database connection or a stale price feed. Returns HTTP 503 (with the same body) when status is "degraded".
Request:
{"jsonrpc":"2.0","method":"health","id":1}
Response:
{
"status":"healthy",
"db":true,
"dbCircuit":"closed",
"oracle_last_finalized_age_s":120,
"oracle_stale":false,
"oracle_staleness_threshold_s":1200
}
| Field | Type | Description |
|---|---|---|
status |
string |
"healthy" when the DB answers, the circuit is not open, and the oracle is not stale; "degraded" otherwise (also sets HTTP 503). |
db |
boolean |
Whether a SELECT 1 probe against the DB pool succeeded within 2s. |
dbCircuit |
string|null |
DB circuit-breaker state ("closed", "open", "half-open"), or null if no DB handle is configured. A value of "open" forces status to "degraded". |
oracle_last_finalized_age_s |
number|null |
Seconds since the most recently finalized oracle round (price_snapshots with status = 'finalized'). null on config-only hubs (no oracle), when the DB probe failed, or when no round has ever finalized (fresh node). |
oracle_stale |
boolean |
true when oracle_last_finalized_age_s exceeds oracle_staleness_threshold_s. Forces status to "degraded". Always false on config-only hubs and fresh nodes. |
oracle_staleness_threshold_s |
number|null |
Staleness threshold in seconds. Defaults to twice the ORACLE_ROUND_INTERVAL; override with the ORACLE_STALENESS_THRESHOLD_S environment variable. null on config-only hubs or when the DB probe failed. |
Note: the three
oracle_*fields are only populated on hubs running the oracle (P2P-enabled). A config-only hub mints no rounds and reports them asnull/false.
getallconfigs
Returns all service configs wrapped in an envelope: { configs, seq, watermark }.
Sensitive read: requires the X-API-Key header when HUB_API_KEY is set. The config tree carries every service’s connection parameters including database credentials, so it is mesh-internal, keyed like a write. Public clients discovering endpoints should use GET /api/v1/chain-registry instead.
configs: the nested config tree:{ coin: { network: { module: { param: value } } } }.seq: the last committed consensus sequence number (0 on a fresh node with no committed config changes yet).watermark: the high-water mark of the configs table as an epoch-seconds integer (the newestupdated_atacross all rows, or 0 when the table is empty). See Delta polling below.
Request:
{
"jsonrpc":"2.0",
"method":"getallconfigs",
"params":{"since_updated_at":0},
"id":1
}
since_updated_at is optional (defaults to 0). See Delta polling below.
Response:
{
"configs": {
"bitcoin": {
"mainnet": {
"xchain-decoder": {
"host": "192.168.1.10",
"port": "8332",
"db_host": "mariadb",
"db_port": "3306",
"name": "XChain_BTC_Mainnet_Decoder",
"user": "xchain_decoder",
"pass": "password"
},
"xchain-indexer": { ... },
"xchain-explorer": { ... }
},
"testnet": { ... }
},
"litecoin": { ... },
"dogecoin": { ... }
},
"seq": 42,
"watermark": 1717400000
}
Note: the config tree lives under
result.configs, not at the top level. Readresult.configs.bitcoin.mainnet..., notresult.bitcoin.mainnet....
Delta polling: watermark is an epoch-seconds timestamp the consumer should retain and pass back as since_updated_at on its next getallconfigs call. When since_updated_at > 0, the hub returns only the config rows that changed strictly after that instant (a delta, typically empty on a quiet poll) rather than the full tree, along with the new watermark to carry into the following poll. Omitting since_updated_at (or passing 0) returns the complete config tree, so first fetches and consumers that don’t track the watermark are unaffected. The configs table is upsert-only (rows are never deleted), so merging successive deltas reconstructs exactly what a full fetch would have returned.
updateconfig
Upserts service configs from a nested JSON object. In validator mode, the write goes through PBFT consensus. In standalone mode, it writes directly to MariaDB.
Request:
{
"jsonrpc":"2.0",
"method":"updateconfig",
"params":{
"config":{
"BTC":{
"mainnet":{
"xchain-decoder":{
"host":"192.168.1.10",
"port":"8332"
}
}
}
}
},
"id":1
}
Response:
{"status":"success"}
Config parameters stored per coin/network/module: host, port, service_port, db_host, db_port, name, user, pass.
Validator Management
registervalidator
Bootstrap-register a validator. The signing public key must be a 64-character hex string (Ed25519 public key).
Request:
{
"jsonrpc":"2.0",
"method":"registervalidator",
"params":{
"signing_pubkey":"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"addr":"validator1.example.com"
},
"id":1
}
Response:
{"status":"success"}
rotatevalidator (write: requires API key)
Rotate the signing key of the validator at addr to a new pubkey. Retires the addr’s current active key, activates the new one, and reloads + propagates the set to every running consensus engine at runtime (no restart). Rejects an addr that has no current active validator, use registervalidator for a fresh addr. This edits the hub’s local validator registry (the authorization floor); on a hub that follows an on-chain validator set, on-chain key rotation via DELEGATE is followed automatically and this call is not required. See Validator Key Rotation.
Request:
{
"jsonrpc":"2.0",
"method":"rotatevalidator",
"params":{
"addr":"validator1.example.com",
"new_signing_pubkey":"f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2"
},
"id":1
}
Response:
{"status":"success"}
deregistervalidator (write: requires API key)
Remove a validator from the registry by signing_pubkey or addr (marks the matching active row(s) status='removed'), then reloads + propagates the set to every consensus engine. The first-class replacement for hand-editing the validators table. As with rotatevalidator, this affects the local registry floor only.
Request:
{
"jsonrpc":"2.0",
"method":"deregistervalidator",
"params":{"addr":"validator1.example.com"},
"id":1
}
(Pass signing_pubkey instead of addr to deregister by key.)
Response:
{"status":"success"}
syncvalidators
Bulk sync the validator set from external staking data (e.g., from the indexer). Replaces the current set and reloads all subsystem validator sets.
Each validator object carries signing_pubkey, addr, status, and an optional comma-separated chains list (used for cross-chain quorum filtering; omit or leave empty to support all chains). Validator capabilities (price, cross_chain, oracle_publish, attestation, full_node) are not part of this object; a pubkey auto-qualifies for each capability whose governance-configured minimum stake its aggregate active stake meets. There is no tier field.
Request:
{
"jsonrpc":"2.0",
"method":"syncvalidators",
"params":{
"validators":[
{"signing_pubkey":"a1b2c3...","addr":"validator1.example.com","status":"active","chains":"BTC,LTC"},
{"signing_pubkey":"d4e5f6...","addr":"validator2.example.com","status":"active","chains":"BTC,LTC,DOGE"}
]
},
"id":1
}
Response:
{"status":"success","count":2}
getvalidators
List all active validators.
Request:
{"jsonrpc":"2.0","method":"getvalidators","id":1}
Response:
[
{"signing_pubkey":"a1b2c3...","addr":"validator1.example.com","status":"active","chains":"BTC,LTC"},
{"signing_pubkey":"d4e5f6...","addr":"validator2.example.com","status":"active","chains":"BTC,LTC,DOGE"}
]
getvalidatorstatus
Detailed status for a specific validator: info, unclaimed rewards, recent rewards, and slash proposals.
Request:
{
"jsonrpc":"2.0",
"method":"getvalidatorstatus",
"params":{"signing_pubkey":"a1b2c3..."},
"id":1
}
Response:
{
"validator": {"signing_pubkey":"a1b2c3...","addr":"validator1.example.com","status":"active"},
"unclaimed_rewards": "150.00000000",
"recent_rewards": [...],
"slash_proposals": [...]
}
getstakesourcebypubkey (indexer endpoint)
Note: this method lives on
xchain-indexer, not the hub. It is documented here because the hub calls it internally; the archive builder and follower hubs use it to resolve the staking-source address that owned or delegated a signing pubkey at a specific block, so rewards can be attributed to the correct on-chain address.
Request (to indexer):
{
"jsonrpc":"2.0",
"method":"getstakesourcebypubkey",
"params":{"pubkey":"a1b2c3...","block_index":850010},
"id":1
}
Response:
{"source":"1BTC...address"}
The lookup is block-scoped (checks stakes, then DELEGATE v0 delegations in the same order as createValidatorReward) so every caller sees the same answer at earn-time.
Oracle / Price Data
getoraclesubmissions
Diagnostic method returning current round submissions per validator.
Request:
{"jsonrpc":"2.0","method":"getoraclesubmissions","id":1}
Response:
[
{"round_number":42,"coin_pair":"BTC/USD","signing_pubkey":"a1b2c3...","price":"67500.00"},
{"round_number":42,"coin_pair":"LTC/USD","signing_pubkey":"a1b2c3...","price":"85.50"}
]
getpricesnapshots
Returns recent finalized price snapshots.
Request:
{
"jsonrpc":"2.0",
"method":"getpricesnapshots",
"params":{"limit":10},
"id":1
}
Response:
[
{"round_number":42,"coin_pair":"BTC/USD","price":"67500.00","status":"finalized","created_at":"2026-04-06T12:00:00.000Z"},
{"round_number":42,"coin_pair":"LTC/USD","price":"85.50","status":"finalized","created_at":"2026-04-06T12:00:00.000Z"},
{"round_number":42,"coin_pair":"DOGE/USD","price":"0.0825","status":"finalized","created_at":"2026-04-06T12:00:00.000Z"}
]
getprice
Returns the latest finalized price for a specific coin pair.
Request:
{
"jsonrpc":"2.0",
"method":"getprice",
"params":{"coin_pair":"BTC/USD"},
"id":1
}
Response:
{"coin_pair":"BTC/USD","price":"67500.00","round_number":42,"status":"finalized"}
pushchaintip (write: requires API key)
Pushes a chain tip update from an indexer. The hub uses this to anchor oracle rounds to the BTC chain tip (replacing the hardcoded reference_block=0 bug).
Request:
{
"jsonrpc":"2.0",
"method":"pushchaintip",
"params":{"coin":"BTC","block_height":850010,"block_time":1712500000},
"id":1
}
Response:
{"status":"success"}
Stored in the configs table as (coin, mainnet, chain_tips, block_height|block_time). Read by OracleRound._executeRound() at the start of each PBFT round.
pushpriceround (write: requires API key)
Pushes a validated PRICE v0 round from an indexer for cross-chain aggregation. The hub deduplicates by round_number (first valid submission wins) and writes to price_snapshots.
Request:
{
"jsonrpc":"2.0",
"method":"pushpriceround",
"params":{
"source_chain":"DOGE",
"round":850010,
"timestamp":1712500000,
"pairs":[{"pair":"BTC/USD","price":"100000.12345678"},{"pair":"BTC/EUR","price":"92000.00000000"}],
"sigs":[{"pubkey":"aabb...","sig":"ccdd..."}],
"action_index":12345,
"block_index":850010
},
"id":1
}
Response:
{"accepted":true}
Or {"accepted":false,"reason":"duplicate"} if the round already exists. The indexer must validate PBFT signatures locally before pushing; the hub trusts indexer validation.
pushoracleprice (write: requires API key)
Pushes a validated PRICE v1 user oracle price from an indexer. The hub applies the 24-hour lock window and writes to oracle_prices.
Request:
{
"jsonrpc":"2.0",
"method":"pushoracleprice",
"params":{
"source_chain":"DOGE",
"source_address":"D1xx...",
"coin":"BTC",
"tick":"PEPECASH",
"fiat":"JPY",
"value":"7.50000000",
"fee":"0.01",
"memo":"hourly update",
"block_time":1712500000,
"action_index":12345
},
"id":1
}
Response:
{"accepted":true}
The hub looks up any prior price for (source_address, coin, tick, fiat). First broadcast: effective_at = block_time (immediate). Subsequent updates: effective_at = block_time + 86400 (24-hour delay).
pushpricereorg (write: requires API key)
Retract user oracle prices after an indexer rolled back PRICE actions in a reorg. The indexer pushes its source chain plus the lowest rolled-back action index; the hub retracts the matching oracle_prices rows at or above that index.
Request:
{
"jsonrpc":"2.0",
"method":"pushpricereorg",
"params":{
"source_chain":"DOGE",
"from_action_index":12345
},
"id":1
}
| Parameter | Type | Required | Description |
|---|---|---|---|
source_chain |
string | Yes | The chain whose PRICE actions were rolled back |
from_action_index |
integer | Yes | Lowest rolled-back action index; rows at or above it are retracted |
Response: a summary of the retracted price rows, or an { "error": ... } object on failure.
Chain Registry (REST)
GET /api/v1/chain-registry
Public bootstrap endpoint for wallets and SDK clients: the chain descriptors (display metadata, address types, derivation paths, fee strategy, supportedActions, and default explorer/encoder/hub endpoint URLs) for every supported coin and network. supportedActions advertises what each chain’s protocol accepts, and clients gate features on it: the wallet shows its betting surface only on chains whose descriptor lists BET, for example. No authentication; served with Cache-Control: public, max-age=300. The payload is a snapshot of the wallet’s bundled descriptors, kept in sync by xchain-wallet/bin/sync-chain-registry.mjs.
Response:
{
"schema_version": 1,
"generatedAt": "2026-07-07T02:20:12.803Z",
"descriptors": [ { "id": "bitcoin-mainnet", "coin": "bitcoin", "displayName": "Bitcoin", "...": "..." } ],
"signer_pubkey": "hex (present when the hub runs with a signing identity)",
"signature": "hex Ed25519 over 'XCHAIN_CHAIN_REGISTRY_V1|<generatedAt>|<sha256hex(JSON.stringify(descriptors))>'"
}
signer_pubkey/signature are omitted on hubs without a signing identity (standalone mode). Clients that pin a federation pubkey can verify the signature before merging descriptors.
Hub DB Sync (REST + WebSocket)
The hub exposes a separate channel for replicating cross-chain infrastructure tables (price_snapshots, oracle_prices) to indexers’ local hub DB copies. Used in geographically distributed deployments where indexers run on different hosts from the hub.
GET /hub-db/snapshot/price_snapshots
Returns rows from the price_snapshots table after since_id (paginated for incremental bootstrap).
Query parameters:
since_id(optional, default 0), return rows whereid > since_idlimit(optional, default 10000, max 10000)
Response:
{
"table": "price_snapshots",
"rows": [
{"id":1,"round_number":850010,"coin_pair":"BTC/USD","price":"100000.12345678","reference_block":850010,"reference_chain":"BTC","block_timestamp":1712500000,"validator_count":5,"consensus_round":1,"consensus_proof":"[...]","status":"finalized","source_chain":"DOGE","source_action_index":12345,"created_at":"2026-04-06T12:00:00.000Z"}
],
"count": 1,
"watermark": 1712500000
}
All six snapshot endpoints return the same four-field envelope { table, rows, count, watermark }. watermark is the Unix timestamp (seconds) at which the response was generated; indexers use it to detect a snapshot that predates a concurrent row they already saw via WebSocket.
GET /hub-db/snapshot/oracle_prices
Returns rows from the oracle_prices table after since_id. Same format as above.
GET /hub-db/snapshot/cross_chain_matches
Returns rows from the cross_chain_matches table after since_id. Same query parameters and response format as above.
GET /hub-db/snapshot/capability_snapshots
Returns rows from the capability_snapshots table after since_id. Same query parameters and response format as above.
GET /hub-db/snapshot/cross_chain_calls
Returns rows from the cross_chain_calls table after since_id. Same query parameters and response format as above.
GET /hub-db/snapshot/state_checkpoints
Returns rows from the state_checkpoints table after since_id. Same query parameters and response format as above.
GET /hub-db/subscribe (WebSocket upgrade: requires Authorization: Bearer <HUB_API_KEY>)
WebSocket channel for live updates across all six hub DB tables: price_snapshots, oracle_prices, state_checkpoints, capability_snapshots, cross_chain_matches, and cross_chain_calls.
Indexers bootstrap by fetching the REST snapshots for each table (paginated by since_id), then subscribe to this WebSocket for live updates. Backpressure handling drops connections that exceed WS_BACKPRESSURE_LIMIT buffered messages. Unknown type values should be silently ignored for forward compatibility.
Message types (server → client):
ready: sent once immediately after the subscriber connection is established. Contains the maximum row ID currently present in each table and the server-side Unix timestamp (seconds). Indexers should gate their WebSocket processing on receiving this message before trusting any row:inserted events.
{"type":"ready","max_ids":{"price_snapshots":42,"oracle_prices":17,"state_checkpoints":3,"capability_snapshots":8,"cross_chain_matches":5,"cross_chain_calls":2},"watermark":1712500000}
row:inserted: sent for each new row inserted into any of the six tables.
{"type":"row:inserted","table":"price_snapshots","row":{...}}
row:deleted: sent when a reorg retraction removes a row. Indexers must delete or invalidate the referenced row from their local copy.
{"type":"row:deleted","table":"cross_chain_matches","source_chain":"BTC","from_action_index":12345}
watermark: periodic heartbeat (interval controlled by WS_WATERMARK_INTERVAL_MS, default 10 s). Carries the current server Unix timestamp so subscribers can detect a stalled connection even when no rows are being inserted.
{"type":"watermark","ts":1712500060}
getcapabilitythresholds
Returns the per-capability minimum-stake thresholds live from the CapabilityRegistry. No parameters. Lets clients (e.g. the wallet stake form) display which capabilities a stake amount qualifies for without hard-coding the values.
Request:
{"jsonrpc":"2.0","method":"getcapabilitythresholds","id":1}
Response:
{"thresholds":[{"capability":"price","min_stake":"1000.00000000","disabled":false},{"capability":"oracle_publish","min_stake":"500.00000000","disabled":false}]}
| Field | Type | Description |
|---|---|---|
capability |
string |
Capability name (price, cross_chain, oracle_publish, attestation) |
min_stake |
string |
Governance-configured minimum aggregate XCHAIN stake required to qualify |
disabled |
boolean |
true when the operator has disabled this capability via DISABLED_CAPABILITIES |
Fee Quotes
getfeequote
Calculates the native coin fee amount for a given action. The conversion uses two steps:
xchainAmount = gasCost x gasPrice(gas units times XCHAIN per gas unit)nativeCoinAmount = (xchainAmount x xchainUsd) / coinUsd(convert XCHAIN value at current USD prices to the target coin)
gasPrice defaults to 0.00001 XCHAIN per gas unit; the hub reads GAS_PRICE from the config store per chain and uses it when present. xchainUsd and coinUsd come from the oracle’s latest finalized XCHAIN/USD and <chain>/USD price snapshots. If the <chain>/USD price is unavailable, the response omits nativeCoinAmount, nativeCoin, feeUsd, and coinUsd but still returns the XCHAIN-denominated fields.
Request:
{
"jsonrpc":"2.0",
"method":"getfeequote",
"params":{"action":"ISSUE","chain":"BTC"},
"id":1
}
Response:
{
"action":"ISSUE",
"chain":"BTC",
"gasCost":100000,
"gasPrice":"0.00001000",
"xchainAmount":"1.00000000",
"xchainUsd":"0.50000000",
"feeUsd":"0.50000000",
"coinUsd":"67500.00000000",
"nativeCoinAmount":"0.00000741",
"nativeCoin":"BTC"
}
Gas schedule (gas units per action):
| Action | Gas Units | Notes |
|---|---|---|
ISSUE |
100,000 | New token issuance |
ISSUE_SUBTOKEN |
50,000 | Subtoken issuance |
EXPIRATION_PER_DAY |
550 | Per calendar day of token expiration |
OWNERSHIP_ESCROW |
50,000 | Ownership escrow deposit |
AIRDROP_PER_RECIPIENT |
100 | Per recipient in an airdrop |
DIVIDEND_PER_RECIPIENT |
100 | Per recipient in a dividend distribution |
VM_EXECUTE_BASE |
1,000 | Base cost for a VM contract execution |
VM_DEPLOY_BASE |
100,000 | Base cost for a VM contract deployment |
VM_DEPLOY_PER_BYTE |
10 | Per byte of contract source code |
VM_STATE_READ |
100 | Per key read from contract state |
VM_STATE_WRITE |
200 | Per key written to contract state |
VM_STATE_DELETE |
100 | Per key deleted from contract state |
VM_ORACLE_READ |
100 | Per oracle price lookup inside a contract |
VM_CROSSCHAIN_READ |
100 | Per cross-chain data read inside a contract |
VM_ATTEST_REQUEST |
5,000 | External attestation request from a contract |
VM_EMISSION |
500 | Per emitted sub-action from a contract |
VM_COMPUTATION |
1 | Per computation unit (billed by the VM) |
Pass any action name from the table as the action parameter. Passing an unknown name returns {"error": "unknown action: <name>"} rather than throwing.
Cross-Chain Attestations
requestattestation
Initiates a PBFT attestation consensus round for a cross-chain action.
Request:
{
"jsonrpc":"2.0",
"method":"requestattestation",
"params":{
"source_chain":"bitcoin",
"source_action_index":12345,
"dest_chain":"litecoin"
},
"id":1
}
Response:
{"status":"success","attestation_id":"bitcoin:12345:litecoin"}
getattestation
Get a specific attestation record by source chain and action index.
Request:
{
"jsonrpc":"2.0",
"method":"getattestation",
"params":{
"source_chain":"bitcoin",
"source_action_index":12345
},
"id":1
}
Response:
{
"attestation_id":"bitcoin:12345:litecoin",
"source_chain":"bitcoin",
"source_action_index":12345,
"dest_chain":"litecoin",
"status":"attested",
"consensus_proof":{...}
}
getattestations
Query attestation records by status.
Request:
{
"jsonrpc":"2.0",
"method":"getattestations",
"params":{"status":"attested","limit":50},
"id":1
}
Response:
[
{"attestation_id":"bitcoin:12345:litecoin","status":"attested",...},
{"attestation_id":"bitcoin:12340:dogecoin","status":"attested",...}
]
getattestationstats
Attestation throughput counters for this hub’s attestation rounds. Useful for monitoring participation and spotting a stalled attestation subsystem.
Request:
{"jsonrpc":"2.0","method":"getattestationstats","id":1}
Response:
{
"seen_count":120,
"in_flight_count":2,
"proposed_count":115,
"failed_count":3
}
| Field | Type | Description |
|---|---|---|
seen_count |
integer | Attestation requests observed |
in_flight_count |
integer | Requests seen but whose round has not yet resolved |
proposed_count |
integer | Rounds that reached a proposal |
failed_count |
integer | Rounds that ended in error |
Swap Tracking
initiateswap
Record a cross-chain swap initiation. The swap auto-progresses to “attested” when the corresponding attestation finalizes.
Request:
{
"jsonrpc":"2.0",
"method":"initiateswap",
"params":{
"source_chain":"bitcoin",
"source_action_index":12345,
"dest_chain":"litecoin",
"dest_action_index":67890
},
"id":1
}
Response:
{"status":"success"}
getswap
Get a specific swap record.
Request:
{
"jsonrpc":"2.0",
"method":"getswap",
"params":{
"source_chain":"bitcoin",
"source_action_index":12345
},
"id":1
}
Response:
{
"source_chain":"bitcoin",
"source_action_index":12345,
"dest_chain":"litecoin",
"dest_action_index":67890,
"status":"attested"
}
getswaps
Query swap records by status.
Request:
{
"jsonrpc":"2.0",
"method":"getswaps",
"params":{"status":"initiated","limit":50},
"id":1
}
Swap statuses: initiated, attested, executed, settled, failed.
stateDiagram-v2
[*] --> Initiated
Initiated --> Attested: attestation finalizes
Attested --> Executed
Executed --> Settled
Settled --> [*]
Initiated --> Failed
Attested --> Failed
Executed --> Failed
Failed --> [*]
Reorg Handling
reportreorg
Report a detected blockchain reorg. Triggers PBFT consensus and hub state rollback if confirmed.
The reporter must include the block hash it saw at reorg_height before the reorg (old_hash) and the hash its node serves now (new_hash). Every hub, including the one receiving this call, checks the new hash against its own indexer before it will co-sign the rollback, so a report no honest node can confirm never reaches quorum.
Request:
{
"jsonrpc":"2.0",
"method":"reportreorg",
"params":{
"chain":"bitcoin",
"reorg_height":893000,
"timestamp":1743690000,
"old_hash":"<64-hex block hash observed at reorg_height before the reorg>",
"new_hash":"<64-hex block hash the node serves at reorg_height now>"
},
"id":1
}
Response:
{"status":"success"}
getreorghistory
Query confirmed reorg attestation history.
Request:
{
"jsonrpc":"2.0",
"method":"getreorghistory",
"params":{"limit":20},
"id":1
}
Response:
[
{"chain":"bitcoin","reorg_height":893000,"timestamp":1743690000,"confirmed_at":"2026-04-06T12:00:00.000Z"}
]
pushxcallreorg (write: requires API key)
Retract cross_chain_calls relay rows after an indexer rolled back XCALL request actions in a reorg. The indexer pushes its source chain plus the lowest rolled-back action index; the hub marks the matching relay rows retracted (both phases) and broadcasts deletions so distributed indexers prune their mirrored copies.
Request:
{
"jsonrpc":"2.0",
"method":"pushxcallreorg",
"params":{
"source_chain":"DOGE",
"from_action_index":12345
},
"id":1
}
| Parameter | Type | Required | Description |
|---|---|---|---|
source_chain |
string | Yes | The chain whose XCALL request actions were rolled back |
from_action_index |
integer | Yes | Lowest rolled-back action index; relay rows at or above it are retracted |
Response:
{"status":"ok","source_chain":"DOGE","from_action_index":12345}
pushdexreorg (write: requires API key)
Retract cross_chain_matches rows after an indexer rolled back DEX ORDER actions in a reorg. The hub marks every match whose retracted leg is on that source chain at or above the given index retracted, restores both legs’ remaining capacity, and broadcasts deletions so distributed indexers prune their mirrored copies.
Request:
{
"jsonrpc":"2.0",
"method":"pushdexreorg",
"params":{
"source_chain":"DOGE",
"from_action_index":12345
},
"id":1
}
| Parameter | Type | Required | Description |
|---|---|---|---|
source_chain |
string | Yes | The chain whose DEX ORDER actions were rolled back |
from_action_index |
integer | Yes | Lowest rolled-back action index; matches at or above it are retracted |
Response:
{"status":"ok","source_chain":"DOGE","from_action_index":12345}
Governance
propose
Submit a governance proposal for a parameter change. Must be an active validator.
Request:
{
"jsonrpc":"2.0",
"method":"propose",
"params":{
"parameter":"ORACLE_ROUND_INTERVAL",
"current_value":"600000",
"proposed_value":"300000",
"rationale":"Faster oracle updates for improved price freshness"
},
"id":1
}
Response:
{"status":"success","proposal_id":1}
vote
Cast a vote on an active governance proposal. Must be an active validator.
Request:
{
"jsonrpc":"2.0",
"method":"vote",
"params":{
"proposal_id":1,
"vote":"approve"
},
"id":1
}
Vote values: approve, reject.
Response:
{"status":"success"}
getproposals
List governance proposals, optionally filtered by status and/or parameter name. limit caps the result count (default 50, maximum 500).
Request:
{
"jsonrpc":"2.0",
"method":"getproposals",
"params":{"status":"voting","parameter":"ORACLE_ROUND_INTERVAL","limit":50},
"id":1
}
Response:
[
{
"proposal_id":1,
"parameter":"ORACLE_ROUND_INTERVAL",
"current_value":"600000",
"proposed_value":"300000",
"rationale":"Faster oracle updates for improved price freshness",
"proposer":"a1b2c3...",
"status":"voting",
"created_at":"2026-04-06T12:00:00.000Z"
}
]
Proposal statuses: voting, passed, failed.
getproposal
Get a specific proposal with all associated votes.
Request:
{
"jsonrpc":"2.0",
"method":"getproposal",
"params":{"proposal_id":1},
"id":1
}
Response:
{
"proposal": {
"proposal_id":1,
"parameter":"ORACLE_ROUND_INTERVAL",
"current_value":"600000",
"proposed_value":"300000",
"status":"voting"
},
"votes": [
{"signing_pubkey":"a1b2c3...","vote":"approve"},
{"signing_pubkey":"d4e5f6...","vote":"reject"}
]
}
getvotes
List individual governance votes by proposal and/or voter. Complements getproposal (which bundles one proposal’s votes) with a list-by-voter view across proposals. limit defaults to 50, maximum 500.
Request:
{
"jsonrpc":"2.0",
"method":"getvotes",
"params":{"voter_pubkey":"a1b2c3...","limit":50},
"id":1
}
Response:
[
{"id":12,"proposal_id":"prop-1","voter_pubkey":"a1b2c3...","vote":"approve","created_at":"2026-04-06T12:00:00.000Z"}
]
getvalidatorcapabilities
List per-validator capability qualification rows, optionally filtered by signing_pubkey and/or capability. Companion to getcapabilitythresholds: thresholds say what a capability requires, this says who currently holds it and why a validator might not be active (qualified, self_test_ok, and enabled are independent flags). limit defaults to 200, maximum 500.
Request:
{
"jsonrpc":"2.0",
"method":"getvalidatorcapabilities",
"params":{"capability":"price"},
"id":1
}
Response:
[
{
"id":3,
"signing_pubkey":"a1b2c3...",
"capability":"price",
"qualified":1,
"self_test_ok":1,
"enabled":1,
"qualified_at_block":901234,
"updated_at":"2026-04-06T12:00:00.000Z"
}
]
ANCHOR Publishing
anchorflush (write: requires API key)
Trigger an immediate out-of-interval ANCHOR publish attempt on the StateAnchorPublisher, bypassing the normal ANCHOR_INTERVAL_MS timer. The publisher still enforces its per-chain election: a hub that is not the elected publisher for a pending anchor skips it and the response indicates so. Useful for operator-forced flushes after federation events or wallet refills.
Request:
{"jsonrpc":"2.0","method":"anchorflush","id":1}
Response:
{"flushed":3,"skipped":0,"elected":true}
Returns {"error":"anchor publisher not active"} when StateAnchorPublisher is not running (standalone mode or P2P_VALIDATOR_ADDR not set).
getanchorstatus
ANCHOR publisher status (read, no auth): cumulative anchor counts plus the last-observed DOGE publisher-wallet balance and low-balance threshold, for runway monitoring. Always returns HTTP 200 (unlike health, which flips to 503 when degraded), so a poller can read the balance independent of overall hub health. Returns {"active":false} when no publisher is running.
Request:
{"jsonrpc":"2.0","method":"getanchorstatus","id":1}
Response:
{
"active": true,
"enabled": true,
"anchorsPublished": 42,
"archiveChunkLosses": 0,
"dogeAddress": "D...",
"dogeBalance": 18.4,
"dogeBalanceAt": 1782000000000,
"lowBalanceThreshold": 10
}
dogeBalance/dogeBalanceAt are null until the first publish cycle reads the wallet (or when no DOGE pipeline is configured).
Rewards
pushvalidatorrewards (write: requires API key, indexer endpoint)
Note: this method is implemented on
xchain-indexer, not the hub. The hub’sRewardTrackercalls it to persist anchor-publish reward rows into the indexer’svalidator_rewardstable.
Accepted reward_type values must match ^anchor_[A-Za-z_]+$ (e.g. anchor_BTC, anchor_DOGE). The indexer rejects oracle_round and attest_fee because those are derived deterministically during block processing, accepting a push for them would open a replay-divergence window.
Request (from hub → indexer):
{
"jsonrpc":"2.0",
"method":"pushvalidatorrewards",
"params":{
"round":850010,
"reward_type":"anchor_DOGE",
"block_index":850010,
"rewards":[{"pubkey":"a1b2c3...","amount":"10.00000000"}]
},
"id":1
}
Response:
{"status":"success","written":1,"skipped":0}
Monitoring Stats
Read-only, no-auth counters for the cross-chain call and state-checkpoint subsystems, mirroring getattestationstats. Each returns {"error": "..."} (still HTTP 200) when its engine is not active on this hub, so a poller can tell “no data” from “subsystem off”.
getcrosschaincallstats
Cross-chain call relay backlog depth and lifetime failure counters. Useful for spotting a stalled XCALL relay (a growing backlog) or repeated result-fetch errors.
Request:
{"jsonrpc":"2.0","method":"getcrosschaincallstats","id":1}
Response:
{
"pending_relay_count":3,
"pending_by_chain":{"DOGE":2,"LTC":1},
"result_attempt_failures":0
}
| Field | Type | Description |
|---|---|---|
pending_relay_count |
integer | Finalized dispatches with no non-retracted result row yet, awaiting relay (total across chains) |
pending_by_chain |
object | The same backlog broken down by target_chain |
result_attempt_failures |
integer | Process-lifetime count of result-fetch attempts that errored |
getcheckpointstats
State-checkpoint health: the last finalized block per chain and a process-lifetime count of rounds that timed out below quorum.
Request:
{"jsonrpc":"2.0","method":"getcheckpointstats","id":1}
Response:
{
"last_finalized_by_chain":{"BTC":{"block_index":957239,"checkpoint_seq":142}},
"round_timeouts":0
}
| Field | Type | Description |
|---|---|---|
last_finalized_by_chain |
object | Per-chain latest finalized checkpoint: {block_index, checkpoint_seq} |
round_timeouts |
integer | Process-lifetime count of checkpoint rounds that timed out without reaching quorum |
OpenRPC Spec
The hub serves a machine-readable OpenRPC 1.3.2 specification at:
GET /openrpc.json
No authentication required. The spec is generated by docs/openrpc.build.js and kept in lockstep with jsonRpcController by test/unit/openrpc-coverage.test.js.
Telemetry (REST)
Anonymous node-operator usage telemetry is served as plain REST routes (not JSON-RPC) on the same hub HTTP port. See TELEMETRY_API.md for full request bodies, query parameters, response shapes, and field-by-field reference. Operator-facing data policy lives in operations/TELEMETRY.md.
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/telemetry |
none | Ingest one anonymous usage ping (fire-and-forget). |
GET |
/telemetry/summary |
none | Aggregate-only census (distribution counts; ?days=1..365, default 30). |
GET |
/telemetry/operators |
x-api-key: <TELEMETRY_ADMIN_KEY> |
Per-install operator detail (?days=1..365, default 30). |
Copyright © 2025–2026 Dankest, LLC
Based on XChain Platform by Dankest, LLC – https://dankest.llc
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later) with a commercial license available for proprietary use.
You may use, modify, and distribute this material under the terms of the License. See LICENSE and NOTICE for full terms. See the licensing overview.