13 Commits

Author SHA1 Message Date
b66b0a65c8 feat: offer only signers that can sign for Quantus
All checks were successful
deploy / build (push) Successful in 2m25s
deploy / deploy-web (push) Successful in 8s
The Quantus runtime accepts only ML-DSA signatures. qapi offered every signer
polkahub knows, and none of those could sign here:

- Polkadot Vault, Ledger and WalletConnect produce sr25519, ed25519 or ecdsa
  signatures (and WalletConnect was configured only for Polkadot, Kusama and
  Paseo);
- the external multisig signer hands signing to multisig.usepapi.app;
- the browser-extension list offered polkadot{.js}, Nova, Talisman,
  SubWallet, PolkaGate and Fearless.

These providers, their management buttons, and the Vault and multisig modals
are removed. What remains: browser extensions, read-only (watch) addresses,
and proxies, which sign through whichever signer they wrap.

@polkahub/pjs-wallet is patched (pinned to 0.9.1, like the substrate-bindings
patches) so the extension list is blackbeard alone, with its sigil as the
logo. Its provider only ever sees, offers or connects blackbeard: anything
else injected into window.injectedWeb3 is ignored, not merely unlabelled.

Verified against a local build in Firefox with blackbeard installed. The
Account Providers panel shows blackbeard (with the sigil), Address and Proxy,
and nothing else, including after fake polkadot-js and talisman entries were
injected into the page and the provider's 2 s re-scan had run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 19:51:02 +03:00
ad670842b3 fix: say which block is missing instead of "e is not an object"
All checks were successful
deploy / build (push) Successful in 2m36s
deploy / deploy-web (push) Successful in 7s
Diagnosed. It is not the Quantus header layout and it is not the header patch —
it is a missing null check in papi, surfacing through three layers of minified
code.

`archive.header$` in @polkadot-api/observable-client is

    const header$ = (blockHash) => rawHeader$(blockHash).pipe(map(blockHeader[1]))

with no guard, and `archive_v1_header` answers `{"result": null}` for any hash
the node does not know — a normal answer, not a fault. scale-ts then reaches
`new DataView(null)`. V8 words that "First argument to DataView constructor must
be an ArrayBuffer"; Firefox words the identical error "e is not an object",
which mentions neither blocks nor headers nor the node.

The trigger is a chain switch. The block list still holds the previous chain's
hashes, every one is fetched from the new client, chainHead reports them
unpinned, and the archive returns null for each — hence a burst of identical
errors, one per listed block, milliseconds apart, then silence. Harmless: the
list refreshes a moment later. It just said nothing a reader could act on.

Reproduced deliberately (switch Quantus -> Heisenberg -> Quantus) rather than
waiting for it, and confirmed in the browser before and after:

    before  fetch header failed TypeError: e is not an object
    after   fetch header failed Error: no block header: the node does not know this block

The null check lives in the patched decoder because that is the narrowest place
we control that sees the value. The real fix belongs upstream in
observable-client, where `header$` should not map a null result through a codec
at all.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 10:36:58 +03:00
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
933ffecc8f feat: accept the Quantus signature type, and list Quantus chains
## The check that fired before code that would have worked

`getSignerType` pulls the extrinsic's `Address` and `Signature` types **out of
the metadata** — the runtime-as-oracle approach, and better than polkadot-js,
which hardcodes `ExtrinsicSignature: 'MultiSignature'` in a type definition file.
Then it threw the answer away:

    if (signature.type !== "enum" || ["Ecdsa", "Ed25519", "Sr25519"].some(...))
      throw unkownSignerType();

Quantus's `DilithiumSignatureScheme` carries `Dilithium87` and `Dilithium65`, so
this raised `Unkown signer` and nothing downstream ever ran. Asking the runtime
and then not believing it is the trap this whole port exists to avoid.

Everything past that point was already correct: `createV4Tx` concatenates and
compact-prefixes and never asserts a signature size, and the address prefix is
the `MultiAddress::Id` variant index read from metadata. The name whitelist only
separated the Substrate shape from the Ethereum one, and the Ethereum arm one
line above already decides that structurally. So the patch drops the names and
keeps `signature.type !== "enum"`.

pnpm `patchedDependencies`, keyed on the package name rather than a version, so
it reaches all fifteen resolved copies in the tree — the nested ones under
pjs-signer, ledger-signer, raw-tx-creator and polkadot-api itself are what
actually run.

## Evidence

Byte-for-byte against the quantus/extension#7 tier-1 harness, same call, same
nonce, same signature:

    papi assembled 7300 bytes
    IDENTICAL to the tier-1 harness

That is the corroboration this repo exists for. polkadot-api shares no code with
the five polkadot-js forks, so until now our stack had only agreed with itself.

Also verified the patch is load-bearing and narrow:

  - the unpatched copy still in the store raises `Unkown signer` on the same
    inputs, so the check really was the blocker
  - Polkadot metadata still takes the Substrate path, and still gets its `01`
    variant byte prepended when a caller names a signing type

## Networks

Quantus, Heisenberg and Planck, with the endpoints verified reachable on
2026-09-15, and Quantus as the default. The Substrate networks stay: a signature
that works on both is the only evidence that the patch widened a check rather
than broke one.

Refs #1, #3. Closes quantus/extension#7's cross-tier comparison.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-15 22:16:40 +03:00
Victor Oliva
473eb011c8 feat: add thousands separators to view and edit components 2026-03-12 15:57:52 +01:00
Victor Oliva
51d82f2640 refactor: use top-level client to get blocks 2025-04-25 10:08:11 +02:00
Victor Oliva
8c4daa6257 update json-view 2025-02-27 09:50:30 +01:00
Victor Oliva
9963736a10 update polkadot-api 2025-02-27 09:26:50 +01:00
Victor Oliva
aa3edcccbd feat: remember selected chain on reload 2024-12-06 11:52:45 +01:00
Victor Oliva
479bf1226d update papi to latest version 2024-11-05 18:20:58 +01:00
Victor Oliva
b6040c7518 fork chart 2024-10-30 23:53:35 +01:00
Victor Oliva
23b05a85a3 patch observable and substrate client with next version 2024-10-30 11:58:12 +01:00
Victor Oliva
86e376b39f feat: add json view 2024-10-23 15:57:51 +02:00