rob thijssen 84f60959d9 fix: decode Quantus block headers, which is what was actually broken
The console rendered nothing against Quantus and I had reported tier 3 as
working on the strength of a unit-level test. It was not working. This is the
fault.

`qp_header::Header` is not `sp_runtime::generic::Header`. `number` is a plain
u32 where Substrate writes a Compact, and a `zk_tree_root: H256` sits between
`extrinsics_root` and `digest` — deliberately, so the ZK root has a fixed offset
in the header preimage that miners cannot shift by manipulating the digest.

Reading a Quantus header as a Substrate one consumes 2 bytes where 4 were
written. Every field after shifts, and the digest vector eventually reads a byte
that is not a known DigestItem index, which surfaces three layers away as
`TypeError: innerDecoder is not a function`. papi retried the subfollow forever,
`runtime$` never emitted, and the page stayed blank.

The patched codec carries both layouts and picks by **round trip**: a layout is
right only if decoding and re-encoding reproduces the input byte for byte, which
also catches a short read, trailing bytes and a non-canonical compact. Trying
them in a fixed order and taking the first that survives is deterministic;
choosing by inspecting a byte would not be.

Pinned per version rather than by bare package name, unlike the signers-common
patch: four copies of substrate-bindings resolve in this tree and blockHeader.js
is not identical across majors — a bare-name key fails the whole install trying
to patch 0.19.0 and 0.6.0.

Two things this cost, both recorded because they are the kind that recur:

  - My first attempt replaced the codec with a bare [enc, dec] pair, which
    dropped scale-ts's `inner`. This console's own block.state.ts does
    `blockHeader.inner.digests.inner`, so that module threw while evaluating,
    `createRoot().render()` never ran, and the page was blank with nothing in
    the UI — the same symptom as the bug, from the opposite cause. The patch now
    copies the original codec and overrides only the decoder.
  - The Poseidon block hash is NOT a second blocker, which I had claimed on #1
    and #4 without checking. `getHasherFromHeader` returns a function that
    throws rather than throwing, and only the extrinsic Analyzer page consumes
    `hasher$`. Everything else works without it.

Verified in a browser this time, not by unit test: against mainnet the console
renders live data — Finalized 53,989, Best 54,089, block time — advancing, and
matching what the node reports. Polkadot still decodes through the same patched
codec, and garbage is refused rather than mis-read.

Residual: four "fetch header failed TypeError: e is not an object" on first load,
from the per-block detail fetch, not recurring since. Not diagnosed.

Refs #1, #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 09:37:55 +03:00
2026-08-24 12:50:13 +02:00
2024-10-29 12:50:59 +01:00
WiP
2024-11-06 14:09:52 +01:00
2026-08-24 12:50:13 +02:00
2024-11-11 12:51:08 +07:00
2026-04-08 09:48:38 +02:00
2024-10-22 14:29:38 +02:00
2024-11-09 23:58:58 +07:00
2026-08-24 12:50:13 +02:00

qapi console

A fork of the papi console, pointed at the Quantus chains and patched to accept their post-quantum signature type.

Deployed at https://qapi.blackbeard.observer.

Why this fork exists

The console is built on polkadot-api, which shares no code with the polkadot-js stack the Quantus browser extension forks (quantus/extension, quantus/common, quantus/ui, quantus/wasm). That makes it an independent implementation of the same wire format — and the only way to find out whether our stack is right about Quantus or merely self-consistent.

It already is: for the same call, nonce and signature, createV4Tx here produces bytes identical to quantus/extension's tier-1 harness.

The one patch

getSignerType in @polkadot-api/signers-common reads the extrinsic's Address and Signature types out of the metadata — the right thing, and better than polkadot-js, which hardcodes ExtrinsicSignature: 'MultiSignature' in a type definition file. It then threw that answer away and required the enum to carry Ecdsa, Ed25519 and Sr25519.

Quantus's DilithiumSignatureScheme carries Dilithium87 and Dilithium65, so every signing attempt raised Unkown signer before reaching code that would have worked — createV4Tx is entirely length-agnostic, and a 7 219-byte ML-DSA-87 signature drops straight in.

patches/@polkadot-api__signers-common.patch drops the names and keeps the structural check. See quantus/papi-console#1.

Develop

pnpm install
pnpm dev

Deploy

Pushes to main deploy to oolon via .gitea/workflows/deploy.yaml. One-time host provisioning — DNS, certificate, nginx vhost, webroot — is script/infra-setup.sh, run by an operator:

./script/infra-setup.sh --pubkey ~/.ssh/id_gitea_ci.pub

Conventions: architecture/deployment-gitea-actions.md, reverse-proxies.md, external-tls.md, public-dns.md.


Upstream's own README, and the licence, are unchanged: this is a fork, not a rewrite.

Description
Fork of polkadot-api/papi-console (AGPL-3.0). A dev console for signing real Quantus extrinsics from the browser extension. Built on polkadot-api, which reads the extrinsic's Address and Signature types from metadata and assembles signed extrinsics length-agnostically — so it needs one patch, not a fork chain, and gives an independent check on the polkadot-js side.
https://qapi.blackbeard.observer
Readme AGPL-3.0 3.5 MiB
Languages
TypeScript 96.4%
Shell 2.4%
CSS 0.9%
JavaScript 0.2%