XChain Platform Hub — Architecture
Position in the Data Pipeline
Coin Node (bitcoind / litecoind / dogecoind)
| JSON-RPC
v
xchain-decoder -> Decoder DB (MariaDB)
|
v
xchain-indexer -> Indexer DB (MariaDB)
| |
v v
xchain-explorer xchain-sync -> Validator replicas
^
|
xchain-hub <--> P2P Validator Network
|
- Config oracle (all services poll)
- Price oracle (CoinGecko, CoinMarketCap)
- Cross-chain attestation
- SWAP lifecycle tracking
- Reorg propagation
- Governance
The hub sits at the center of the platform. All other services depend on it for configuration discovery. In validator mode, it also serves as the decentralized coordination layer for pricing, cross-chain operations, and governance.
Operating Modes
STANDALONE MODE VALIDATOR MODE
(P2P_VALIDATOR_ADDR not set) (P2P_VALIDATOR_ADDR set)
+---------------------------+ +---------------------------+
| xchain-hub | | xchain-hub |
| | | |
| +---------------------+ | | +---------------------+ |
| | api.js | | | | api.js | |
| | Express + JSON-RPC | | | | Express + JSON-RPC | |
| +----------+-----------+ | | +----------+-----------+ |
| | | | | |
| +----------v-----------+ | | +----------v-----------+ |
| | XChainHub | | | | XChainHub | |
| | (orchestrator) | | | | (orchestrator) | |
| +----------+-----------+ | | +----------+-----------+ |
| | | | | |
| +----------v-----------+ | | +----------v-----------+ |
| | db.js | | | | PeerManager | |
| | MariaDB pool + | | | | P2P gossip layer | |
| | circuit breaker | | | +----------+-----------+ |
| +----------------------+ | | | |
| | | +----+-----+-----+----+ |
+---------------------------+ | | | | | | |
| v v v v v |
Config writes go directly | Con- Oracle Cross Reorg |
to MariaDB. | sen- Round Chain Hand- |
No P2P, no consensus. | sus Engine ler |
| | | | | | |
| v v v v v |
| Gov- Reward Slash Swap |
| ern- Track- Detec Track |
| ance er tor er |
| +----+-----+-----+----+ |
| | |
| +----------v-----------+ |
| | db.js | |
| | MariaDB pool + | |
| | circuit breaker | |
| +----------------------+ |
+---------------------------+
In standalone mode, the hub is a simple config oracle. In validator mode, the XChainHub orchestrator wires together all subsystems via event-driven architecture.
Internal Components
Event-Driven Wiring
Subsystems communicate via Node.js EventEmitter events rather than direct method calls:
OracleConsensus --round:finalized--> RewardTracker
--round:finalized--> SlashDetector
--round:finalized--> OraclePublisher (queues for DOGE broadcast)
PriceAggregator --row:inserted--> HubDbBroadcaster (forwards to WebSocket subscribers)
CrossChainEngine --attestation:finalized--> SwapTracker
ReorgHandler --reorg:confirmed--> (downstream indexer notification)
Governance --proposal:passed--> (parameter application)
Source Files
| File | Class/Module | Role |
|---|---|---|
api.js |
— | Entry point: Express app, JSON-RPC routes, env var validation, starts XChainHub |
XChainHub.js |
XChainHub |
Orchestrator: wires all subsystems, exposes JSON-RPC method handlers |
db.js |
Database |
MariaDB connection pool with circuit breaker and exponential backoff |
PeerManager.js |
PeerManager |
WebSocket P2P gossip layer: peer connections, message signing, heartbeats |
Consensus.js |
Consensus |
PBFT consensus for config writes: PRE_PREPARE → PREPARE → COMMIT |
ValidatorIdentity.js |
ValidatorIdentity |
Ed25519 key management: signing, verification, key generation |
OracleRound.js |
OracleRound |
Oracle round lifecycle: timer, price fetching, submission broadcast |
OracleConsensus.js |
OracleConsensus |
PBFT consensus for price finalization: trimmed median, propose/prepare/commit |
PriceFetcher.js |
PriceFetcher |
External price API client: CoinGecko and CoinMarketCap |
CrossChainEngine.js |
CrossChainEngine |
PBFT attestation for cross-chain actions with per-chain-pair validators |
SwapTracker.js |
SwapTracker |
Cross-chain SWAP lifecycle tracking: initiated → attested → executed → settled |
ReorgHandler.js |
ReorgHandler |
Blockchain reorg detection, PBFT consensus, and hub state rollback |
Governance.js |
Governance |
Off-chain PBFT voting for parameter changes |
RewardTracker.js |
RewardTracker |
Per-round XCHAIN reward distribution to oracle participants; pushes rewards to BTC indexer for COLLECT |
SlashDetector.js |
SlashDetector |
Validator misbehavior detection: price deviation, non-participation |
PriceAggregator.js |
PriceAggregator |
Receives validated PRICE v0/v1 actions from indexers, deduplicates by round_number (v0) or (source, action_index) (v1), writes to price_snapshots/oracle_prices. EventEmitter — emits row:inserted for hub DB sync. |
OraclePublisher.js |
OraclePublisher |
oracle_publish capability publisher: deterministic leader rotation, persistent JSONL queue, builds PRICE v0 wire format, broadcasts to DOGE via the encoder pipeline, monitors DOGE balance |
EncoderClient.js |
EncoderClient |
Minimal JSON-RPC client for talking to xchain-encoder (get_utxos, create_tx, broadcast_tx) — used by OraclePublisher |
HubDbBroadcaster.js |
HubDbBroadcaster |
WebSocket subscriber registry; broadcasts row:inserted events from PriceAggregator to all connected indexers’ HubDbSync clients |
sql/*.sql |
— | MariaDB table schemas (configs, validators, price_snapshots, oracle_prices, validator_rewards, governance, etc.) |
P2P Gossip Layer
The PeerManager provides the transport layer for all consensus protocols.
Validator A Validator B
+-----------+ +-----------+
|PeerManager|---WebSocket outbound->|PeerManager|
| |<--WebSocket inbound---| |
+-----------+ +-----------+
| ^ | ^
| | message events | |
v | v |
+----------+ +----------+
|Consensus | |Consensus |
|Oracle | |Oracle |
|CrossChain| |CrossChain|
|Governance| |Governance|
+----------+ +----------+
Message Flow
- Subsystem calls
peerManager.broadcast(type, data)(orsendToPeer(addr, type, data)for a directed message). - PeerManager wraps
datain an envelope, assigns a unique ID, signs with Ed25519 (if identity configured), and adds toseenIdsdedup cache. - Message sent to all connected peers.
- Receiving PeerManager checks
seenIds— drops duplicates. - Verifies Ed25519 signature against registered validator pubkeys (if
REQUIRE_SIGNATURES=true). - Emits
messageevent; relays to other peers (flood-fill gossip).
Connection Management
- Outbound connections to
SEED_NODESwith exponential backoff (2s base, 60s max). - Inbound connections on
P2P_PORT(default 10001). - Deduplicates bidirectional connections (if both A→B and B→A connect, one is dropped).
- Heartbeat broadcasts every 15 seconds (includes hub software version for upgrade coordination).
- WS ping/pong every 30 seconds to detect dead connections.
- Peer records persisted to
p2p_peerstable.
Envelope Wire Format
Every message on the gossip layer — regardless of which subsystem produced it — is a single JSON object with the same envelope shape. The subsystem-specific payload lives entirely inside data; everything else is transport metadata.
{
"type": "CAPABILITY_ACTIVATED",
"id": "v1:<sender-addr>:1743690000000:550e8400-e29b-41d4-a716-446655440000",
"sender": "<originating-validator-addr>",
"timestamp": 1743690000000,
"data": { },
"sig": "<128-hex-char Ed25519 signature>"
}
| Field | Type | Description |
|---|---|---|
type |
string |
Message type (see table below). Required; messages without a string type are dropped. |
id |
string |
Globally unique message ID, format v1:<sender-addr>:<unix-ms>:<uuid>. Used for flood-fill deduplication. Required. |
sender |
string |
Address of the original publisher (does not change as the message is relayed). Required. |
timestamp |
number |
Unix epoch milliseconds when the envelope was built. Required (must be a number). |
data |
object |
Subsystem-specific payload. Defaults to {} when omitted by the sender. |
sig |
string |
Optional Ed25519 signature, hex-encoded. Present when the sender has a configured validator identity. |
Signature canonicalization. The signature covers a deterministic JSON serialization of exactly five fields, in this fixed key order — id, type, sender, timestamp, data — with sig itself excluded:
JSON.stringify({ id, type, sender, timestamp, data })
A verifier must reconstruct this exact string to check the signature. The signing key is the sender’s Ed25519 validator key; the verifier looks up sender in the validator registry to obtain the 64-hex-char public key. When REQUIRE_SIGNATURES=true, unsigned messages and messages from unknown senders are rejected; otherwise they are accepted (bootstrap mode).
Inbound processing order (in _handleInbound): JSON parse → reject non-object/array values → validate type/id/sender/timestamp → self-connection guard (drop messages whose sender is this node) → dedup against seenIds → per-peer rate limit → signature verification → emit message (and type-specific events) → relay to all peers except the source connection and the original sender.
Message Types
All types below ride the envelope above; only the data payload differs. Every node emits a generic message event for any valid inbound envelope; some types additionally emit a typed event.
Liveness:
| Type | data shape |
Purpose |
|---|---|---|
HEARTBEAT |
{ "version": "<hub-version>" } |
Broadcast every P2P_HEARTBEAT_INTERVAL (default 15s). Carries the hub software version for upgrade coordination; emits a heartbeat event with (sender, timestamp). |
Capability gossip (advertises which staking-backed capabilities a validator is running; emits a capability event). The receiver additionally requires that data.pubkey match the sender’s registered validator pubkey — a validator cannot advertise capabilities on behalf of another pubkey.
| Type | data shape |
Purpose |
|---|---|---|
CAPABILITY_ACTIVATED |
{ "pubkey": "<hex>", "capability": "<name>", "block_at": <block-index> } |
Sender advertises that it has activated capability (e.g. price, cross_chain, oracle_publish, attestation). The receiver verifies the stake-backed qualification against the indexer stake snapshot at block_at before trusting it; if the snapshot is unavailable it falls back to accepting for liveness. |
CAPABILITY_DEACTIVATED |
{ "pubkey": "<hex>", "capability": "<name>", "block_at": <block-index>, "reason": "<string, optional>" } |
Sender reports that capability is no longer active (failed self-test or lost qualification). reason carries the self-test failure message when present. |
CAPABILITY_SELF_TEST |
{ "pubkey": "<hex>", "capability": "<name>", "ok": <bool>, "reason": "<string, optional>" } |
Carries a capability self-test result (ok true/false, with optional reason on failure). Recognized and applied on receipt to the local capability registry. |
Consensus and coordination (carried over the same gossip layer; payloads are defined by their respective engines):
| Type | Producer | Purpose |
|---|---|---|
PBFT_PRE_PREPARE / PBFT_PREPARE / PBFT_COMMIT |
Consensus |
Three-phase PBFT for config writes. |
PBFT_VIEW_CHANGE / PBFT_NEW_VIEW |
Consensus |
Leader view-change protocol. |
ORACLE_PRICE_SUBMIT |
OracleRound |
Per-round price submission for oracle aggregation. |
ATTEST_PROPOSE / ATTEST_PREPARE / ATTEST_COMMIT |
AttestationConsensus |
PBFT-style consensus over external attestation responses. |
XCHAIN_ATTEST_PROPOSE / XCHAIN_ATTEST_PREPARE / XCHAIN_ATTEST_COMMIT |
CrossChainEngine |
Consensus over cross-chain action confirmations. |
Unrecognized types are still deduplicated, signature-checked, and relayed (so the gossip mesh forwards message types a given node may not handle), but produce no local side effect beyond the generic message event.
PBFT Consensus
The consensus engine implements simplified PBFT for config writes:
Leader Validators (2f+1 required)
| |
|--PRE_PREPARE----------->| Leader proposes config write
| |
|<---------PREPARE---------| Validators acknowledge
| (collect 2f+1) |
| |
|--COMMIT---------------->| Leader broadcasts commit
| |
|<---------COMMIT----------| Validators confirm
| (collect 2f+1) |
| |
[Apply config to MariaDB] [Apply config to MariaDB]
Leader Selection
Leader for sequence N = validatorSet[(N + view) % validatorCount], where validators are sorted by pubkey.
View Change
If the leader fails to drive consensus within PBFT_TIMEOUT (default 30s):
- Validators broadcast
PBFT_VIEW_CHANGEforview + 1. - Once 2f+1 view-change votes are collected, the new view is adopted.
- The next leader (per the new view number) takes over.
Quorum
max(2f+1, ceil((N+1)/2)) where f = floor((N-1)/3) — tolerates f Byzantine validators
out of N total. The simple-majority floor matters for small federations: bare 2f+1
degenerates to a quorum of 1 at N=3 (f=0), which would let a single validator finalize
alone. With the floor, N=3 requires 2 votes and N=2 requires both.
Oracle Pipeline
Every ORACLE_ROUND_INTERVAL (default 10 min):
1. CHAIN TIP OracleRound reads BTC chain tip from configs table
-> currentBtcBlockHeight, currentBtcBlockTime
2. FETCH PriceFetcher queries CoinGecko + CoinMarketCap
-> 3 coins x 12 fiats = 36 pairs per source
-> compute local median across sources
3. SUBMIT Broadcast ORACLE_PRICE_SUBMIT via gossip
-> stored in oracle_submissions table
4. COLLECT Wait ORACLE_SUBMISSION_WINDOW (default 3 min)
-> accumulate other validators' submissions
5. AGGREGATE Round leader computes trimmed median:
-> sort submissions, discard top/bottom 15%
-> median of remaining values
6. SIGN Each validator signs the canonical PRICE v0 payload
-> JSON.stringify({round, timestamp, sortedPairs})
-> Ed25519 via ValidatorIdentity
7. PROPOSE Leader broadcasts ORACLE_PROPOSE (with sig)
8. PREPARE Validators verify and send ORACLE_PREPARE (with their sig)
-> sigs stored on pending.signatures Map
-> collect 2f+1 prepares
9. COMMIT Leader broadcasts ORACLE_COMMIT (with sig)
-> collect 2f+1 commits
10. FINALIZE Store in price_snapshots (status='finalized')
-> reference_block = btcBlockHeight (not 0)
-> emit round:finalized event with collected sigs
-> RewardTracker distributes XCHAIN (pushes to BTC indexer)
-> SlashDetector checks for misbehavior
-> OraclePublisher queues for DOGE broadcast (if leader)
oracle_publish Capability Publishing Pipeline
After consensus finalizes a round, the OraclePublisher takes over:
1. ROTATION Leader = SHA256(round_number || pubkey) ordering
oracle_publish validators sorted deterministically
2. ENQUEUE If local node is the leader, append to JSONL queue (fsync)
3. BROADCAST EncoderClient.getUtxos(DOGE_ADDRESS)
-> EncoderClient.createTx(payload, P2SH encoding)
-> walletSignFn(psbtHex) [operator-provided signer]
-> EncoderClient.broadcastTx(signedHex)
4. CONFIRM Remove from queue on successful broadcast
5. FAILOVER If leader misses by 1 BTC block, next oracle_publish validator in rotation
takes over and batches all missed rounds in a single tx
Price Sources
| Source | Coverage | Requires API Key |
|---|---|---|
| CoinGecko | 3 coins x 12 fiats (single API call via vs_currencies) |
Optional (rate limits apply) |
| CoinMarketCap | 3 coins x 12 fiats (single API call via convert) |
Yes (COINMARKETCAP_API_KEY) |
Supported fiat currencies: USD, CAD, AUD, MXN, GBP, JPY, CNY, CHF, BRL, INR, EUR, KRW.
The local price is the median across available sources. If only one source is available, its price is used directly.
Hub DB Sync Channel
For geographically distributed deployments, the hub broadcasts row inserts to indexers’ local hub DB copies via a WebSocket channel separate from the per-chain sync.
Hub Indexer (HubDbSync client)
+--------------------+ +--------------------+
| PriceAggregator | | XChainIndexer |
| receiveValidated | | - hubDb (local) |
| Round/OraclePrice | | - HubDbSync |
| | | | ^ |
| v | | | |
| emit row:inserted | | | |
| | | | Apply row to |
| v | | local hub DB |
| HubDbBroadcaster | ws send --> | via INSERT IGNORE |
| WebSocket subs | ---------> +--------------------+
+--------------------+
REST Bootstrap Endpoints
| Endpoint | Returns |
|---|---|
GET /hub-db/snapshot/price_snapshots?since_id=N&limit=10000 |
Rows from price_snapshots table after since_id |
GET /hub-db/snapshot/oracle_prices?since_id=N&limit=10000 |
Rows from oracle_prices table after since_id |
WebSocket Channel
| Path | Auth | Messages |
|---|---|---|
/hub-db/subscribe |
Authorization: Bearer <HUB_API_KEY> |
{type: 'row:inserted', table, row} per inserted row |
Indexers bootstrap by fetching the REST snapshots (paginated by since_id) then subscribe to the WebSocket for live updates. Failed sends apply backpressure handling — connections exceeding WS_BACKPRESSURE_LIMIT buffered messages are dropped.
Trimmed Median
Given N validator submissions for a coin pair:
- Sort all submissions by price.
- Discard the top 15% and bottom 15%.
- Compute the median of the remaining values.
This resists manipulation: an attacker would need to control >30% of validators to significantly shift the price.
Cross-Chain Attestation
1. REQUEST requestattestation(source_chain, source_action_index, dest_chain)
2. PROPOSE Leader broadcasts XCHAIN_ATTEST_PROPOSE
-> includes attestation_id: "{source_chain}:{action_index}:{dest_chain}"
3. PREPARE Validators send XCHAIN_ATTEST_PREPARE
-> only validators supporting BOTH chains participate
-> collect 2f+1 from eligible validator subset
4. COMMIT Leader broadcasts XCHAIN_ATTEST_COMMIT
-> collect 2f+1 commits
5. FINALIZE Store in attestations table (status='attested')
-> emit attestation:finalized event
-> SwapTracker auto-progresses matching swaps
Confirmation Thresholds
Higher on the lower-hashpower chains to approach Bitcoin-comparable settlement assurance. Per-chain, configurable via XCHAIN_CONFIRMATIONS_<COIN>.
| Chain | Required Confirmations |
|---|---|
| Bitcoin | 6 |
| Litecoin | 12 |
| Dogecoin | 60 |
Supported Chain Pairs
BTC-LTC, BTC-DOGE, LTC-DOGE.
Per-Chain-Pair Validators
Validators declare which chains they support via the chains column. Only validators supporting both chains in a pair participate in attestation consensus. Validators with NULL chains support all chain-pairs (backward compatible).
Reorg Handling
1. REPORT reportreorg(chain, reorg_height, timestamp)
2. ALERT Broadcast REORG_ALERT via gossip
3. CONSENSUS PBFT round: XCHAIN_REORG_PREPARE / XCHAIN_REORG_COMMIT
-> 2f+1 agreement
4. ROLLBACK Hub state cleanup:
-> DELETE attestations after reorg timestamp for affected chain
-> Mark price_snapshots as 'disputed'
5. NOTIFY Store in reorg_attestations table
-> emit reorg:confirmed event
Governance
1. PROPOSE propose(parameter, current_value, proposed_value, rationale)
-> only active validators can propose
-> stored in governance_proposals (status='voting')
2. GOSSIP Broadcast GOV_PROPOSE via P2P
3. VOTE vote(proposal_id, vote) [approve/reject]
-> stored in governance_votes
-> broadcast GOV_VOTE via P2P
4. TALLY Automatic tally every 60 seconds:
-> check if voting period (7 days) has ended
-> quorum: 50% minimum participation
-> approval: 2/3+ of validator set
-> broadcast GOV_RESULT via P2P
5. APPLY If passed: emit proposal:passed event
-> downstream parameter application
Constraints
| Rule | Value |
|---|---|
| Voting period | 7 days (GOV_VOTING_PERIOD) |
| Quorum | 50% of active validators |
| Approval threshold | 2/3+ of validator set |
| General param change bounds | Max +50% / −33% |
| Slashing param change bounds | Max +25% / −20% |
| Cooldown after rejection | 14 days before re-proposing same parameter |
Reward and Slash System
Rewards
On each finalized oracle round, ORACLE_REWARD_PER_ROUND (default “10.00000000”) XCHAIN is distributed equally among validators who submitted a price in that round. Rewards are recorded in the validator_rewards table with claimed=0 and are collectable via a COLLECT action on the BTC chain (handled by the indexer, not the hub).
Slash Detection
Three offense types are monitored:
| Offense | Trigger | Description |
|---|---|---|
price_deviation |
>5% from consensus | Submission deviates more than SLASH_DEVIATION_THRESHOLD from the finalized price |
repeated_deviation |
3+ in 24 hours | Three or more deviations within a rolling 24-hour window |
non_participation |
30+ missed rounds | SLASH_MISSED_ROUNDS_THRESHOLD consecutive rounds without a submission |
Detection is recorded in the slash_proposals table. Actual stake slashing is executed by the indexer, not the hub.
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.