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
oolon is a shared edge proxy. blackbeard.observer and others already grant
gitea_ci `/usr/bin/rsync` into their own webroots, so checking for the bare
command path passed on somebody else's grant — and the deploy would then fail at
the rsync, halfway, having already been told everything was fine.
The grants are destination-qualified in script/infra-setup.sh for exactly this
reason; the check now matches them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
The console is now **qapi console**, served at https://qapi.blackbeard.observer.
The papi logo and the upstream licence stay: this is a fork of
polkadot-api/papi-console, not a rewrite, and the sidebar link points at our
fork rather than theirs.
Deployment follows architecture/deployment-gitea-actions.md — the workflow is
the source of infra truth, and one-time host provisioning is an operator script:
script/infra-setup.sh dns -> the Cloudflare CNAME to bl.thgttg.com
cert -> Let's Encrypt, DNS-01, ECDSA
edge -> gitea_ci, scoped sudoers, webroot, vhosts
.gitea/workflows/deploy.yaml build (pnpm) -> rsync the bundle -> reload
The roles run in that order because certbot uses a DNS-01 challenge and nginx
fails its config test on a missing ssl_certificate — which would block every
reload on a SHARED proxy, not just this vhost. The same reasoning bounds what CI
may do: the sudoers drop-in grants rsync into one webroot, restorecon on it,
a config test and a reload. No certbot, no /etc/letsencrypt, no sites-available
write, no useradd.
Two vhosts, per architecture/reverse-proxies.md: the public one on the https
tier at 127.0.0.1:14443 behind the stream SNI router that owns TCP 443 (binding
443 directly is undetectable by `nginx -t` and ends with nginx silently serving
stale certificates), and a qapi.internal one for mesh clients, which stays
disabled until someone mints its internal certificate.
There is no application host and no upstream: the console is a static SPA that
opens WebSockets straight from the browser to the chains' own RPC endpoints.
The build gate is `pnpm build` (tsc -b + vite build) plus a check that the
signers-common patch is applied to the copy node actually resolves. Without that
patch every signing attempt dies with `Unkown signer` in a browser, after
deploy, in front of a user — and a lockfile drift is all it would take.
`pnpm lint` is deliberately not in the gate: it is broken upstream and was
before this fork touched anything (typescript-eslint 8.69 refuses to load
against the TypeScript 7.0 this repo resolves).
Live and verified: the public name answers 200 with this console's title over
the WAN address, serving the Let's Encrypt certificate for it, and the whole
deploy path — rsync as gitea_ci through the scoped sudoers, restorecon, config
test, reload, fetch — has been run by hand end to end.
Refs #3
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
## 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
* feat: fork with forklift
* fix: missing entry input in storage set
* fix: use best number to produce new blocks
* fix empty block table on chain stop
* fix: make network selector highlight the selected chain
* fix: handle case where forklift has all storage diff values
* refactor: break down SubmitTx into separate files
* add validate transaction, start dry run
* add dry run
* fix worker getting stuck after forklift is disposed
* handle rejections in promiseWithTeardown
* fix: show error on invalid transactions
* fix: leave nonce unset if not changed by the user
Co-authored-by: Carlo Sala <carlosalag@protonmail.com>
* avoid spamming nonce requests
---------
Co-authored-by: Carlo Sala <carlosalag@protonmail.com>
* playground
* playground tx and weights
* vibe UI
* chart improvements
* wip fix ai slop
* wip fix ai slop
* fix ai slop on charts
* subscribe to main state
* show blocks in chronological order, fix performance issue on startup
* update node health
* add connected peers chart
* fix unused locals
* integrate teleport with paraspell
* add some style
* finish up styles
* format token decimals, token input component
* fill in genesis to paraspell map
* add link to paraspell, fix invalid address on destination chain check
* codesplit teleport page
* add button to setup wallet
* chore: update dependencies
* add hash params state
* remove console logs, remove support for negative values in token input
* relayout extrinsics
* feat: extrinsics submit panel
* restyle submit, scroll to submit on small screens
* disable sign&submit buttons when no signer is selected