Three options, and this issue should settle it rather than leave each developer to guess:
Git dependencies — simplest, no publishing step, but yarn resolution of a monorepo subpackage from a git URL is awkward and the forks are polkadot-dev workspaces that need building.
A private npm registry — Gitea has a package registry; publish @quantus/keyring etc. and depend on versions. Most conventional, needs auth in CI.
Yarn portal:/workspace links for local dev, published artifacts for CI — probably where this ends up, but say so explicitly.
Whatever is chosen, yarn build:chrome and yarn build:ff must work from a clean checkout, and the extension's own release build must not require a developer to have the fork repos checked out beside it.
Also
Both manifests already set script-src 'self' 'wasm-unsafe-eval', so the wasm from quantus/wasm#1 needs no manifest change — confirm this rather than assuming it, since the background context is an MV3 service worker, not a page.
Extension branding: name, description, icons, and the polkadot{.js} strings in manifest_chrome.json / manifest_firefox.json. Cosmetic, but it decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.
Keep a documented git remote for upstream polkadot-js/extension. The rebasability convention in #1 applies here too.
Part of #1. Nothing else in this repo can be tested until this lands.
## What
The root `package.json` already pins the whole `@polkadot` surface through `resolutions`:
```json
"resolutions": {
"@polkadot/api": "^16.5.6",
"@polkadot/keyring": "^14.0.3",
"@polkadot/networks": "^14.0.3",
"@polkadot/types": "^16.5.6",
"@polkadot/util": "^14.0.3",
"@polkadot/util-crypto": "^14.0.3",
…
}
```
Point the ones we fork at [quantus/common](../../../common), [quantus/ui](../../../ui), [quantus/wasm](../../../wasm) and — only if quantus/api#1 concludes it is needed — [quantus/api](../../../api). Everything else keeps coming from npm.
## Decide how the forks are consumed
Three options, and this issue should settle it rather than leave each developer to guess:
1. **Git dependencies** — simplest, no publishing step, but yarn resolution of a monorepo subpackage from a git URL is awkward and the forks are `polkadot-dev` workspaces that need building.
2. **A private npm registry** — Gitea has a package registry; publish `@quantus/keyring` etc. and depend on versions. Most conventional, needs auth in CI.
3. **Yarn `portal:`/workspace links for local dev, published artifacts for CI** — probably where this ends up, but say so explicitly.
Whatever is chosen, `yarn build:chrome` and `yarn build:ff` must work from a clean checkout, and the extension's own release build must not require a developer to have the fork repos checked out beside it.
## Also
- Both manifests already set `script-src 'self' 'wasm-unsafe-eval'`, so the wasm from quantus/wasm#1 needs no manifest change — confirm this rather than assuming it, since the background context is an MV3 **service worker**, not a page.
- Extension branding: name, description, icons, and the `polkadot{.js}` strings in `manifest_chrome.json` / `manifest_firefox.json`. Cosmetic, but it decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.
- Keep a documented `git remote` for upstream polkadot-js/extension. The rebasability convention in #1 applies here too.
The "how are the forks consumed" question is answered and in place. Option 2 — a private registry.
Published
Gitea's npm registry at https://git.lair.cafe/api/packages/quantus/npm/:
package
version
@quantus/crypto
0.1.0
@polkadot/keyring
14.0.3
@polkadot/util-crypto
14.0.3
@polkadot/networks
14.0.3
Reads are anonymous — verified — so a consumer needs no credential to install. Publishing uses pass gitea/package-publisher-token, which is now in the agent credential store with its blast radius documented.
@polkadot/util is deliberately not published: this fork does not modify it, so it comes from npm like the rest of the scope.
Two different mechanisms, for a reason
@quantus is routed as a whole scope in .yarnrc.yml:
@polkadot is not, and must not be. Most of that scope is unforked — util, dev, dev-ts, ui-settings, x-*, wasm-crypto and more — so routing it would send yarn looking for dozens of packages that are not there. The three forks are pinned individually to tarball URLs in resolutions:
Worth knowing when this repo is wired up: the extension will need the same split. It depends on far more of the @polkadot scope than ui does.
What came out
quantus/ui carried four workarounds, all for one cause — node resolves a portal or link symlink to its realpath, after which @polkadot/util-crypto resolved inside common's tree, where it is a workspace source directory with no exports map. All four are gone:
resolutions pointing at ../common/packages/*/build
tsconfig.base.json*.d.ts path mappings
tsconfig.json runtime .js overrides for @polkadot/dev-ts
--preserve-symlinks in the test scripts
tsconfig.base.json and tsconfig.json are now byte-identical to upstream again, which is the rebasability convention honoured rather than merely stated.
Verified
common: build clean, 3084 tests pass
ui: build clean, 46 tests pass including the 5 ML-DSA ones, and the installed @polkadot/util-crypto is confirmed to be the fork (has dilithium/, not a symlink)
ui lint: 0 errors, down from 25 on pristine upstream — those were no-unsafe-assignment/no-unsafe-argument on values that were any only because the types never resolved
One operational note
A version cannot be overwritten in place — npm refuses with "You cannot publish over the previously published versions". Republishing during development is delete-then-publish:
The token can do this (write:package covers delete, returns 204). Worth deciding before long whether fork builds get a distinguishing version — 14.0.3-quantus.1 or similar — rather than shadowing upstream's number. Publishing under upstream's exact version is unambiguous in a lockfile because the URL is recorded, but it reads confusingly in yarn why.
The "how are the forks consumed" question is answered and in place. **Option 2 — a private registry.**
## Published
Gitea's npm registry at `https://git.lair.cafe/api/packages/quantus/npm/`:
| package | version |
|---|---|
| `@quantus/crypto` | 0.1.0 |
| `@polkadot/keyring` | 14.0.3 |
| `@polkadot/util-crypto` | 14.0.3 |
| `@polkadot/networks` | 14.0.3 |
**Reads are anonymous** — verified — so a consumer needs no credential to install. Publishing uses `pass gitea/package-publisher-token`, which is now in the agent credential store with its blast radius documented.
`@polkadot/util` is deliberately **not** published: this fork does not modify it, so it comes from npm like the rest of the scope.
## Two different mechanisms, for a reason
**`@quantus` is routed as a whole scope** in `.yarnrc.yml`:
```yaml
npmScopes:
quantus:
npmRegistryServer: "https://git.lair.cafe/api/packages/quantus/npm/"
```
Safe, because we own every name in that scope.
**`@polkadot` is not, and must not be.** Most of that scope is unforked — `util`, `dev`, `dev-ts`, `ui-settings`, `x-*`, `wasm-crypto` and more — so routing it would send yarn looking for dozens of packages that are not there. The three forks are pinned individually to tarball URLs in `resolutions`:
```json
"@polkadot/keyring": "https://git.lair.cafe/api/packages/quantus/npm/%40polkadot%2Fkeyring/-/14.0.3/keyring-14.0.3.tgz"
```
Worth knowing when this repo is wired up: **the extension will need the same split.** It depends on far more of the `@polkadot` scope than `ui` does.
## What came out
`quantus/ui` carried four workarounds, all for one cause — node resolves a portal or link symlink to its realpath, after which `@polkadot/util-crypto` resolved inside *common's* tree, where it is a workspace source directory with no `exports` map. All four are gone:
- resolutions pointing at `../common/packages/*/build`
- `tsconfig.base.json` `*.d.ts` path mappings
- `tsconfig.json` runtime `.js` overrides for `@polkadot/dev-ts`
- `--preserve-symlinks` in the test scripts
`tsconfig.base.json` and `tsconfig.json` are now **byte-identical to upstream** again, which is the rebasability convention honoured rather than merely stated.
## Verified
- `common`: build clean, **3084 tests pass**
- `ui`: build clean, **46 tests pass** including the 5 ML-DSA ones, and the installed `@polkadot/util-crypto` is confirmed to be the fork (has `dilithium/`, not a symlink)
- `ui` lint: **0 errors, down from 25 on pristine upstream** — those were `no-unsafe-assignment`/`no-unsafe-argument` on values that were `any` only because the types never resolved
## One operational note
A version **cannot be overwritten in place** — npm refuses with *"You cannot publish over the previously published versions"*. Republishing during development is delete-then-publish:
```sh
curl -X DELETE -H "Authorization: token $T" \
"https://git.lair.cafe/api/v1/packages/quantus/npm/@polkadot%2Fkeyring/14.0.3"
```
The token can do this (`write:package` covers delete, returns `204`). Worth deciding before long whether fork builds get a distinguishing version — `14.0.3-quantus.1` or similar — rather than shadowing upstream's number. Publishing under upstream's exact version is unambiguous in a lockfile because the URL is recorded, but it reads confusingly in `yarn why`.
Done. Branch quantus-registry (bff8eec1). yarn build:chrome completes with post-quantum crypto in the bundle, and 65 tests pass.
Registry contents
package
version
@quantus/crypto
0.1.1
@polkadot/keyring
14.0.3-quantus.1
@polkadot/util-crypto
14.0.3-quantus.1
@polkadot/networks
14.0.3-quantus.1
@polkadot/ui-keyring
3.16.7-quantus.1
Everything else — api, types, rpc-provider, util, dev, x-*, hw-ledger — still comes from npm, which is why @polkadot is pinned per-package rather than scope-routed.
The bundler bug none of our tests could have caught
The first build:chrome failed:
Module not found: Error: Can't resolve 'quantus_crypto_bg.wasm'
in node_modules/@quantus/crypto/generated
wasm-bindgen's async __wbg_init contains new URL('quantus_crypto_bg.wasm', import.meta.url), and webpack resolves that statically at build time whether or not the branch can run. The file isn't in the package — the wasm ships base64'd in bytes.js, which is the package's whole purpose.
Ten Rust tests, twelve consumer assertions and a browser probe all passed while the package was unusable in a bundler. Fixed in quantus/wasm (e8bf9e20) by stripping the dead init after bindgen, with assertions so a wasm-bindgen upgrade fails the build rather than silently shipping it again.
The lesson worth keeping: this package's consumers bundle, and nothing in its own test suite does.
One upstream test rewritten
buildHierarchy.spec.ts failed on account ordering. compareByNetwork resolves a genesis hash through getNetworkMap() → selectableNetworks, which this fork narrowed to post-quantum chains — so Polkadot and Kusama both resolved to '' and compared equal. The network-ordering tests were passing without ordering anything.
Rewritten with Quantus and Heisenberg, whose display names sort in the same direction the originals did, so the fixtures keep their shape. (I got the direction wrong on the first attempt and had to redo it — 'Quantus' < 'Quantus Heisenberg' matches 'Kusama' < 'Polkadot', not the reverse.)
Verified in the artifacts, not assumed
background.js (1.1 MB) — QUANTUS_EXTRINSIC, the dilithium keypair types, ext_poseidon_hash, ext_mldsa_sign, and the base64 wasm payload
extension.js (3.5 MB) — the Quantus network entry, QTC, the mainnet genesis hash
The split is correct: signing lives in the service worker, the UI only renders addresses.
Still open on this issue
The branding item from the original body — name, description, icons, and the polkadot{.js} strings in both manifests. Untouched. It decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.
Also still true: no CI publishes these packages. Today it is a manual npm publish --tag quantus per package against pass gitea/package-publisher-token. Fine while one person is building; worth a Gitea Actions workflow before a second.
Done. Branch `quantus-registry` (`bff8eec1`). **`yarn build:chrome` completes with post-quantum crypto in the bundle**, and 65 tests pass.
## Registry contents
| package | version |
|---|---|
| `@quantus/crypto` | 0.1.1 |
| `@polkadot/keyring` | 14.0.3-quantus.1 |
| `@polkadot/util-crypto` | 14.0.3-quantus.1 |
| `@polkadot/networks` | 14.0.3-quantus.1 |
| `@polkadot/ui-keyring` | 3.16.7-quantus.1 |
Everything else — `api`, `types`, `rpc-provider`, `util`, `dev`, `x-*`, `hw-ledger` — still comes from npm, which is why `@polkadot` is pinned per-package rather than scope-routed.
## The bundler bug none of our tests could have caught
The first `build:chrome` failed:
```
Module not found: Error: Can't resolve 'quantus_crypto_bg.wasm'
in node_modules/@quantus/crypto/generated
```
wasm-bindgen's async `__wbg_init` contains `new URL('quantus_crypto_bg.wasm', import.meta.url)`, and **webpack resolves that statically at build time** whether or not the branch can run. The file isn't in the package — the wasm ships base64'd in `bytes.js`, which is the package's whole purpose.
Ten Rust tests, twelve consumer assertions and a browser probe all passed while the package was unusable in a bundler. Fixed in quantus/wasm (`e8bf9e20`) by stripping the dead init after bindgen, with assertions so a wasm-bindgen upgrade fails the build rather than silently shipping it again.
The lesson worth keeping: **this package's consumers bundle, and nothing in its own test suite does.**
## One upstream test rewritten
`buildHierarchy.spec.ts` failed on account ordering. `compareByNetwork` resolves a genesis hash through `getNetworkMap()` → `selectableNetworks`, which this fork narrowed to post-quantum chains — so Polkadot and Kusama both resolved to `''` and compared equal. The network-ordering tests were passing **without ordering anything**.
Rewritten with Quantus and Heisenberg, whose display names sort in the same direction the originals did, so the fixtures keep their shape. (I got the direction wrong on the first attempt and had to redo it — `'Quantus' < 'Quantus Heisenberg'` matches `'Kusama' < 'Polkadot'`, not the reverse.)
## Verified in the artifacts, not assumed
- `background.js` (1.1 MB) — `QUANTUS_EXTRINSIC`, the dilithium keypair types, `ext_poseidon_hash`, `ext_mldsa_sign`, and the base64 wasm payload
- `extension.js` (3.5 MB) — the Quantus network entry, `QTC`, the mainnet genesis hash
The split is correct: signing lives in the service worker, the UI only renders addresses.
## Still open on this issue
The **branding** item from the original body — name, description, icons, and the `polkadot{.js}` strings in both manifests. Untouched. It decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.
Also still true: no CI publishes these packages. Today it is a manual `npm publish --tag quantus` per package against `pass gitea/package-publisher-token`. Fine while one person is building; worth a Gitea Actions workflow before a second.
Forks from the registry:.yarnrc.yml routes the @quantus scope, and package.json pins keyring, networks, ui-keyring and util-crypto to the -quantus.2 tarballs (bff8eec1). Nothing points at local fork checkouts.
WASM under the extension's CSP: both manifests keep 'wasm-unsafe-eval'. @quantus/crypto runs in the Firefox background page and in the Chrome MV3 service worker, since wallets were created and used in both browsers.
Branding: done in #15 (e0aa7836); 851500a5 fixed the manifest author and homepage_url it left behind.
Upstream: the README's first paragraph links polkadot-js/extension as the upstream. package.json authorship stays upstream's, as credit for the code.
Verified against `main`, closing.
- **Forks from the registry:** `.yarnrc.yml` routes the `@quantus` scope, and `package.json` pins keyring, networks, ui-keyring and util-crypto to the `-quantus.2` tarballs (`bff8eec1`). Nothing points at local fork checkouts.
- **WASM under the extension's CSP:** both manifests keep `'wasm-unsafe-eval'`. `@quantus/crypto` runs in the Firefox background page and in the Chrome MV3 service worker, since wallets were created and used in both browsers.
- **Branding:** done in #15 (`e0aa7836`); `851500a5` fixed the manifest `author` and `homepage_url` it left behind.
- **Upstream:** the README's first paragraph links polkadot-js/extension as the upstream. `package.json` authorship stays upstream's, as credit for the code.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #1. Nothing else in this repo can be tested until this lands.
What
The root
package.jsonalready pins the whole@polkadotsurface throughresolutions:Point the ones we fork at quantus/common, quantus/ui, quantus/wasm and — only if quantus/api#1 concludes it is needed — quantus/api. Everything else keeps coming from npm.
Decide how the forks are consumed
Three options, and this issue should settle it rather than leave each developer to guess:
polkadot-devworkspaces that need building.@quantus/keyringetc. and depend on versions. Most conventional, needs auth in CI.portal:/workspace links for local dev, published artifacts for CI — probably where this ends up, but say so explicitly.Whatever is chosen,
yarn build:chromeandyarn build:ffmust work from a clean checkout, and the extension's own release build must not require a developer to have the fork repos checked out beside it.Also
script-src 'self' 'wasm-unsafe-eval', so the wasm from quantus/wasm#1 needs no manifest change — confirm this rather than assuming it, since the background context is an MV3 service worker, not a page.polkadot{.js}strings inmanifest_chrome.json/manifest_firefox.json. Cosmetic, but it decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.git remotefor upstream polkadot-js/extension. The rebasability convention in #1 applies here too.grenade referenced this issue2026-09-10 10:15:38 +00:00
The "how are the forks consumed" question is answered and in place. Option 2 — a private registry.
Published
Gitea's npm registry at
https://git.lair.cafe/api/packages/quantus/npm/:@quantus/crypto@polkadot/keyring@polkadot/util-crypto@polkadot/networksReads are anonymous — verified — so a consumer needs no credential to install. Publishing uses
pass gitea/package-publisher-token, which is now in the agent credential store with its blast radius documented.@polkadot/utilis deliberately not published: this fork does not modify it, so it comes from npm like the rest of the scope.Two different mechanisms, for a reason
@quantusis routed as a whole scope in.yarnrc.yml:Safe, because we own every name in that scope.
@polkadotis not, and must not be. Most of that scope is unforked —util,dev,dev-ts,ui-settings,x-*,wasm-cryptoand more — so routing it would send yarn looking for dozens of packages that are not there. The three forks are pinned individually to tarball URLs inresolutions:Worth knowing when this repo is wired up: the extension will need the same split. It depends on far more of the
@polkadotscope thanuidoes.What came out
quantus/uicarried four workarounds, all for one cause — node resolves a portal or link symlink to its realpath, after which@polkadot/util-cryptoresolved inside common's tree, where it is a workspace source directory with noexportsmap. All four are gone:../common/packages/*/buildtsconfig.base.json*.d.tspath mappingstsconfig.jsonruntime.jsoverrides for@polkadot/dev-ts--preserve-symlinksin the test scriptstsconfig.base.jsonandtsconfig.jsonare now byte-identical to upstream again, which is the rebasability convention honoured rather than merely stated.Verified
common: build clean, 3084 tests passui: build clean, 46 tests pass including the 5 ML-DSA ones, and the installed@polkadot/util-cryptois confirmed to be the fork (hasdilithium/, not a symlink)uilint: 0 errors, down from 25 on pristine upstream — those wereno-unsafe-assignment/no-unsafe-argumenton values that wereanyonly because the types never resolvedOne operational note
A version cannot be overwritten in place — npm refuses with "You cannot publish over the previously published versions". Republishing during development is delete-then-publish:
The token can do this (
write:packagecovers delete, returns204). Worth deciding before long whether fork builds get a distinguishing version —14.0.3-quantus.1or similar — rather than shadowing upstream's number. Publishing under upstream's exact version is unambiguous in a lockfile because the URL is recorded, but it reads confusingly inyarn why.Done. Branch
quantus-registry(bff8eec1).yarn build:chromecompletes with post-quantum crypto in the bundle, and 65 tests pass.Registry contents
@quantus/crypto@polkadot/keyring@polkadot/util-crypto@polkadot/networks@polkadot/ui-keyringEverything else —
api,types,rpc-provider,util,dev,x-*,hw-ledger— still comes from npm, which is why@polkadotis pinned per-package rather than scope-routed.The bundler bug none of our tests could have caught
The first
build:chromefailed:wasm-bindgen's async
__wbg_initcontainsnew URL('quantus_crypto_bg.wasm', import.meta.url), and webpack resolves that statically at build time whether or not the branch can run. The file isn't in the package — the wasm ships base64'd inbytes.js, which is the package's whole purpose.Ten Rust tests, twelve consumer assertions and a browser probe all passed while the package was unusable in a bundler. Fixed in quantus/wasm (
e8bf9e20) by stripping the dead init after bindgen, with assertions so a wasm-bindgen upgrade fails the build rather than silently shipping it again.The lesson worth keeping: this package's consumers bundle, and nothing in its own test suite does.
One upstream test rewritten
buildHierarchy.spec.tsfailed on account ordering.compareByNetworkresolves a genesis hash throughgetNetworkMap()→selectableNetworks, which this fork narrowed to post-quantum chains — so Polkadot and Kusama both resolved to''and compared equal. The network-ordering tests were passing without ordering anything.Rewritten with Quantus and Heisenberg, whose display names sort in the same direction the originals did, so the fixtures keep their shape. (I got the direction wrong on the first attempt and had to redo it —
'Quantus' < 'Quantus Heisenberg'matches'Kusama' < 'Polkadot', not the reverse.)Verified in the artifacts, not assumed
background.js(1.1 MB) —QUANTUS_EXTRINSIC, the dilithium keypair types,ext_poseidon_hash,ext_mldsa_sign, and the base64 wasm payloadextension.js(3.5 MB) — the Quantus network entry,QTC, the mainnet genesis hashThe split is correct: signing lives in the service worker, the UI only renders addresses.
Still open on this issue
The branding item from the original body — name, description, icons, and the
polkadot{.js}strings in both manifests. Untouched. It decides whether a user can tell this apart from the upstream extension when both are installed, which they will be.Also still true: no CI publishes these packages. Today it is a manual
npm publish --tag quantusper package againstpass gitea/package-publisher-token. Fine while one person is building; worth a Gitea Actions workflow before a second.Verified against
main, closing..yarnrc.ymlroutes the@quantusscope, andpackage.jsonpins keyring, networks, ui-keyring and util-crypto to the-quantus.2tarballs (bff8eec1). Nothing points at local fork checkouts.'wasm-unsafe-eval'.@quantus/cryptoruns in the Firefox background page and in the Chrome MV3 service worker, since wallets were created and used in both browsers.e0aa7836);851500a5fixed the manifestauthorandhomepage_urlit left behind.package.jsonauthorship stays upstream's, as credit for the code.