Bloch Genesis-4 developer portal
The integration reference for the Bloch Genesis-4 chain: a post-quantum proof-of-stake L1 with an extended-UTXO ledger and explicit Casper-FFG finality. Written for engineers — exchange integrators, wallet builders, node operators.
This portal documents the network at the deepest honest level. Where something does not exist yet — a transaction index, an EVM, official SDKs, a joinable sync path for brand-new nodes — this page says so plainly instead of papering over it. Every figure here was verified against source commit 72e5525 and the live network on 2026-09-07; where a feature is in source but not yet on the fleet, it is labeled next release.
- Read network parameters and the integer rule first — the latter breaks naive JSON parsers.
- Everything is JSON-RPC 2.0 over HTTP POST: endpoints, method reference.
- Crediting payments? The deposit rule is normative — this chain has no transaction index, tracking works differently than on Bitcoin or Ethereum.
- Running infrastructure? Start at Run a node — including the one current limitation you must know about before provisioning.
Network parameters
| Chain | Bloch Genesis-4 — post-quantum proof-of-stake L1, extended-UTXO (eUTXO) ledger, explicit Casper-FFG finality gadget over an LMD-GHOST fork choice |
| Predecessor | Genesis-3 (proof-of-work), closed at height 39,918; its ledger carried over into Genesis-4's opening state |
| Ticker | BLCH (native asset — there is no token contract) |
| Total supply | 100,000,000,000 BLCH — fixed hard cap, enforced per block |
| Denomination | 8 decimals; 1 BLCH = 100,000,000 sat; total supply = 1019 sat |
| Genesis time | 2026-08-13 21:31:19.962 UTC |
| Slot time | 30 s |
| Epoch | 32 slots (16 min) |
| Genesis validators | 64 |
| Signatures | Hybrid ML-DSA-65 ‖ Falcon-1024 — both components must verify; a signature blob measures ≈ 4,589 bytes |
| Hashing | SHA3-256 / SHAKE-256, domain-separated throughout |
| Custody model | Non-custodial by construction — the node never holds a spending key; all signing is client-side |
| Testnet | None exists. Integrate against mainnet with small amounts, or against your own observer node |
The integer rule: every *_sat field is a string
Every satoshi-denominated RPC field (balance_sat, value_sat, total_active_stake_sat, …) is a decimal string, never a JSON number. The total supply is 1019 sat, which exceeds the 253 exact-integer range of a JavaScript double (and of any IEEE-754 double). A parser that runs these values through JSON.parse as numbers will silently corrupt balances.
Parse every *_sat field as a big integer — BigInt in JS, int in Python, big.Int / uint64-with-care in Go. Plain counts, heights, and slots are ordinary numbers.
// JS: correct handling of *_sat fields
const res = await fetch("https://posternlabs.com/g4rpc", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getbalance",
params: ["7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000"] })
});
const { result } = await res.json();
const balance = BigInt(result.balance_sat); // string -> BigInt. Never Number().
const utxoCount = result.utxo_count; // plain count -> ordinary number is fine
Endpoints & transport
Public proxy (recommended)
POST https://posternlabs.com/g4rpc
JSON-RPC 2.0 over HTTP POST. The proxy exposes a read-method allowlist plus sendrawtransaction. It is backed by multiple upstream nodes with read corroboration: branch-sensitive reads require a quorum of ≥ 2 upstream nodes agreeing before a response is served. CORS is answered at the proxy, so browser apps can call it directly.
Per-method cache TTLs at the proxy:
| Method | Cache TTL |
|---|---|
getchaininfo | 3 s |
getblockcount | 3 s |
getvalidatorcount | 5 s |
getblockbyslot | 10 s |
getblockbyid | 300 s |
Direct archival nodes
POST http://139.180.166.5:8080 · POST http://139.180.173.231:8080
Two keyless observer nodes, plain HTTP, POST only. No TLS — treat the transport as untrusted and corroborate any answer across both nodes (this is exactly what the public proxy automates for you). Useful when you want raw node behavior without the proxy's cache or allowlist.
Explorer & wallet
- Explorer: blochl1.com — blocks, validators, epochs, analytics, transactions.
- Wallet: posternlabs.com/apps/wallet/ — a PWA with client-side signing; usable as a reference integration for the signing flow documented on this page.
Transport rules
- One call per connection. The node answers with
Connection: close; there is no keep-alive at the node. Pool at your HTTP client's peril — open a fresh connection per request. - Request body cap: 1 MiB.
- Both positional-array and named-object
paramsare accepted. - Batch arrays are refused with
-32600— one JSON-RPC call per request.
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"getchaininfo","params":[]}'
JSON-RPC reference — live surface
The methods below are the surface the public proxy and the live fleet serve today. Methods that exist in source but are not yet on the fleet are listed under Next release. All sample responses use illustrative values (heights, hashes, and balances are made up); shapes and field names are the contract.
- JSON-RPC 2.0;
paramspositional ([…]) or named ({…}). - All
*_satfields are decimal strings — see the integer rule. - Hashes and roots are 64-char lowercase hex (32 bytes).
- One call per connection; no batches; 1 MiB body cap.
getchaininfo live
getchaininfo [] → object
One-call snapshot of the node's view: head, finality checkpoints, validator counts, fee state, and transport health. Proxy cache TTL: 3 s.
Parameters
None — pass [].
Response fields
| Field | Type | Notes |
|---|---|---|
block_id | hex-64 | Head block id |
slot, height | number | Head slot / chain height |
finalized_height | number | Height of the finalized checkpoint |
epoch, slot_in_epoch, slots_per_epoch | number | slots_per_epoch is 32 |
state_root | hex-64 | State root at head |
justified, finalized, previous_justified | object | Each {epoch, root} — the FFG checkpoints |
validators | object | {total, active} |
total_active_stake_sat | string | Big integer — parse as BigInt |
base_fee_millisat_per_gas, next_base_fee_millisat_per_gas | string | Current and one-block-look-ahead base fee |
mempool, blocks_known | number | Mempool size; total blocks this node knows |
wall_slot, behind_by_slots | number | Wall-clock slot and how far the node lags it |
transport | object | {name, peers} |
Poll transport.peers as well as behind_by_slots. A node that is partitioned can keep a fresh-looking head (low behind_by_slots) while extending a private fork; peers dropping to 0 is the signal behind_by_slots will not give you.
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getchaininfo","params":[]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"block_id": "7c1e29aa40b3d8f15e6a0c92d4b7f81326c5a90e8f1d3b6407e2c9a51b8d4f60",
"slot": 71182,
"height": 34120,
"finalized_height": 34064,
"epoch": 2224,
"slot_in_epoch": 14,
"slots_per_epoch": 32,
"state_root": "e0a4c1d293b85f671c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7",
"justified": { "epoch": 2223, "root": "44f0b2c9d81e6a35970c4d2f8b1e5a603d7c92f406b8e1a5c2d94f705e8a1b36" },
"finalized": { "epoch": 2222, "root": "b2d94f705e8a1b3644f0b2c9d81e6a35970c4d2f8b1e5a603d7c92f406b8e1a5" },
"previous_justified": { "epoch": 2222, "root": "b2d94f705e8a1b3644f0b2c9d81e6a35970c4d2f8b1e5a603d7c92f406b8e1a5" },
"validators": { "total": 64, "active": 64 },
"total_active_stake_sat": "3200000000000000000",
"base_fee_millisat_per_gas": "1000",
"next_base_fee_millisat_per_gas": "1024",
"mempool": 3,
"blocks_known": 34121,
"wall_slot": 71182,
"behind_by_slots": 0,
"transport": { "name": "devnet", "peers": 8 }
}
}
getblockcount live
getblockcount [] → object
Lightweight height/finality probe — the cheap poll for crediting pipelines. Proxy cache TTL: 3 s.
Parameters
None — pass [].
Response fields
| Field | Type | Notes |
|---|---|---|
height, slot, epoch | number | Head position |
finalized_height | number | null | null before the first finalized checkpoint is known |
justified_epoch, finalized_epoch | number | FFG checkpoint epochs |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getblockcount","params":[]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"height": 34120,
"slot": 71182,
"epoch": 2224,
"finalized_height": 34064,
"justified_epoch": 2223,
"finalized_epoch": 2222
}
}
getblockbyslot / getblockbyid live
getblockbyslot [slot] → block · getblockbyid [block_id hex-64] → block
Fetch one canonical block, by slot or by id. Both return the same block object. Under proof of stake, empty slots are normal: getblockbyslot answers -32007 SLOT_EMPTY when no canonical block occupies the slot — advance to the next slot, it is not an error condition. Proxy cache TTLs: 10 s by slot, 300 s by id.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | slot / block_id | number / hex-64 | Slot number, or 64-hex block id |
Response fields (block object)
| Field | Type | Notes |
|---|---|---|
block_id | hex-64 | Consensus block id — clients recompute it over the header |
version | number | Always 2970353669 — the raw 0xB10C0005 network magic, exposed verbatim by design; do not expect a small integer |
parent | hex-64 | Parent block id |
slot, epoch, height, proposer_index | number | |
timestamp | number | Block time, unix seconds (genesis + slot × 30 s) |
state_root, body_root | hex-64 | |
randao_reveal, randao_mix | hex | Reveal is a full hybrid signature (≈ 4,589 B); mix is 32 bytes |
justified_root, finalized_root | hex-64 | FFG votes carried by this block |
attestation_root, coherence_root | hex-64 | |
finality | string | "finalized" | "justified" | … — this block's finality standing |
finalized | boolean | Convenience flag |
tx_count, attestation_count | number | Counts only — the block object does not inline transactions |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getblockbyslot","params":[71180]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"block_id": "7c1e29aa40b3d8f15e6a0c92d4b7f81326c5a90e8f1d3b6407e2c9a51b8d4f60",
"version": 2970353669,
"parent": "9d3b6407e2c9a51b8d4f607c1e29aa40b3d8f15e6a0c92d4b7f81326c5a90e8f",
"slot": 71180,
"epoch": 2224,
"height": 34118,
"proposer_index": 41,
"timestamp": 1788792079,
"state_root": "e0a4c1d293b85f671c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7",
"body_root": "970c4d2f8b1e5a603d7c92f406b8e1a5c2d94f705e8a1b3644f0b2c9d81e6a35",
"randao_reveal": "a3b1…(hybrid signature, ~4589 bytes hex, truncated here)",
"randao_mix": "a5c3e71b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20f31a7c059e64b2d80",
"justified_root": "44f0b2c9d81e6a35970c4d2f8b1e5a603d7c92f406b8e1a5c2d94f705e8a1b36",
"finalized_root": "b2d94f705e8a1b3644f0b2c9d81e6a35970c4d2f8b1e5a603d7c92f406b8e1a5",
"attestation_root": "0c92d4b7f81326c5a90e8f1d3b6407e2c9a51b8d4f607c1e29aa40b3d8f15e6a",
"coherence_root": "1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7e0a4c1d293b85f67",
"finality": "justified",
"finalized": false,
"tx_count": 1,
"attestation_count": 42
}
}
Empty slot
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32007, "message": "no canonical block at slot" }
}
getvalidator live
getvalidator [index u32] → object
One validator's registry entry by index.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | index | u32 | Validator index; unknown index → -32001 |
Response fields
| Field | Type | Notes |
|---|---|---|
index | number | |
pubkey_hash | hex-64 | SHA3-256 of the raw public key |
pubkey_bytes | number | Length of the raw hybrid public key in bytes |
state | string | e.g. "active" |
own_stake_sat | string | |
effective_stake_sat | string | null | |
commission_bps | string | Basis points, as a string on this method |
randao_commitment | hex-64 | |
slashed | boolean | |
activation_epoch, exit_epoch, withdrawable_epoch | number | null |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getvalidator","params":[41]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"index": 41,
"pubkey_hash": "5a0e8d21c47bf93602de5a81b64c9f307e12ad85f09c36b4d871e25a40c3b96f",
"pubkey_bytes": 3749,
"state": "active",
"own_stake_sat": "50000000000000000",
"effective_stake_sat": "50000000000000000",
"commission_bps": "500",
"randao_commitment": "d871e25a40c3b96f5a0e8d21c47bf93602de5a81b64c9f307e12ad85f09c36b4",
"slashed": false,
"activation_epoch": 0,
"exit_epoch": null,
"withdrawable_epoch": null
}
}
getvalidatorcount live
getvalidatorcount [] → object
Aggregate validator statistics. Proxy cache TTL: 5 s.
Parameters
None — pass [].
Response fields
| Field | Type | Notes |
|---|---|---|
total, active | number | |
total_active_stake_sat | string |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getvalidatorcount","params":[]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": { "total": 64, "active": 64, "total_active_stake_sat": "3200000000000000000" }
}
getbalance live
getbalance [script_hash hex-64] → object
Balance and UTXO count for one script hash. This is the cheap poll for deposit detection. See Addresses & keys for how to derive a script_hash from an address.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | script_hash | hex-64 | 32-byte script hash (Carried or Native form) |
Response fields
| Field | Type | Notes |
|---|---|---|
script_hash | hex-64 | Echoed |
balance_sat | string | Parse as BigInt |
utxo_count | number (u64) | Reference total for UTXO enumeration — see getutxos |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getbalance","params":["7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000"]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000",
"balance_sat": "125000000000",
"utxo_count": 3
}
}
getutxos / listunspent live
getutxos [script_hash hex-64, limit?] → object · listunspent is an alias
Unspent outputs for a script hash. limit defaults to 100 and is clamped to 1..=1000.
limit is a count, not a page: repeated calls return the same first page. You cannot enumerate past the first 1,000 outputs with this method. The working pattern:
- Use
getbalance.utxo_countas the reference total; if it exceedsreturned, you are not seeing everything. - Keep hot addresses under 1,000 outputs (consolidate).
gettxout(txid, vout)is the only exact single-output check past the first page.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | script_hash | hex-64 | Required |
| 1 | limit | number | Optional; default 100; clamped 1..=1000 |
Response fields
| Field | Type | Notes |
|---|---|---|
script_hash | hex-64 | Echoed |
total, returned | number | |
truncated | boolean | true when more outputs exist than were returned |
utxos[] | array | Each {txid, vout, value_sat (string), script_hash} |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getutxos","params":["7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000", 100]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000",
"total": 3,
"returned": 3,
"truncated": false,
"utxos": [
{ "txid": "f31a7c059e64b2d80a5c3e71b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20",
"vout": 0, "value_sat": "100000000000",
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000" },
{ "txid": "28c07e1a538b2f4d96e05a7c31d48f6b20f31a7c059e64b2d80a5c3e71b94d6f",
"vout": 1, "value_sat": "20000000000",
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000" },
{ "txid": "6f28c07e1a538b2f4d96e05a7c31d48f6b20f31a7c059e64b2d80a5c3e71b94d",
"vout": 0, "value_sat": "5000000000",
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000" }
]
}
}
gettxout live
gettxout [txid hex-64, vout?] → object
Exact spent/unspent status for one specific outpoint. This is the precision instrument of the whole read surface: it works regardless of how many outputs an address holds.
at_slot is the answering node's head — not the output's age
at_slot is the node's current head slot at the moment it answered, NOT the slot the output was created in. No RPC exposes an output's creation height. If you need "this output existed at or before height H", read getblockcount in the same round-trip — see the crediting rule.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | txid | hex-64 | Client-computed consensus txid |
| 1 | vout | number | Optional |
Response fields
| Field | Type | Notes |
|---|---|---|
txid, vout | hex-64 / number | Echoed |
unspent | boolean | |
utxo | object | null | {txid, vout, value_sat (string), script_hash} when unspent |
at_slot | number | Answering node's current head slot — see callout above |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"gettxout","params":["f31a7c059e64b2d80a5c3e71b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20", 0]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"txid": "f31a7c059e64b2d80a5c3e71b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20",
"vout": 0,
"unspent": true,
"utxo": {
"txid": "f31a7c059e64b2d80a5c3e71b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20",
"vout": 0,
"value_sat": "100000000000",
"script_hash": "7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000"
},
"at_slot": 71182
}
}
getmempoolinfo live
getmempoolinfo [] → object
Mempool occupancy and fee look-ahead. Read next_base_fee_millisat_per_gas here or from getchaininfo immediately before building a transaction.
Parameters
None — pass [].
Response fields
| Field | Type | Notes |
|---|---|---|
size, max | number | max is 4096 |
bytes | number | Total serialized bytes queued |
next_base_fee_millisat_per_gas | string | One-block look-ahead base fee |
barred, barred_hits | number | Currently barred sources / hits against bars |
expired, evicted_low_fee | number | Lifetime drop counters (TTL expiry / fee eviction) |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getmempoolinfo","params":[]}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"size": 2,
"max": 4096,
"bytes": 9178,
"next_base_fee_millisat_per_gas": "1024",
"barred": 0,
"barred_hits": 0,
"expired": 1,
"evicted_low_fee": 0
}
}
sendrawtransaction live
sendrawtransaction [hex] → object
Submit signed transaction bytes. The response is a queue acknowledgement, not inclusion — the transaction has been admitted to the mempool, nothing more. Track inclusion yourself, by the txid you computed client-side before broadcast (see Transactions & fees).
txid field in the response — deliberately
The tx_hash field is a node-local digest over the raw bytes, not the consensus txid. The consensus txid is SHA3-256("BLCH4:TXID" ‖ spend_signing_root), computable client-side before you broadcast. Everything downstream (outpoints, gettxout, reconciliation) keys on the txid you computed — never on tx_hash.
Parameters
| Position | Name | Type | Notes |
|---|---|---|---|
| 0 | hex | string | Serialized signed transaction, hex-encoded |
Response fields
| Field | Type | Notes |
|---|---|---|
accepted | boolean | true on admission |
status, kind | string | Queue status; transaction kind |
bytes | number | Size as admitted |
tx_hash | hex-64 | Node-local digest over raw bytes — not the consensus txid |
tx_hash_note | string | The node says the above itself |
confirmation | string | States that this is queue acknowledgement, not inclusion |
Example
curl -s -X POST https://posternlabs.com/g4rpc -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"sendrawtransaction","params":{"hex":"06a1b2c3…"}}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"accepted": true,
"status": "accepted",
"kind": "transfer_v2",
"bytes": 4890,
"tx_hash": "b94d6f28c07e1a538b2f4d96e05a7c31d48f6b20f31a7c059e64b2d80a5c3e71",
"tx_hash_note": "node-local digest over raw bytes; not the consensus txid — compute the txid client-side",
"confirmation": "queue acknowledgement only; not evidence of inclusion"
}
}
Resubmitting byte-identical bytes while the transaction is pending returns Admitted::Duplicate — a success, not an error. See error codes for the retry discipline on refusals.
Methods that are routed but refuse — by design
gettransaction [txid] → error -32005 · getnewaddress [] → error -32006
gettransactionrefuses — permanently answers-32005 NO_TRANSACTION_INDEX. This chain has no txid→block index, by design. There is no server-side transaction lookup to wait for; build your tracking on client-computed txids +gettxout/getutxoscorrelation. See Transactions & fees.getnewaddressrefuses — answers-32006 NO_WALLET. The node holds no wallet and no keys; address generation is a client-side operation (Addresses & keys).
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32005, "message": "no transaction index on this chain (by design)" }
}
Next-release methods
The three methods below are in source at commit 72e5525 and arrive with the next fleet release. Today both the public proxy and the direct nodes refuse them. Documented here so you can build against the shapes; do not ship code that assumes they answer yet.
getvalidators next release
getvalidators [] → array
The whole validator registry in one call: for each validator, index, pubkey_hash, status, effective_stake_sat (string), and commission_bps. Note an inconsistency to plan for: commission_bps is a plain number on this method, while getvalidator returns it as a string.
gettxstatus next release
gettxstatus [txid hex-64] → { status }
Returns {"status": "pending" | "included" | "justified" | "finalized" | "unknown"}. This is a tracking convenience, not a settlement signal: do not use its "finalized" string as the crediting trigger — the crediting rule (finalized-height cross-reference, prudence margin, two-node agreement) remains normative.
getbuildinfo next release
getbuildinfo [] → object
Returns build_version, commit, source_digest (SHA3-256 over the source set), rustc, target, and similar build provenance. Operational use: compare source_digest across the nodes you trust — two nodes claiming the same version with different digests are not running the same code.
Error codes
Standard JSON-RPC
| Code | Meaning |
|---|---|
-32700 | Parse error |
-32600 | Invalid request — also returned for batch arrays, which are refused: one call per request |
-32601 | Method not found |
-32602 | Invalid params |
-32603 | Internal error |
Dedicated codes
| Code | Name | Meaning & handling |
|---|---|---|
-32000 | BLOCK_NOT_FOUND | No such block |
-32001 | VALIDATOR_NOT_FOUND | No such validator index |
-32002 | TX_DECODE_FAILED | Bytes did not decode as a transaction |
-32003 | MEMPOOL_FULL | Capacity, not a verdict on your transaction — retry later |
-32004 | NODE_UNAVAILABLE | Consensus thread busy — retry |
-32005 | NO_TRANSACTION_INDEX | Permanent. This chain has no txid→block index by design |
-32006 | NO_WALLET | Permanent. The node holds no wallet |
-32007 | SLOT_EMPTY | Normal under PoS — no canonical block at that slot; advance |
-32008 | TX_REFUSED | Terminal. Never resubmit those bytes; rebuild the transaction |
-32009 | TX_REFUSED_RETRYABLE | State-dependent bar that lapses; error.data = {retryable: true, until_slot: N} — bar duration ≈ 64 min (REJECTION_TTL_SLOTS = 128) |
-32010 | see callout | Meaning depends on which layer answered — read the callout below |
-32010 means two different things — know which layer answered
- At the node:
TX_REFUSED_SOURCE_CAP— this spend-authority source already has 64 transactions pending (MEMPOOL_MAX_PER_SOURCE = 64). Wait for inclusion or expiry, then submit more. - At the public proxy: "no read quorum" — fewer than 2 upstream nodes agreed on a branch-sensitive read. Nothing to do with your transaction; retry, or corroborate against the direct nodes.
If you call the public proxy, assume the proxy meaning for reads and the node meaning for sendrawtransaction — and log the raw error body either way.
Retry discipline
-32008: rebuild, never resubmit. The bytes are terminally refused.-32009: retry afteruntil_slot, or rebuild against the current base fee — usually faster.-32003/-32004: capacity/availability — back off and retry the same bytes.- Identical duplicate bytes while pending:
Admitted::Duplicate— a success response, not an error. Safe to use as an idempotent redelivery strategy.
Transactions & fees
The txid: computed by you, before broadcast
txid = SHA3-256( "BLCH4:TXID" ‖ spend_signing_root )
The consensus transaction id is deterministic and non-malleable — it excludes witnesses, so no third party can mutate it in flight. Because it is a pure function of the spend signing root, you compute it client-side, before broadcast. Outpoints are (txid, vout). This txid is how you track everything on this chain: the node will never look a transaction up for you (no transaction index, by design), but gettxout(txid, vout) answers exactly for any outpoint you know.
Fees: exactly one valid base fee
- A transaction commits to exactly one valid base fee — the one committed by the block that includes it. There is no fee range, no tolerance.
- Read
next_base_fee_millisat_per_gas(fromgetchaininfoorgetmempoolinfo) immediately before building — it is a one-block look-ahead. - If the base fee moves before your transaction is included, the signed bytes fail
ValueNotConserved. Rebuild against the new fee; do not resubmit. - Conservation is an equality:
spent == created + fee— not a tolerance.
TransferV2 (wire tag 0x06)
Live since epoch 800. Witness-table format: ~40 bytes per input instead of full per-input keys as in V1. The practical input ceiling rises from ~61 inputs (V1) to ~1,000 — which is also the UTXO enumeration bound (getutxos limit clamp), so the two constraints align. V1 remains valid; a single-input payment may reasonably choose V1.
Mempool & block mechanics
- Transactions in a block are packed by
tip_millisat_per_gasdescending. - Per-source pending cap: 64 transactions per spend-authority source (
-32010at the node when exceeded). - Mempool TTL: 100 slots unincluded → dropped. Rebuild and resubmit after expiry (the fee has almost certainly moved by then anyway).
There is no fee/tip history endpoint and no way to ask the chain "what did transaction X pay, and when was it included?" after the fact. Correlate client-side, keyed by the txid you computed before broadcast: record (txid, inputs, outputs, fee, next_base_fee at build time) in your own database at build time, then confirm outpoint existence via gettxout/getutxos. Plan fee-reconciliation tooling around this from day one — it cannot be bolted on later by querying the chain.
Client-side signing flow
- Select inputs —
getutxosfor the first page;gettxoutfor exact checks. - Read
next_base_fee_millisat_per_gas— immediately before building. - Set tip and outputs.
- Fix the
tx_bytesbudget — Falcon signatures vary in length; budget the declared size, not the size of one sample signature. - Sign — the
spend_signing_rootfixes the txid at this moment; record it. - Broadcast —
sendrawtransaction {"hex": …}; track by your recorded txid.
Addresses & keys
Address format
bloch1q + 48 hex = 55 characters
- The 48 hex characters are: 40 hex = the 20-byte SHA3-256 prefix of the raw public key (this is not RIPEMD — there is no hash160 anywhere), followed by 8 hex = a SHA3 double-checksum.
- The network emits lowercase. The reference parser accepts mixed-case hex — validate leniently, emit lowercase.
# illustrative address anatomy
bloch1q 7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4 0a9b3c7d
prefix 20-byte SHA3-256 prefix of raw pubkey checksum
(7) (40 hex) (8 hex)
script_hash — what every balance/UTXO method keys on
The 32-byte script_hash has two valid forms:
| Form | Construction | Derivable from an address? |
|---|---|---|
| Carried (legacy-compatible) | The 20 address bytes + 12 zero bytes | Yes — from the address string alone |
| Native | Full 32-byte hash | No — requires the raw public key; not expressible as an address |
To derive the Carried script_hash from an address: strip bloch1q, take the first 40 hex characters, zero-pad to 64 hex.
# address -> Carried script_hash (shell)
addr="bloch1q7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f40a9b3c7d"
hex="${addr#bloch1q}" # drop the prefix
hex="${hex:0:40}" # 20 address bytes (drops the 8-hex checksum)
printf '%s%024d\n' "$hex" 0 # zero-pad to 64 hex
# -> 7d02c4a19e5f60b3d8a1f2c47e9b0d6a35c8e1f4000000000000000000000000
// address -> Carried script_hash (JS)
function scriptHashFromAddress(addr) {
const a = addr.toLowerCase();
if (!/^bloch1q[0-9a-f]{48}$/.test(a)) throw new Error("not a bloch address");
// NOTE: a full implementation verifies the 8-hex SHA3 double-checksum here.
return a.slice(7, 47).padEnd(64, "0");
}
Keys are hybrid ML-DSA-65 ‖ Falcon-1024; the node never sees them — generation, storage, and signing are entirely client-side (see the signing flow).
Crediting deposits — the rule
Normative procedure for anyone crediting incoming payments: exchanges, merchants, custody providers. It exists because this chain has no transaction index and no per-output creation height — detection and settlement are built from primitives.
1. Detect
- Poll
getbalance(script_hash)cheaply per deposit address. - On change, call
getutxosto see which outpoints arrived. - Use
gettxout(txid, vout)to confirm a specific expected payment — the only exact check for addresses past 1,000 outputs.
2. Cross-reference an observed height
When gettxout shows unspent: true, read height H from getblockcount in the same round-trip. You now know: the output existed at or before height H. (Remember: gettxout.at_slot is the node's head, not the output's age — this cross-reference is the substitute for a creation height the RPC does not expose.)
3. Credit
Credit the deposit when all of the following hold:
finalized_height ≥ H, plus- a prudence margin of ~30 epochs (~8 h) of continued, uninterrupted finality advance beyond that, and
- two independently operated nodes agree on the same finalized root at the same epoch.
Reverify immediately before releasing funds.
Finality on this young network is not yet backed by live slashing economics. The ~30-epoch margin and the two-node agreement requirement are deliberate compensations. Choosing a lower margin is a legitimate business decision — but document it as an accepted risk, not as "finality means final".
Reorg handling
Above the finalized checkpoint, reorgs to any depth are normal LMD-GHOST behavior for included-but-unfinalized transactions. An included transaction is not settled; only the rule above settles.
Run a node
The source is public: github.com/tiagobeltraoacioli-sketch/bloch-sis-pow. Running your own observer node is the recommended posture for any serious integrator: it validates every block independently, serves the full RPC surface, and signs nothing (no validator.key).
A brand-new node cannot join mainnet right now. The 2016-epoch weak-subjectivity trust window closed on 2026-09-05 07:07:19 UTC and no signed checkpoint has been published yet. A fresh sync will refuse to complete with ERR_WS_REQUIRE_CHECKPOINT — by design, to reject forged histories rather than accept them silently.
Nodes seeded from an existing data directory (blocks.log, meta.bin, ws_latest.bin) are unaffected. When the signed checkpoint ships (via --ws-checkpoint + --ws-signer-set), this section will be updated. This is the #1 fact for node operators today; everything below works once you have either a checkpoint or a seeded data directory.
Requirements
- Linux x86-64; 2+ vCPU, 8 GB RAM, 80 GB SSD.
- Full replay from genesis measured ≈ 21–22 min (~34k blocks, peak RSS ~934 MB). Replay cost is O(chain length) — there is no snapshot layer yet, so expect this to grow with the chain.
Build
git clone https://github.com/tiagobeltraoacioli-sketch/bloch-sis-pow
cd bloch-sis-pow
# genesis artifacts: verify the carryover checksum BEFORE unpacking
shasum -a 256 -c carryover.tsv.gz.sha256
gunzip -k carryover.tsv.gz # -> carryover.tsv (~55 MB, 452,726 opening outputs)
# build FROM THE CRATE DIRECTORY — rust-toolchain.toml lives inside it;
# the workspace root resolves a different pinned toolchain
cd crates/bloch-pos-node
cargo build --locked --release
# binary: target/release/bloch-pos
genesis/mainnet.manifestships in the repo;carryover.tsv.gz+ its.sha256sit at the repository root and must be verified and decompressed as above — the node wants the plain.tsv.- The node refuses to start on a carryover/manifest mismatch (four manifest-committed fields are checked), so a corrupted or wrong-version
.tsvfails loudly, not silently.
Run (observer posture)
# stage the genesis artifacts wherever you keep node data
mkdir -p /var/lib/bloch
cp genesis/mainnet.manifest carryover.tsv /var/lib/bloch/
./target/release/bloch-pos run \
--data-dir /var/lib/bloch/data \
--genesis /var/lib/bloch/mainnet.manifest \
--carryover /var/lib/bloch/carryover.tsv \
--transport devnet \
--listen 19100 --listen-addr 127.0.0.1 \
--peers 139.180.166.5:19100,139.180.173.231:19100 \
--rpc-port 16310 --rpc-bind 127.0.0.1
- Bootnodes (devnet transport — plain host:port, not libp2p multiaddrs):
139.180.166.5:19100,139.180.173.231:19100. - RPC defaults to
127.0.0.1:16310. Never widen--rpc-bind— front the node with your own authenticated proxy instead. On newer builds, the env varBLOCH_RPC_HOST_ALLOWLISTwidens the HTTPHostallowlist when your proxy needs it. - Observer nodes carry no
validator.keyand can be restarted freely.
Observability next release
/health and /metrics (Prometheus, bloch_pos_* series — behind_by_slots, finalized_epoch, peer_count, equivocations_observed_total, …) exist on next-release builds, behind --metrics-port, off by default. On today's fleet binary, monitor via the RPC surface (getchaininfo.behind_by_slots and transport.peers).
SDKs & tooling
The repository's sdk/typescript, sdk/python and sdk/go directories are legacy Genesis-3-era client scaffolds — machine-generated from an OpenAPI spec of the retired proof-of-work chain, explicitly labeled scaffold / pre-production / unaudited. Their method surface, constants, and disclosures describe the closed Genesis-3 chain and must not be pointed at Genesis-4. Treat them as historical reference material only.
The practical integration path today:
- Direct JSON-RPC. This portal's method reference is the contract — the surface is small enough that a thin typed client in your own codebase is an afternoon's work.
- The wallet PWA as reference implementation. posternlabs.com/apps/wallet/ signs entirely client-side; study it for the transaction-building and signing flow.
- Contributing a Genesis-4 typed client is the natural community contribution: the frozen method registry in the node source (
src/rpc/method_registry.rs, withtests/rpc_method_registry.rspinning it) is the surface to generate from.
Other tooling: the explorer at blochl1.com (blocks, validators, epochs, analytics, transactions).
Integration reference documents for exchanges and partners exist and are provided on request via Postern Labs — they are not published here.
EVM roadmap & status
No EVM-compatible execution layer is operating on Genesis-4. There is no EVM node software to download, and offering one here would be a lie.
What is true, precisely:
- The Genesis-4 L1 is an eUTXO chain. Smart-contract execution is roadmap, not product.
- Chain id 8400 (
0x20d0) is reserved, but it refers to a legacy Genesis-3-era L2 scaffold that is retired, not extended. Please do not deploy against chain id 8400 expecting a current network — nothing is listening on the other end that you should build on. - The successor plan — EVM at L1 — is an explicit draft with no code on either track yet.
If you need EVM execution for your use case, the honest advice is: not here, not yet. Watch this page and the repository for the flag-day announcement when an execution layer ships; this section will change from a warning into documentation on that day.
Security & disclosure
Coordinated disclosure
- Report via the repository's security advisory flow, or encrypted contact, with the subject tag
[bloch-security]. - Acknowledgement target: ≤ 2 days.
- In scope: Genesis-4 consensus, P2P/RPC surfaces, hybrid signature verification, privacy findings.
Audit status — stated plainly
No external third-party audit of the post-quantum stack has been contracted to date. Internal tool-assisted audits are continuous. If your risk model requires an external audit, that requirement is not yet met — factor it in.
Decentralization — stated plainly
The chain is young and run by a small operator set today; the validator-opening program is staged. Do not build assumptions of a large independent validator set into your threat model yet — the crediting rule's margins exist precisely because of this.