Compare commits

9 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
9d7b002464 feat: label the blackbeard extension by name
All checks were successful
deploy / build (push) Successful in 2m33s
deploy / deploy-web (push) Successful in 8s
The Quantus browser wallet now injects itself as `blackbeard` instead of
`polkadot-js` (quantus/extension#15), so it no longer collides with the real
polkadot{.js} extension. qapi already found it by enumeration; this gives it
a label in the extension list instead of the raw key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 19:36:51 +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
32d8a6ecab chore: updated repo paths
All checks were successful
deploy / build (push) Successful in 2m21s
deploy / deploy-web (push) Successful in 7s
2026-09-16 10:28:51 +03:00
2c8cefab07 fix: remove the fork-with options, and name the workflow step for what it does
All checks were successful
deploy / build (push) Successful in 5m11s
deploy / deploy-web (push) Successful in 6s
**Fork with Chopsticks / Forklift is gone.** Neither can fork a chain in this
list. Chopsticks is built on `@polkadot/types`, which cannot decode a Quantus
block at all — it caps fixed arrays at 2048 bytes where ML-DSA signatures are
5261 and 7219, reads the extrinsic preamble byte as a version, and knows nothing
of this chain's header layout. Forklift resolves its own unpatched copy of
`@polkadot-api/substrate-bindings`, so it hits the very header bug this console
patches around.

So the two buttons offered things that could only fail, and fail without saying
why. Removed rather than disabled: a greyed-out control still asks the reader to
work out why. `forkMethod` stays in SelectedChain because the hash params and
chain.state still read it; nothing in the UI can now set it to anything but
"none", so the two light-client guards that reset it go with it.

**The workflow step called "the patch is applied"** — an assertion sitting in a
list of imperatives (install, build, ship the bundle), reading like a claim CI
was making rather than a thing it was doing. Now "verify the signers-common
patch".

Verified in a browser: the switcher shows Quantus, Heisenberg, Planck and
Localhost with both endpoints per chain and no FORK WITH row, and the explorer
still runs — Finalized 54,153, Best 54,253.

Refs #3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 10:16:48 +03:00
6358367b61 feat: offer every Quantus endpoint, and drop the chains that are not post-quantum
All checks were successful
deploy / build (push) Successful in 2m19s
deploy / deploy-web (push) Successful in 8s
**Every known endpoint per chain.** Each Quantus network listed only one RPC, so
the CONNECTION panel offered a single choice and a node going down took the
console with it. All of these were probed and answer `system_chain` today:

  Quantus     QTC  wss://rpc1-mainnet.quantus.com   wss://rpc2-mainnet.quantus.com
  Heisenberg  HEI  wss://a1-heisenberg.quantus.cat  wss://a2-heisenberg.quantus.cat
  Planck      PLK  wss://a1-planck.quantus.cat      wss://a2-planck.quantus.cat

rpc3/rpc4, a3, a4 and the b* names do not resolve, so they are not listed.
Planck's PLK symbol is from the node rather than assumed — it was a guess in the
first version of this file.

**Polkadot, Kusama, Paseo and Westend are gone**, with their parachains. Every
one signs with sr25519, ed25519 or ecdsa — discrete-log schemes Shor's algorithm
breaks together — so none has a quantum-safe account type to offer, and this
console could not sign for them anyway. Offering them made it look like a
general-purpose Substrate tool that happens to include Quantus, which is the
wrong way round.

I had kept them deliberately as a control, on the argument that a signature
working on both proves the signers-common patch widened a check rather than
broke one. That argument does not need them in the UI: the header codec's
Substrate layout still has a test against a real Polkadot header, and Localhost
and Custom can still point anywhere, so the round-trip disambiguation has to
keep working regardless.

Verified in a browser against the real chains, not by inspection: the switcher
lists Quantus, Heisenberg, Planck, Localhost and nothing else; selecting Quantus
offers rpc1 and rpc2, Heisenberg offers a1 and a2; and the explorer shows live
mainnet data — Finalized 53,999, Best 54,099.

Refs #3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 09:40:32 +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
0448d9df7a fix: say what is happening while connecting, and stop defaulting to a light client
All checks were successful
deploy / build (push) Successful in 5m28s
deploy / deploy-web (push) Successful in 8s
The deployed page was a bare black screen for minutes with nothing but the tab
spinner to suggest it was not simply broken. Two separate faults.

**No fallback.** `main.tsx` wraps the whole app in
`<Subscribe source$={merge(dynamicBuilder$, …)}>`, and Subscribe's `fallback`
defaults to null — "it will render null until the subscription exists".
`dynamicBuilder$` does not emit until a chain has been followed and its metadata
decoded, so until then the document body is empty. On a slow connection that is
seconds of nothing; on a chain the console cannot read it is forever, and a
visitor cannot tell the two apart. There is now a splash naming the chain and
the endpoint, which escalates at 8s and again at 25s and tells the reader how to
force a different network.

**The default endpoint was the light client.** `defaultSelectedChain` used
LIGHT_CLIENT_ENDPOINT unconditionally, which routes through smoldot — and
smoldot needs a chain spec. The Quantus networks have none
(`hasChainSpecs: false`), so the default asked it to follow a chain it had never
heard of. It now picks AUTO_RPC for any network without chain specs, which is
what the endpoints in quantus.json are for.

This does not make the console work against Quantus. It makes it explain itself
while failing, which it could not do before — the remaining fault is much
deeper and is written up on #1.

Refs #1, #3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 09:14:22 +03:00
40927fa910 fix: build on the rust runner, because fedora-44's pnpm cannot start
All checks were successful
deploy / build (push) Successful in 2m21s
deploy / deploy-web (push) Successful in 8s
The first run of this workflow failed before it did anything:

    pnpm: error while loading shared libraries: libatomic.so.1:
    cannot open shared object file: No such file or directory
    ##[error]Process completed with exit code 127.

`@pnpm/exe` is a native binary and the fedora-4x runner images lack libatomic,
so the pnpm they advertise (gitea-runners.md §4) cannot run at all.
`runner-rust` is built on `runner-fedora-44` and adds gcc, musl-gcc and cmake,
which pull libatomic in as a side effect — which is why blackbeard.observer has
been building its SPA there without hitting this.

A workaround, not the fix. The fix is libatomic in the fedora-4x Containerfiles,
per gitea-runners.md §5 — bake build dependencies into the image, not the
workflow — and this should move back when that lands: a 4 CPU / 4 GiB image to
run `vite build` is more than this needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
2026-09-16 08:49:46 +03:00
19 changed files with 559 additions and 278 deletions

View File

@@ -28,9 +28,23 @@ env:
jobs:
build:
# fedora-44 carries node + npm + pnpm (gitea-runners.md §4) and nothing else
# is needed — there is no Rust half here.
runs-on: fedora-44
# `rust`, not `fedora-44`, despite there being no Rust here.
#
# The fedora-4x images list pnpm (gitea-runners.md §4) but their pnpm cannot
# run: `@pnpm/exe` is a native binary and the images lack libatomic, so the
# very first step dies with
#
# pnpm: error while loading shared libraries: libatomic.so.1
#
# `runner-rust` is built on `runner-fedora-44` and adds gcc, musl-gcc and
# cmake, which pull libatomic in as a side effect — which is why
# blackbeard.observer builds its SPA there and this one now does too.
#
# This is a workaround for a runner-image bug, not the fix. The fix is
# libatomic in the fedora-4x Containerfiles (gitea-runners.md §5: bake build
# dependencies into the image, not the workflow). Move back when that lands;
# a 4 CPU / 4 GiB image to run `vite build` is more than this needs.
runs-on: rust
steps:
- uses: actions/checkout@v4
@@ -44,7 +58,7 @@ jobs:
# shipping a console that rejects every Quantus signature at run time.
run: pnpm install --frozen-lockfile
- name: the patch is applied
- name: verify the signers-common patch
# Cheap, and it is the one thing about this fork that a normal build
# would not notice going missing. Without it every signing attempt dies
# with `Unkown signer` — in the browser, after deploy, to a user.

View File

@@ -30,7 +30,7 @@ signing attempt raised `Unkown signer` before reaching code that would have work
straight in.
`patches/@polkadot-api__signers-common.patch` drops the names and keeps the structural
check. See [quantus/papi-console#1](https://git.lair.cafe/quantus/papi-console/issues/1).
check. See [quantus/papi-console#1](https://git.lair.cafe/blackbeard/qapi/issues/1).
## Develop

View File

@@ -0,0 +1,112 @@
diff --git a/dist/codecs/blockHeader.js b/dist/codecs/blockHeader.js
index 0675e59633a14c34148e90aa8c32bcfbc437330c..3cb9f47e889827d3b9d2c8490418176eda2d7590 100644
--- a/dist/codecs/blockHeader.js
+++ b/dist/codecs/blockHeader.js
@@ -1,4 +1,4 @@
-import { enhanceCodec, Bytes, _void } from 'scale-ts';
+import { enhanceCodec, Bytes, _void, u32 } from 'scale-ts';
import '../utils/ss58-util.js';
import './scale/Binary.js';
import './scale/bitSequence.js';
@@ -33,7 +33,23 @@ const diggest = Variant(
[0, 4, 5, 6, 8]
);
const hex32 = Hex(32);
-const blockHeader = Struct({
+
+// Quantus patch: this console serves two header layouts.
+//
+// `sp_runtime::generic::Header` encodes `number` with #[codec(compact)] and has
+// no field between `extrinsics_root` and `digest`. Quantus's `qp_header::Header`
+// does neither: `number` is a plain u32, and a `zk_tree_root: H256` sits before
+// the 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. That surfaces three layers away as
+// `TypeError: innerDecoder is not a function`, papi retries the subfollow
+// forever, `runtime$` never emits, and the page renders nothing at all.
+//
+// See quantus/papi-console#4.
+const substrateHeader = Struct({
parentHash: hex32,
number: compactNumber,
stateRoot: hex32,
@@ -41,5 +57,76 @@ const blockHeader = Struct({
digests: Vector(diggest)
});
+const quantusHeader = Struct({
+ parentHash: hex32,
+ number: u32,
+ stateRoot: hex32,
+ extrinsicRoot: hex32,
+ zkTreeRoot: hex32,
+ digests: Vector(diggest)
+});
+
+const asBytes = (input) =>
+ typeof input === "string"
+ ? Uint8Array.from(
+ input
+ .slice(2)
+ .match(/../g)
+ ?.map((b) => parseInt(b, 16)) ?? []
+ )
+ : input;
+
+const sameBytes = (a, b) =>
+ a.length === b.length && a.every((x, i) => x === b[i]);
+
+// Decide by round trip, not by guessing. 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 the layouts in a
+// fixed order and taking the first that survives that check is deterministic;
+// picking one by inspecting a byte would not be.
+const decodeEitherLayout = (input) => {
+ // `archive.header$` in @polkadot-api/observable-client maps the RPC result
+ // straight through this decoder with no null check, and `archive_v1_header`
+ // answers `null` for any hash the node does not know. scale-ts then reaches
+ // `new DataView(null)`, which V8 reports as "First argument to DataView
+ // constructor must be an ArrayBuffer" and Firefox as "e is not an object" —
+ // neither of which mentions blocks, headers, or the node.
+ //
+ // It happens in bursts on a chain switch: the block list still holds the
+ // previous chain's hashes, and every one of them is unknown to the new node.
+ // Harmless, but it needs to say so.
+ if (input == null)
+ throw new Error("no block header: the node does not know this block");
+
+ const bytes = asBytes(input);
+ let firstError;
+
+ for (const codec of [substrateHeader, quantusHeader]) {
+ try {
+ const value = codec.dec(bytes);
+
+ if (sameBytes(codec.enc(value), bytes)) return value;
+ } catch (error) {
+ firstError ??= error;
+ }
+ }
+
+ throw firstError ?? new Error("block header matches no known layout");
+};
+
+// Keep every property the original codec carried and override only the decoder.
+// A scale-ts Struct exposes `inner` — its field codecs — and consumers reach for
+// it: this console's own block.state.ts does `blockHeader.inner.digests.inner`.
+// Replacing the codec with a bare [enc, dec] pair drops that, block.state.ts
+// throws while its module is evaluating, `createRoot().render()` never runs, and
+// the page is blank with no error on screen — the same symptom as the bug this
+// patch exists to fix, from the opposite cause.
+const blockHeader = Object.assign([], substrateHeader, {
+ 0: substrateHeader.enc,
+ 1: decodeEitherLayout,
+ enc: substrateHeader.enc,
+ dec: decodeEitherLayout
+});
+
export { blockHeader };
//# sourceMappingURL=blockHeader.js.map

View File

@@ -0,0 +1,112 @@
diff --git a/dist/codecs/blockHeader.js b/dist/codecs/blockHeader.js
index 0675e59633a14c34148e90aa8c32bcfbc437330c..3cb9f47e889827d3b9d2c8490418176eda2d7590 100644
--- a/dist/codecs/blockHeader.js
+++ b/dist/codecs/blockHeader.js
@@ -1,4 +1,4 @@
-import { enhanceCodec, Bytes, _void } from 'scale-ts';
+import { enhanceCodec, Bytes, _void, u32 } from 'scale-ts';
import '../utils/ss58-util.js';
import './scale/Binary.js';
import './scale/bitSequence.js';
@@ -33,7 +33,23 @@ const diggest = Variant(
[0, 4, 5, 6, 8]
);
const hex32 = Hex(32);
-const blockHeader = Struct({
+
+// Quantus patch: this console serves two header layouts.
+//
+// `sp_runtime::generic::Header` encodes `number` with #[codec(compact)] and has
+// no field between `extrinsics_root` and `digest`. Quantus's `qp_header::Header`
+// does neither: `number` is a plain u32, and a `zk_tree_root: H256` sits before
+// the 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. That surfaces three layers away as
+// `TypeError: innerDecoder is not a function`, papi retries the subfollow
+// forever, `runtime$` never emits, and the page renders nothing at all.
+//
+// See quantus/papi-console#4.
+const substrateHeader = Struct({
parentHash: hex32,
number: compactNumber,
stateRoot: hex32,
@@ -41,5 +57,76 @@ const blockHeader = Struct({
digests: Vector(diggest)
});
+const quantusHeader = Struct({
+ parentHash: hex32,
+ number: u32,
+ stateRoot: hex32,
+ extrinsicRoot: hex32,
+ zkTreeRoot: hex32,
+ digests: Vector(diggest)
+});
+
+const asBytes = (input) =>
+ typeof input === "string"
+ ? Uint8Array.from(
+ input
+ .slice(2)
+ .match(/../g)
+ ?.map((b) => parseInt(b, 16)) ?? []
+ )
+ : input;
+
+const sameBytes = (a, b) =>
+ a.length === b.length && a.every((x, i) => x === b[i]);
+
+// Decide by round trip, not by guessing. 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 the layouts in a
+// fixed order and taking the first that survives that check is deterministic;
+// picking one by inspecting a byte would not be.
+const decodeEitherLayout = (input) => {
+ // `archive.header$` in @polkadot-api/observable-client maps the RPC result
+ // straight through this decoder with no null check, and `archive_v1_header`
+ // answers `null` for any hash the node does not know. scale-ts then reaches
+ // `new DataView(null)`, which V8 reports as "First argument to DataView
+ // constructor must be an ArrayBuffer" and Firefox as "e is not an object" —
+ // neither of which mentions blocks, headers, or the node.
+ //
+ // It happens in bursts on a chain switch: the block list still holds the
+ // previous chain's hashes, and every one of them is unknown to the new node.
+ // Harmless, but it needs to say so.
+ if (input == null)
+ throw new Error("no block header: the node does not know this block");
+
+ const bytes = asBytes(input);
+ let firstError;
+
+ for (const codec of [substrateHeader, quantusHeader]) {
+ try {
+ const value = codec.dec(bytes);
+
+ if (sameBytes(codec.enc(value), bytes)) return value;
+ } catch (error) {
+ firstError ??= error;
+ }
+ }
+
+ throw firstError ?? new Error("block header matches no known layout");
+};
+
+// Keep every property the original codec carried and override only the decoder.
+// A scale-ts Struct exposes `inner` — its field codecs — and consumers reach for
+// it: this console's own block.state.ts does `blockHeader.inner.digests.inner`.
+// Replacing the codec with a bare [enc, dec] pair drops that, block.state.ts
+// throws while its module is evaluating, `createRoot().render()` never runs, and
+// the page is blank with no error on screen — the same symptom as the bug this
+// patch exists to fix, from the opposite cause.
+const blockHeader = Object.assign([], substrateHeader, {
+ 0: substrateHeader.enc,
+ 1: decodeEitherLayout,
+ enc: substrateHeader.enc,
+ dec: decodeEitherLayout
+});
+
export { blockHeader };
//# sourceMappingURL=blockHeader.js.map

View File

@@ -0,0 +1,89 @@
diff --git a/dist/ManagePjsWallet.js b/dist/ManagePjsWallet.js
index ec891b30a0bda8eb60b0ec647262ad334c04db89..03c93af0448018b58ef8abc2395e56b330d64038 100644
--- a/dist/ManagePjsWallet.js
+++ b/dist/ManagePjsWallet.js
@@ -4,38 +4,16 @@ import { cn, SourceButton } from '@polkahub/ui-components';
import { state, useStateObservable } from '@react-rxjs/core';
import { CircleQuestionMark } from 'lucide-react';
import { filter, switchMap } from 'rxjs';
-import nova from './assets/nova.webp.js';
-import pjs from './assets/pjs.webp.js';
-import subwallet from './assets/subwallet.webp.js';
-import talisman from './assets/talisman.webp.js';
-import polkagate from './assets/polkagate.webp.js';
-import fearless from './assets/fearless.webp.js';
+import blackbeard from './assets/blackbeard.svg.js';
import { pjsWalletProviderId } from './provider.js';
+// Only extensions that sign ML-DSA are listed: the Quantus runtime accepts no
+// other signature, so offering polkadot{.js}, Talisman and the rest would
+// connect wallets that can never sign here. Patched in qapi.
const knownExtensions = {
- "polkadot-js": {
- name: "Polkadot JS",
- logo: pjs
- },
- "nova-wallet": {
- name: "Nova Wallet",
- logo: nova
- },
- talisman: {
- name: "Talisman",
- logo: talisman
- },
- "subwallet-js": {
- name: "Subwallet",
- logo: subwallet
- },
- polkagate: {
- name: "Polkagate",
- logo: polkagate
- },
- "fearless-wallet": {
- name: "Fearless Wallet",
- logo: fearless
+ blackbeard: {
+ name: "blackbeard",
+ logo: blackbeard
}
};
const [pjsWalletPlugin$, useExternalizedPlugin] = externalizePlugin(pjsWalletProviderId);
diff --git a/dist/assets/blackbeard.svg.js b/dist/assets/blackbeard.svg.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dfb32200a16b6c32b258d0519cd6c820a3bf9ab
--- /dev/null
+++ b/dist/assets/blackbeard.svg.js
@@ -0,0 +1,4 @@
+// blackbeard's sigil, the Quantus post-quantum wallet (quantus/extension#15)
+var blackbeard = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIj4KICA8IS0tIGJsYWNrYmVhcmQub2JzZXJ2ZXIncyBzaWdpbCwgY2VudHJlZCBvbiBhIHNxdWFyZSBmb3IgaWNvbnMgLS0+CiAgPHJlY3Qgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiByeD0iNiIgZmlsbD0iIzBkMGIwOSIvPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMgMCkiPgogICAgPHBhdGggZD0iTTE3IDEgTDMyIDcgVjIwIEMzMiAyOSAyNSAzNSAxNyAzOSBDOSAzNSAyIDI5IDIgMjAgVjcgWiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYmQ4ODI5IiBzdHJva2Utd2lkdGg9IjEuNiIvPgogICAgPHBhdGggZD0iTTE3IDggTDI0IDEyIFYyMSBDMjQgMjYgMjAuNSAyOS41IDE3IDMxLjUgQzEzLjUgMjkuNSAxMCAyNiAxMCAyMSBWMTIgWiIgZmlsbD0iI2JkODgyOTIyIi8+CiAgICA8cGF0aCBkPSJNMTcgMTEgVjI4IiBzdHJva2U9IiNkODQ1M2EiIHN0cm9rZS13aWR0aD0iMS42Ii8+CiAgICA8cGF0aCBkPSJNMTMgMTUgTDE3IDExIEwyMSAxNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDg0NTNhIiBzdHJva2Utd2lkdGg9IjEuNiIvPgogIDwvZz4KPC9zdmc+Cg==";
+
+export { blackbeard as default };
diff --git a/dist/provider.js b/dist/provider.js
index a0a44532893fc9b9b3d9c449c40362e60cfa3b95..d8f90bf491ea5fc4f708d456d2c3037255bc6dc8 100644
--- a/dist/provider.js
+++ b/dist/provider.js
@@ -6,6 +6,10 @@ import { getSs58AddressInfo } from 'polkadot-api';
import { concat, timer, map, filter, take, interval, switchMap, defer, retry, fromEventPattern, startWith, catchError, tap, ignoreElements, NEVER, shareReplay, firstValueFrom, scan } from 'rxjs';
const pjsWalletProviderId = "pjs-wallet";
+// Patched in qapi: only extensions that can sign for a post-quantum chain are
+// offered or connected. Anything else injected into the page is ignored.
+const PQ_EXTENSIONS = ["blackbeard"];
+const getPqExtensions = () => getInjectedExtensions().filter((name) => PQ_EXTENSIONS.includes(name));
const createPjsWalletProvider = (opts) => {
const { persist, accountFormat } = {
persist: localStorageProvider("pjs-wallet-plugin"),
@@ -15,11 +19,11 @@ const createPjsWalletProvider = (opts) => {
const availableExtensions$ = state(
concat(
timer(0, 100).pipe(
- map(getInjectedExtensions),
+ map(getPqExtensions),
filter((v) => v.length > 0),
take(1)
),
- interval(2e3).pipe(map(getInjectedExtensions))
+ interval(2e3).pipe(map(getPqExtensions))
),
[]
);

87
pnpm-lock.yaml generated
View File

@@ -6,6 +6,9 @@ settings:
patchedDependencies:
'@polkadot-api/signers-common': 365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37
'@polkadot-api/substrate-bindings@0.21.0': 44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537
'@polkadot-api/substrate-bindings@0.21.1': 44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537
'@polkahub/pjs-wallet@0.9.1': f359348c980dcf8a9f7077a067ffbc79e23c166172a4fa1be36de5340072104d
react18-json-view: 962696fac5fe7ad3e94fd36dad07521c6c3eafd52f219ab335d52438387f6b66
importers:
@@ -50,7 +53,7 @@ importers:
version: 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings':
specifier: ^0.21.1
version: 0.21.1
version: 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator':
specifier: ^0.2.0
version: 0.2.0(rxjs@7.8.2)
@@ -5024,7 +5027,7 @@ snapshots:
'@paraspell/descriptors': 14.3.4(polkadot-api@3.1.0(esbuild@0.28.1)(rxjs@7.8.2))
'@paraspell/sdk-core': 14.3.4(typescript@7.0.2)(zod@3.25.76)
'@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-labs/hdkd': 0.0.31
'@polkadot-labs/hdkd-helpers': 0.0.33
polkadot-api: 3.1.0(esbuild@0.28.1)(rxjs@7.8.2)
@@ -5053,7 +5056,7 @@ snapshots:
'@polkadot-api/observable-client': 0.19.0(rxjs@7.8.2)
'@polkadot-api/sm-provider': 0.3.8(@polkadot-api/smoldot@0.4.7)
'@polkadot-api/smoldot': 0.4.7
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/utils': 0.4.0
'@polkadot-api/wasm-executor': 0.2.3
@@ -5088,7 +5091,7 @@ snapshots:
'@polkadot-api/observable-client': 0.19.1(rxjs@7.8.2)
'@polkadot-api/sm-provider': 0.3.8(@polkadot-api/smoldot@0.4.7)
'@polkadot-api/smoldot': 0.4.7
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/utils': 0.4.0
'@polkadot-api/wasm-executor': 0.2.3
@@ -5117,7 +5120,7 @@ snapshots:
'@polkadot-api/ink-contracts': 0.7.0
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/metadata-compatibility': 0.7.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/codegen@0.23.1':
@@ -5125,7 +5128,7 @@ snapshots:
'@polkadot-api/ink-contracts': 0.7.1
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/metadata-compatibility': 0.7.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/descriptors@file:.papi/descriptors(polkadot-api@3.1.0(esbuild@0.28.1)(rxjs@7.8.2))':
@@ -5137,7 +5140,7 @@ snapshots:
'@acala-network/chopsticks-executor': 1.5.1
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/observable-client': 0.19.0(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/tx-utils': 0.6.0(rxjs@7.8.2)
'@polkadot-api/ws-middleware': 0.4.0(rxjs@7.8.2)
@@ -5164,13 +5167,13 @@ snapshots:
'@polkadot-api/ink-contracts@0.7.0':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/ink-contracts@0.7.1':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/json-rpc-provider-proxy@0.1.0':
@@ -5192,7 +5195,7 @@ snapshots:
'@ledgerhq/hw-transport': 6.35.5
'@polkadot-api/merkleize-metadata': 1.3.0
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
transitivePeerDependencies:
@@ -5205,32 +5208,32 @@ snapshots:
'@polkadot-api/merkleize-metadata@1.3.0':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/merkleize-metadata@1.3.1':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/meta-signers@0.3.0':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
'@polkadot-api/metadata-builders@0.15.0':
dependencies:
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/metadata-builders@0.15.1':
dependencies:
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@polkadot-api/metadata-builders@0.3.2':
@@ -5242,17 +5245,17 @@ snapshots:
'@polkadot-api/metadata-compatibility@0.7.0':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/metadata-compatibility@0.7.1':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/observable-client@0.19.0(rxjs@7.8.2)':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5260,7 +5263,7 @@ snapshots:
'@polkadot-api/observable-client@0.19.1(rxjs@7.8.2)':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5278,7 +5281,7 @@ snapshots:
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
transitivePeerDependencies:
@@ -5288,7 +5291,7 @@ snapshots:
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
transitivePeerDependencies:
@@ -5304,7 +5307,7 @@ snapshots:
'@polkadot-api/merkleize-metadata': 1.3.0
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5315,7 +5318,7 @@ snapshots:
'@polkadot-api/merkleize-metadata': 1.3.1
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5323,7 +5326,7 @@ snapshots:
'@polkadot-api/react-builder@0.6.1(react@19.2.8)':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
react: 19.2.8
@@ -5339,7 +5342,7 @@ snapshots:
'@polkadot-api/signers-common@0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)':
dependencies:
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5347,7 +5350,7 @@ snapshots:
'@polkadot-api/signers-common@0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)':
dependencies:
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5373,14 +5376,14 @@ snapshots:
'@scure/base': 2.4.0
scale-ts: 1.6.1
'@polkadot-api/substrate-bindings@0.21.0':
'@polkadot-api/substrate-bindings@0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)':
dependencies:
'@noble/hashes': 2.4.0
'@polkadot-api/utils': 0.4.0
'@scure/base': 2.4.0
scale-ts: 1.6.1
'@polkadot-api/substrate-bindings@0.21.1':
'@polkadot-api/substrate-bindings@0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)':
dependencies:
'@noble/hashes': 2.4.0
'@polkadot-api/utils': 0.4.0
@@ -5416,7 +5419,7 @@ snapshots:
'@polkadot-api/merkleize-metadata': 1.3.0
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
transitivePeerDependencies:
- rxjs
@@ -5426,7 +5429,7 @@ snapshots:
'@polkadot-api/merkleize-metadata': 1.3.1
'@polkadot-api/metadata-builders': 0.15.1
'@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
transitivePeerDependencies:
- rxjs
@@ -5445,7 +5448,7 @@ snapshots:
'@polkadot-api/json-rpc-provider': 0.2.0
'@polkadot-api/json-rpc-provider-proxy': 0.4.1
'@polkadot-api/raw-client': 0.3.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5454,7 +5457,7 @@ snapshots:
'@polkadot-api/json-rpc-provider': 0.2.0
'@polkadot-api/json-rpc-provider-proxy': 0.4.1
'@polkadot-api/raw-client': 0.3.0
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
rxjs: 7.8.2
@@ -5718,7 +5721,7 @@ snapshots:
dependencies:
'@polkadot-api/meta-signers': 0.3.0
'@polkadot-api/metadata-builders': 0.15.0
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/proxy': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5739,7 +5742,7 @@ snapshots:
- supports-color
- utf-8-validate
'@polkahub/pjs-wallet@0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
'@polkahub/pjs-wallet@0.9.1(patch_hash=f359348c980dcf8a9f7077a067ffbc79e23c166172a4fa1be36de5340072104d)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@polkadot-api/pjs-signer': 0.8.0(rxjs@7.8.2)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5800,7 +5803,7 @@ snapshots:
'@polkahub/read-only@0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/select-account': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5823,7 +5826,7 @@ snapshots:
'@polkahub/select-account@0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@polkadot-api/react-components': 0.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/ui-components': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5860,7 +5863,7 @@ snapshots:
'@polkahub/ui-components@0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
dependencies:
'@polkadot-api/react-components': 0.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/utils': 0.4.0
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5894,7 +5897,7 @@ snapshots:
dependencies:
'@polkadot-api/merkleize-metadata': 1.3.1
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/select-account': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -5916,7 +5919,7 @@ snapshots:
'@polkahub/wallet-connect@0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(debug@4.4.3)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)(use-sync-external-store@1.6.0(react@19.2.8))(zod@3.25.76)':
dependencies:
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/select-account': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -9238,7 +9241,7 @@ snapshots:
'@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/sm-provider': 0.3.8(@polkadot-api/smoldot@0.4.7)
'@polkadot-api/smoldot': 0.4.7
'@polkadot-api/substrate-bindings': 0.21.0
'@polkadot-api/substrate-bindings': 0.21.0(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
@@ -9267,7 +9270,7 @@ snapshots:
'@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2)
'@polkadot-api/sm-provider': 0.3.8(@polkadot-api/smoldot@0.4.7)
'@polkadot-api/smoldot': 0.4.7
'@polkadot-api/substrate-bindings': 0.21.1
'@polkadot-api/substrate-bindings': 0.21.1(patch_hash=44a839dff72a3f1f9334c05ea6c72f29757e5d7168e8fee564f12de216710537)
'@polkadot-api/substrate-client': 0.7.0
'@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2)
'@polkadot-api/utils': 0.4.0
@@ -9286,7 +9289,7 @@ snapshots:
'@polkahub/context': 0.9.1(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/ledger': 0.9.1(@ledgerhq/hw-transport@6.35.5)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/multisig': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/pjs-wallet': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/pjs-wallet': 0.9.1(patch_hash=f359348c980dcf8a9f7077a067ffbc79e23c166172a4fa1be36de5340072104d)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/plugin': 0.9.1(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(esbuild@0.28.1)(react@19.2.8)
'@polkahub/proxy': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
'@polkahub/read-only': 0.9.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)

View File

@@ -10,4 +10,13 @@ minimumReleaseAgeExclude:
- '@paraspell/sdk@14.3.0 || 14.3.4'
patchedDependencies:
'@polkadot-api/signers-common': patches/@polkadot-api__signers-common.patch
# Pinned per version, unlike the signers-common patch which is keyed on the
# bare name. Four copies of substrate-bindings resolve in this tree and their
# blockHeader.js is NOT identical across majors — 0.19.0 and 0.6.0 differ, and
# a bare-name key fails the whole install trying to patch them. 0.21.1 is what
# the console and observable-client actually import; 0.21.0 is byte-identical
# and patched too so a minor bump does not silently drop the fix.
'@polkadot-api/substrate-bindings@0.21.0': patches/@polkadot-api__substrate-bindings@0.21.0.patch
'@polkadot-api/substrate-bindings@0.21.1': patches/@polkadot-api__substrate-bindings@0.21.1.patch
'@polkahub/pjs-wallet@0.9.1': patches/@polkahub__pjs-wallet@0.9.1.patch
react18-json-view: patches/react18-json-view.patch

View File

@@ -1,4 +1,3 @@
import { VaultTxModal } from "polkahub"
import { lazy } from "react"
import { Navigate, Route, Routes } from "react-router-dom"
import { ConnectionState } from "./components/ConnectionState"
@@ -35,7 +34,6 @@ export default function App() {
<Route path="*" element={<Navigate to="/explorer" replace />} />
</Routes>
</AppShell>
<VaultTxModal />
<ConnectionState />
</div>
)

View File

@@ -0,0 +1,81 @@
import { FC, useEffect, useState } from "react"
/**
* What the page shows while the chain is being reached.
*
* `main.tsx` wraps the entire app in `<Subscribe source$={dynamicBuilder$ …}>`,
* and `Subscribe`'s fallback defaults to `null` — "it will render null until the
* subscription exists". `dynamicBuilder$` does not emit until a chain has been
* followed and its metadata decoded, so until then the document body is empty
* and a visitor sees a bare black page with nothing but the tab spinner to
* suggest it is not simply broken.
*
* That is indistinguishable from a crash, and on a chain the console cannot read
* it never resolves at all. So: say what is happening, and say it louder the
* longer it takes.
*/
const SLOW_MS = 8_000
const STUCK_MS = 25_000
export const BootSplash: FC<{ endpoint?: string; network?: string }> = ({
endpoint,
network,
}) => {
const [elapsed, setElapsed] = useState(0)
useEffect(() => {
const started = Date.now()
const id = setInterval(() => setElapsed(Date.now() - started), 500)
return () => clearInterval(id)
}, [])
return (
<div className="flex h-screen w-full flex-col items-center justify-center gap-4 bg-background px-6 text-center">
<div className="text-base">
<span className="poppins-regular">qapi</span>{" "}
<span className="poppins-extralight">console</span>
</div>
<div
className="h-6 w-6 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent"
role="status"
aria-label="connecting"
/>
<div className="text-sm text-muted-foreground">
Connecting to {network ?? "the chain"}
{endpoint ? (
<>
{" "}
at <code className="break-all">{endpoint}</code>
</>
) : null}
</div>
{elapsed > SLOW_MS && elapsed <= STUCK_MS ? (
<div className="max-w-md text-sm text-muted-foreground">
Still going. The console downloads the chain's metadata and decodes it
before it can render anything, which takes a moment on a first visit.
</div>
) : null}
{elapsed > STUCK_MS ? (
<div className="max-w-md space-y-2 text-sm">
<p className="text-destructive">
This is taking much longer than it should.
</p>
<p className="text-muted-foreground">
Either the node is unreachable, or the console cannot read this
chain. Check the browser console for the reason, and try another
network the switcher is in the header once any chain loads, and{" "}
<code>#networkId=polkadot&amp;endpoint=wss://rpc.polkadot.io</code>{" "}
in the URL will force one.
</p>
</div>
) : null}
</div>
)
}

View File

@@ -1,6 +1,6 @@
import { dynamicBuilder$ } from "@/state/chains/chain.state"
import { dynamicBuilder$, getDefaultChain } from "@/state/chains/chain.state"
import { RemoveSubscribe, Subscribe } from "@react-rxjs/core"
import { MultisigExternalSignerModal, PolkaHubProvider } from "polkahub"
import { PolkaHubProvider } from "polkahub"
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
@@ -8,6 +8,7 @@ import { merge } from "rxjs"
import App from "./App.tsx"
import { TooltipProvider } from "./components/ui/tooltip.tsx"
import "./index.css"
import { BootSplash } from "./components/BootSplash.tsx"
import { codeSplit$ } from "./lib/externalState.ts"
import { explorer$ } from "./pages/Explorer"
import { polkaHub } from "./state/polkahub.ts"
@@ -17,8 +18,22 @@ const metrics$ = codeSplit$(() =>
import("./pages/Metrics/metrics.state.ts").then((r) => r.blockStats$),
)
// Without a fallback, `Subscribe` renders null until `dynamicBuilder$` emits —
// which is after a chain has been followed and its metadata decoded. On a slow
// connection that is seconds of empty black page; on a chain the console cannot
// read, it is forever, and indistinguishable from a crash.
const initial = getDefaultChain()
createRoot(document.getElementById("root")!).render(
<Subscribe source$={merge(dynamicBuilder$, explorer$, metrics$)}>
<Subscribe
fallback={
<BootSplash
endpoint={initial.endpoint}
network={initial.network.display}
/>
}
source$={merge(dynamicBuilder$, explorer$, metrics$)}
>
<RemoveSubscribe>
<StrictMode>
<ThemeProvider>
@@ -28,7 +43,6 @@ createRoot(document.getElementById("root")!).render(
<App />
</TooltipProvider>
</BrowserRouter>
<MultisigExternalSignerModal />
</PolkaHubProvider>
</ThemeProvider>
</StrictMode>

View File

@@ -16,12 +16,9 @@ import { CompatibilityLevel, SS58String } from "polkadot-api"
import { toHex } from "polkadot-api/utils"
import {
Account,
ledgerProviderId,
PjsWalletAccount,
polkadotVaultProviderId,
readOnlyProviderId,
useAvailableAccounts,
walletConnectProviderId,
} from "polkahub"
import { FC } from "react"
import {
@@ -34,15 +31,11 @@ import {
const knownGroupsNames: Record<string, string> = {
"pjs-wallet": "Browser Extensions",
[ledgerProviderId]: "Ledger",
[readOnlyProviderId]: "Read Only",
[polkadotVaultProviderId]: "Polkadot Vault",
[walletConnectProviderId]: "Wallet Connect",
}
const knownGroupsColors: Record<string, string> = {
"pjs-wallet": "var(--color-lime-700)",
[readOnlyProviderId]: "var(--color-orange-700)",
[walletConnectProviderId]: "var(--color-blue-700)",
}
export const AccountList = () => {
@@ -95,10 +88,7 @@ const AccountCard: FC<{
)
}
export const knownExtensions: Record<string, string> = {
"polkadot-js": "Polkadot JS",
"nova-wallet": "Nova Wallet",
talisman: "Talisman",
"subwallet-js": "Subwallet",
blackbeard: "blackbeard",
}
const getExtensionName = (id: string) => knownExtensions[id] ?? id
const getGroupName = (id: string) => knownGroupsNames[id] ?? id

View File

@@ -8,15 +8,11 @@ import {
} from "@polkahub/ui-components"
import { ChevronLeft } from "lucide-react"
import {
ManageLedger,
ManageMultisig,
ManageProxy,
ManageReadOnly,
ManageVault,
ModalContext,
PjsWalletButtons,
usePolkaHubModalState,
WalletConnectButton,
} from "polkahub"
import { FC, PropsWithChildren } from "react"
@@ -71,15 +67,9 @@ const ManageToDialog: FC<PropsWithChildren> = ({ children }) => {
export const PolkahubModalBasedManagers = () => (
<>
<ul className="flex gap-2 flex-wrap items-center justify-center">
<ManageLedger />
<ManageVault />
<WalletConnectButton />
</ul>
<ul className="flex gap-2 flex-wrap items-center justify-center">
<ManageReadOnly />
<ManageProxy />
<ManageMultisig />
</ul>
</>
)

View File

@@ -195,7 +195,7 @@ const SidebarContent: FC<{ mobile?: boolean; onNavigate?: () => void }> = ({
<div className="shrink-0 border-t p-3">
<ThemeToggle />
<a
href="https://git.lair.cafe/quantus/papi-console"
href="https://git.lair.cafe/blackbeard/qapi"
target="_blank"
rel="noreferrer"
className="mt-2 flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"

View File

@@ -8,10 +8,8 @@ import {
import { FC } from "react"
const groupLabels: Record<string, string> = {
ledger: "Ledger",
"pjs-wallet": "Browser Extensions",
readonly: "Read Only",
"polkadot-vault": "Vault",
walletconnect: "Wallet Connect",
}
export const SelectAccount: FC<{

View File

@@ -1,7 +1,5 @@
import { CopyText } from "@/components/Copy"
import {
Chopsticks,
Forklift,
ForkMethodIcon,
Spinner,
} from "@/components/Icons"
@@ -28,7 +26,6 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import {
AUTO_RPC_ENDPOINT,
currentWsStatus$,
ForkMethod,
LIGHT_CLIENT_ENDPOINT,
Network,
networkCategories,
@@ -85,10 +82,21 @@ const NetworkSwitchDialogContent: FC<{
const [query, setQuery] = useState("")
const [network, setNetwork] = useState(selectedChain.network)
const [endpoint, setEndpoint] = useState(selectedChain.endpoint)
const [forkMethod, setForkMethod] = useState(selectedChain.forkMethod)
const customUrl = normalizeWsUrl(query)
const canFork = endpoint !== LIGHT_CLIENT_ENDPOINT
const selectedForkMethod = canFork ? forkMethod : "none"
// Forking is not offered.
//
// Chopsticks is built on `@polkadot/types`, which cannot decode a Quantus
// block at all: it caps fixed arrays at 2048 bytes where ML-DSA signatures are
// 5261 and 7219, reads the extrinsic preamble byte as a version, and knows
// nothing of this chain's header layout. Forklift resolves its own unpatched
// copy of `@polkadot-api/substrate-bindings`, so it hits the very header bug
// this console patches around.
//
// Neither can fork a chain in this list, so the two buttons offered things
// that could only fail, and fail without saying why. `forkMethod` stays in
// SelectedChain — the hash params and chain.state still read it — but nothing
// in the UI can now set it to anything other than "none".
const selectedForkMethod = "none" as const
const hasChanged =
network.id !== selectedChain.network.id ||
endpoint !== selectedChain.endpoint ||
@@ -100,9 +108,6 @@ const NetworkSwitchDialogContent: FC<{
) => {
setNetwork(next)
setEndpoint(nextEndpoint)
if (nextEndpoint === LIGHT_CLIENT_ENDPOINT) {
setForkMethod("none")
}
}
const selectCustomUrl = (url: string) => {
@@ -112,14 +117,8 @@ const NetworkSwitchDialogContent: FC<{
const setConnection = (nextEndpoint: string) => {
setEndpoint(nextEndpoint)
if (nextEndpoint === LIGHT_CLIENT_ENDPOINT) {
setForkMethod("none")
}
}
const toggleForkMethod = (method: ForkMethod) => {
setForkMethod((current) => (current === method ? "none" : method))
}
const confirm = () => {
if (network.id === "custom") {
@@ -185,32 +184,7 @@ const NetworkSwitchDialogContent: FC<{
/>
</div>
<div className="flex shrink-0 items-center justify-between gap-3 border-t pt-3">
{canFork ? (
<div className="flex min-w-0 items-center gap-3">
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Fork with
</div>
<div className="flex flex-wrap gap-2">
<ForkMethodOption
value="chopsticks"
title="Chopsticks"
selected={selectedForkMethod === "chopsticks"}
icon={<Chopsticks size={18} />}
onSelect={toggleForkMethod}
/>
<ForkMethodOption
value="forklift"
title="Forklift"
selected={selectedForkMethod === "forklift"}
icon={<Forklift size={18} />}
onSelect={toggleForkMethod}
/>
</div>
</div>
) : (
<div />
)}
<div className="flex shrink-0 items-center justify-end gap-3 border-t pt-3">
<Button
className="min-w-28"
onClick={confirm}
@@ -227,26 +201,6 @@ const NetworkSwitchDialogContent: FC<{
)
}
const ForkMethodOption: FC<{
value: Exclude<ForkMethod, "none">
title: string
selected: boolean
icon?: React.ReactNode
onSelect: (value: ForkMethod) => void
}> = ({ value, title, selected, icon, onSelect }) => (
<button
type="button"
className={twMerge(
"flex items-center gap-1.5 rounded-md border border-transparent px-2 py-1.5 text-sm",
selected && "border-polkadot bg-polkadot/5",
)}
aria-pressed={selected}
onClick={() => onSelect(value)}
>
{icon}
{title}
</button>
)
const NetworkList: FC<{
query: string

View File

@@ -145,10 +145,17 @@ export const isValidUri = (input: string): boolean => {
const defaultSelectedChain: SelectedChain = {
network: defaultNetwork,
endpoint: LIGHT_CLIENT_ENDPOINT,
// AUTO_RPC, not LIGHT_CLIENT. A light client needs a chain spec, and the
// Quantus networks have none (`hasChainSpecs: false`), so defaulting to
// smoldot asks it to follow a chain it has never heard of — which fails
// before a single byte is fetched, and with `Subscribe`'s null fallback it
// failed as a bare black page.
endpoint: defaultNetwork.lightclient
? LIGHT_CLIENT_ENDPOINT
: AUTO_RPC_ENDPOINT,
forkMethod: "none",
}
const getDefaultChain = (): SelectedChain => {
export const getDefaultChain = (): SelectedChain => {
const hashParams = getHashParams()
if (hashParams.has("networkId") && hashParams.has("endpoint")) {
const networkId = hashParams.get("networkId")!

View File

@@ -1,8 +1,4 @@
import ksmRawNetworks from "./kusama.json"
import paseoRawNetworks from "./paseo.json"
import polkadotRawNetworks from "./polkadot.json"
import quantusRawNetworks from "./quantus.json"
import westendRawNetworks from "./westend.json"
export type Network = {
id: string
@@ -17,38 +13,45 @@ export type NetworkCategory = {
networks: Network[]
}
const [Polkadot, Kusama, Paseo, Westend, Quantus] = (
[
polkadotRawNetworks,
ksmRawNetworks,
paseoRawNetworks,
westendRawNetworks,
quantusRawNetworks,
] as const
).map((n): Network[] =>
n.map((x) => ({
endpoints: x.rpcs as any,
lightclient: !!x.hasChainSpecs,
id: x.id,
display: x.display,
// Quantus is a solo chain and its entries carry no `relayChainInfo`, so the
// union of the imported JSON literals no longer has the property on every
// arm. Narrowed rather than cast: a typo in one of the relay-chain files
// should still be a type error.
relayChain: "relayChainInfo" in x ? x.relayChainInfo?.id : undefined,
})),
)
// Quantus only.
//
// Upstream ships Polkadot, Kusama, Paseo, Westend and their parachains. Every
// one of them signs with sr25519, ed25519 or ecdsa — discrete-log schemes that
// Shor's algorithm breaks together — so none has a quantum-safe account type to
// offer, and this console cannot sign for them anyway: polkadot-api rejected
// Quantus's DilithiumSignatureScheme until the patch in patches/, and that patch
// widened a check rather than adding support for anything else.
//
// Offering them would make the console look like a general-purpose Substrate
// tool that happens to include Quantus. It is the other way round.
//
// The patched header codec still carries both the Substrate and the Quantus
// layouts, and still has a test for each: Localhost and Custom below can point
// at anything, so the disambiguation has to keep working even with no Substrate
// chain in this list.
// Typed once here rather than per-field. TypeScript infers a union of object
// literals from the JSON, and because the endpoint keys differ per chain
// (rpc1/rpc2 vs a1/a2) every key is `?: undefined` on the arms that lack it —
// which is not assignable to Record<string, string>. The double assertion is
// the narrowest way through and is what upstream did too (`x.rpcs as any`);
// the shape is checked by the build the moment quantus.json drifts from it.
type RawNetwork = {
id: string
display: string
rpcs: Record<string, string>
hasChainSpecs: boolean
}
const Quantus: Network[] = (quantusRawNetworks as unknown as RawNetwork[]).map((x) => ({
endpoints: x.rpcs,
lightclient: !!x.hasChainSpecs,
id: x.id,
display: x.display,
relayChain: undefined,
}))
// Quantus first, and the default below. This console exists to exercise a
// post-quantum chain; the Substrate networks stay because a signature that works
// on both is the only evidence that the patch in patches/ widened a check rather
// than broke one.
const networks = {
Quantus,
Polkadot,
Kusama,
Paseo,
Westend,
Localhost: [
{
id: "localhost",

View File

@@ -3,12 +3,10 @@
"id": "quantus",
"display": "Quantus",
"rpcs": {
"Quantus": "wss://rpc1-mainnet.quantus.com"
},
"nativeToken": {
"symbol": "QTC",
"decimals": 12
"rpc1": "wss://rpc1-mainnet.quantus.com",
"rpc2": "wss://rpc2-mainnet.quantus.com"
},
"nativeToken": { "symbol": "QTC", "decimals": 12 },
"hasChainSpecs": false
},
{
@@ -18,10 +16,7 @@
"a1": "wss://a1-heisenberg.quantus.cat",
"a2": "wss://a2-heisenberg.quantus.cat"
},
"nativeToken": {
"symbol": "HEI",
"decimals": 12
},
"nativeToken": { "symbol": "HEI", "decimals": 12 },
"hasChainSpecs": false
},
{
@@ -31,10 +26,7 @@
"a1": "wss://a1-planck.quantus.cat",
"a2": "wss://a2-planck.quantus.cat"
},
"nativeToken": {
"symbol": "PLK",
"decimals": 12
},
"nativeToken": { "symbol": "PLK", "decimals": 12 },
"hasChainSpecs": false
}
]

View File

@@ -1,20 +1,13 @@
import { polkadot_people } from "@polkadot-api/descriptors"
import { liftSuspense, state, SUSPENSE, withDefault } from "@react-rxjs/core"
import { AccountId, Binary, HexString, SS58String } from "polkadot-api"
import { WsEvent } from "polkadot-api/ws"
import {
Account,
createLedgerProvider,
createMultisigProvider,
createPjsWalletProvider,
createPolkadotVaultProvider,
createPolkaHub,
createProxyProvider,
createReadOnlyProvider,
createSelectedAccountPlugin,
createWalletConnectProvider,
knownChains,
multisigExternalSigner,
Plugin,
} from "polkahub"
import {
@@ -23,20 +16,15 @@ import {
firstValueFrom,
map,
pipe,
startWith,
switchMap,
} from "rxjs"
import { chainProperties$ } from "./chain-props.state"
import {
canSetStorage$,
client$,
currentWsStatus$,
getChainSource,
selectedChain$,
unsafeApi$,
} from "./chains/chain.state"
import { identity$, isVerified } from "./identity.state"
import { WebsocketSource } from "./chains/websocket"
const removeSuspense = <T>() =>
pipe(
@@ -44,23 +32,8 @@ const removeSuspense = <T>() =>
filter<T | SUSPENSE, T>((v): v is T => v !== SUSPENSE),
)
const getNetworkInfo = () =>
firstValueFrom(
chainProperties$.pipe(
removeSuspense(),
filter((v) => v != null),
map(({ tokenDecimals, tokenSymbol }) => ({
decimals: tokenDecimals!,
tokenSymbol: tokenSymbol!,
})),
),
)
const selectedAccountPlugin = createSelectedAccountPlugin()
const pjsWalletProvider = createPjsWalletProvider()
const polkadotVaultProvider = createPolkadotVaultProvider({
getNetworkInfo,
})
const readOnlyProvider$ = canSetStorage$.pipe(
liftSuspense(),
filter((v) => v !== SUSPENSE),
@@ -70,21 +43,6 @@ const readOnlyProvider$ = canSetStorage$.pipe(
}),
),
)
const ledgerAccountProvider = createLedgerProvider(async () => {
const module = await import("@ledgerhq/hw-transport-webusb")
return module.default.create()
}, getNetworkInfo)
const walletConnectProvider = createWalletConnectProvider(
import.meta.env.VITE_REOWN_PROJECT_ID,
[
knownChains.polkadot,
knownChains.polkadotAh,
knownChains.kusama,
knownChains.kusamaAh,
knownChains.paseo,
knownChains.paseoAh,
],
)
const proxyProvider = createProxyProvider((address) =>
firstValueFrom(
unsafeApi$.pipe(
@@ -95,61 +53,18 @@ const proxyProvider = createProxyProvider((address) =>
),
),
)
const multisigProvider$ = selectedChain$.pipe(
switchMap((chain) =>
combineLatest([getChainSource(chain), currentWsStatus$]),
),
map(([source, wsStatus]) => {
if (source.type === "websocket" && source.forkMethod !== "none") {
return null
}
if (
source.type === "chainSpec" &&
![
"polkadot",
"westend",
"kusama",
"paseo",
"polkadot_asset_hub",
"westend_asset_hub",
"kusama_asset_hub",
"paseo_asset_hub",
].includes(source.id)
) {
return null
}
const getWsUri = (source: WebsocketSource) => {
if (wsStatus?.type === WsEvent.CONNECTED) return wsStatus.uri
return typeof source.endpoint === "string"
? source.endpoint
: source.endpoint[0]
}
const chain =
source.type === "chainSpec" ? `sm-${source.id}` : `ws-${getWsUri(source)}`
return createMultisigProvider(
multisigExternalSigner(
(info, callData) =>
`https://multisig.usepapi.app/?chain=${chain}&calldata=${callData}&signatories=${info.signatories.join("_")}&threshold=${info.threshold}`,
),
)
}),
startWith(null),
)
// Signers that cannot sign for Quantus are not offered. Polkadot Vault, Ledger
// and WalletConnect produce sr25519/ed25519/ecdsa signatures, and the external
// multisig signer hands signing to multisig.usepapi.app; the runtime accepts
// only ML-DSA. What remains: browser extensions (blackbeard, the only one that
// signs ML-DSA), read-only accounts, and proxies, which sign through whichever
// of those they wrap.
export const polkaHub = createPolkaHub(
combineLatest([
[selectedAccountPlugin],
[pjsWalletProvider],
[polkadotVaultProvider],
readOnlyProvider$,
[ledgerAccountProvider],
[walletConnectProvider],
[proxyProvider],
multisigProvider$,
]).pipe(map((v: (Plugin<any> | null)[]) => v.filter((v) => v != null))),
{
getIdentity: (address) =>