From 933ffecc8f2dc3c4b02348cada5906469ce73cf1 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Tue, 15 Sep 2026 22:16:40 +0300 Subject: [PATCH] feat: accept the Quantus signature type, and list Quantus chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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) Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f --- patches/@polkadot-api__signers-common.patch | 30 ++++++++++++++++ pnpm-lock.yaml | 33 ++++++++--------- pnpm-workspace.yaml | 1 + src/state/chains/networks/index.ts | 18 ++++++++-- src/state/chains/networks/quantus.json | 40 +++++++++++++++++++++ 5 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 patches/@polkadot-api__signers-common.patch create mode 100644 src/state/chains/networks/quantus.json diff --git a/patches/@polkadot-api__signers-common.patch b/patches/@polkadot-api__signers-common.patch new file mode 100644 index 0000000..71e9ac6 --- /dev/null +++ b/patches/@polkadot-api__signers-common.patch @@ -0,0 +1,30 @@ +diff --git a/dist/v4.js b/dist/v4.js +index e8ec50ec7fb5d3dab4f30cbc21fe36d0416f8a9d..cddc959ac2f0f396f17c18680d3d3fb0ca310f46 100644 +--- a/dist/v4.js ++++ b/dist/v4.js +@@ -21,8 +21,23 @@ const getSignerType = (metadata) => { + } + if (address.type === "AccountId20" && signature.type === "array" && signature.len === 65 && signature.value.type === "primitive" && signature.value.value === "u8") + return [1 /* Ethereum */, []]; +- if (signature.type !== "enum" || ["Ecdsa", "Ed25519", "Sr25519"].some((x) => !(x in signature.value))) +- throw unkownSignerType(); ++ // Quantus patch: the variant *names* are not load-bearing. ++ // ++ // Upstream required the signature enum to carry Ecdsa, Ed25519 and Sr25519. ++ // That whitelist exists only to tell the Substrate shape from the Ethereum ++ // one, and the Ethereum arm one line above already decides that structurally — ++ // AccountId20 plus a 65-byte array. Everything below this point is ++ // length-agnostic: createV4Tx concatenates whatever the signer returned and ++ // compact-prefixes the result. ++ // ++ // So this check fired before code that would have worked. Quantus's ++ // DilithiumSignatureScheme carries Dilithium87 and Dilithium65, and the ++ // metadata said so — getSignerType read the type out of the metadata and then ++ // threw the answer away in favour of three hardcoded names. Asking the runtime ++ // and then not believing it is the trap this whole port exists to avoid. ++ // ++ // See quantus/papi-console#1. ++ if (signature.type !== "enum") throw unkownSignerType(); + if (address.type === "enum") { + const id = address.value["Id"]; + if (id.type === "lookupEntry" && id.value.type === "AccountId32") diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5df8142..7693437 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false patchedDependencies: + '@polkadot-api/signers-common': 365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37 react18-json-view: 962696fac5fe7ad3e94fd36dad07521c6c3eafd52f219ab335d52438387f6b66 importers: @@ -46,7 +47,7 @@ importers: version: 0.4.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@polkadot-api/signers-common': specifier: ^0.3.1 - version: 0.3.1(rxjs@7.8.2) + version: 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': specifier: ^0.21.1 version: 0.21.1 @@ -5022,7 +5023,7 @@ snapshots: '@noble/hashes': 2.4.0 '@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(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.1 '@polkadot-labs/hdkd': 0.0.31 '@polkadot-labs/hdkd-helpers': 0.0.33 @@ -5190,7 +5191,7 @@ snapshots: dependencies: '@ledgerhq/hw-transport': 6.35.5 '@polkadot-api/merkleize-metadata': 1.3.0 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5216,7 +5217,7 @@ snapshots: '@polkadot-api/meta-signers@0.3.0': dependencies: '@polkadot-api/metadata-builders': 0.15.0 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5276,7 +5277,7 @@ snapshots: '@polkadot-api/pjs-signer@0.8.0(rxjs@7.8.2)': dependencies: '@polkadot-api/metadata-builders': 0.15.0 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5286,7 +5287,7 @@ snapshots: '@polkadot-api/pjs-signer@0.8.1(rxjs@7.8.2)': dependencies: '@polkadot-api/metadata-builders': 0.15.1 - '@polkadot-api/signers-common': 0.3.1(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.1 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5302,7 +5303,7 @@ snapshots: '@noble/hashes': 2.4.0 '@polkadot-api/merkleize-metadata': 1.3.0 '@polkadot-api/metadata-builders': 0.15.0 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5313,7 +5314,7 @@ snapshots: '@noble/hashes': 2.4.0 '@polkadot-api/merkleize-metadata': 1.3.1 '@polkadot-api/metadata-builders': 0.15.1 - '@polkadot-api/signers-common': 0.3.1(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.1 '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@polkadot-api/utils': 0.4.0 @@ -5335,7 +5336,7 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - '@polkadot-api/signers-common@0.3.0(rxjs@7.8.2)': + '@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 @@ -5343,7 +5344,7 @@ snapshots: '@polkadot-api/utils': 0.4.0 rxjs: 7.8.2 - '@polkadot-api/signers-common@0.3.1(rxjs@7.8.2)': + '@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 @@ -5414,7 +5415,7 @@ snapshots: dependencies: '@polkadot-api/merkleize-metadata': 1.3.0 '@polkadot-api/metadata-builders': 0.15.0 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@polkadot-api/utils': 0.4.0 transitivePeerDependencies: @@ -5424,7 +5425,7 @@ snapshots: dependencies: '@polkadot-api/merkleize-metadata': 1.3.1 '@polkadot-api/metadata-builders': 0.15.1 - '@polkadot-api/signers-common': 0.3.1(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.1(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.1 '@polkadot-api/utils': 0.4.0 transitivePeerDependencies: @@ -5761,7 +5762,7 @@ snapshots: '@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)': dependencies: - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/tx-creator': 0.2.0(rxjs@7.8.2) '@react-rxjs/utils': 0.9.7(@react-rxjs/core@0.10.8(react@19.2.8)(rxjs@7.8.2))(react@19.2.8)(rxjs@7.8.2) '@rx-state/core': 0.1.4(rxjs@7.8.2) @@ -5892,7 +5893,7 @@ snapshots: '@polkahub/vault@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/merkleize-metadata': 1.3.1 - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@polkadot-api/signers-common': 0.3.0(patch_hash=365a34617de2ae3ad2f7e009f7f3e20e0078448fbfc93c395d323f0e21b75d37)(rxjs@7.8.2) '@polkadot-api/substrate-bindings': 0.21.0 '@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) @@ -9234,7 +9235,7 @@ snapshots: '@polkadot-api/observable-client': 0.19.0(rxjs@7.8.2) '@polkadot-api/pjs-signer': 0.8.0(rxjs@7.8.2) '@polkadot-api/raw-tx-creator': 0.4.0(rxjs@7.8.2) - '@polkadot-api/signers-common': 0.3.0(rxjs@7.8.2) + '@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 @@ -9263,7 +9264,7 @@ snapshots: '@polkadot-api/observable-client': 0.19.1(rxjs@7.8.2) '@polkadot-api/pjs-signer': 0.8.1(rxjs@7.8.2) '@polkadot-api/raw-tx-creator': 0.4.1(rxjs@7.8.2) - '@polkadot-api/signers-common': 0.3.1(rxjs@7.8.2) + '@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 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 00dbec0..62c54c2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,4 +9,5 @@ minimumReleaseAgeExclude: - '@paraspell/sdk-core@14.3.0 || 14.3.4' - '@paraspell/sdk@14.3.0 || 14.3.4' patchedDependencies: + '@polkadot-api/signers-common': patches/@polkadot-api__signers-common.patch react18-json-view: patches/react18-json-view.patch diff --git a/src/state/chains/networks/index.ts b/src/state/chains/networks/index.ts index d543c39..830ae8c 100644 --- a/src/state/chains/networks/index.ts +++ b/src/state/chains/networks/index.ts @@ -1,6 +1,7 @@ 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 = { @@ -16,12 +17,13 @@ export type NetworkCategory = { networks: Network[] } -const [Polkadot, Kusama, Paseo, Westend] = ( +const [Polkadot, Kusama, Paseo, Westend, Quantus] = ( [ polkadotRawNetworks, ksmRawNetworks, paseoRawNetworks, westendRawNetworks, + quantusRawNetworks, ] as const ).map((n): Network[] => n.map((x) => ({ @@ -29,11 +31,20 @@ const [Polkadot, Kusama, Paseo, Westend] = ( lightclient: !!x.hasChainSpecs, id: x.id, display: x.display, - relayChain: x.relayChainInfo?.id, + // 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 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, @@ -69,4 +80,5 @@ export const addCustomNetwork = (uri: string) => { getCustomNetwork().endpoints[uri] = uri } -export const defaultNetwork = Polkadot[0] +// Mainnet. The console opens on the chain it was forked for. +export const defaultNetwork = Quantus[0] diff --git a/src/state/chains/networks/quantus.json b/src/state/chains/networks/quantus.json new file mode 100644 index 0000000..9284a4c --- /dev/null +++ b/src/state/chains/networks/quantus.json @@ -0,0 +1,40 @@ +[ + { + "id": "quantus", + "display": "Quantus", + "rpcs": { + "Quantus": "wss://rpc1-mainnet.quantus.com" + }, + "nativeToken": { + "symbol": "QTC", + "decimals": 12 + }, + "hasChainSpecs": false + }, + { + "id": "heisenberg", + "display": "Heisenberg", + "rpcs": { + "a1": "wss://a1-heisenberg.quantus.cat", + "a2": "wss://a2-heisenberg.quantus.cat" + }, + "nativeToken": { + "symbol": "HEI", + "decimals": 12 + }, + "hasChainSpecs": false + }, + { + "id": "planck", + "display": "Planck", + "rpcs": { + "a1": "wss://a1-planck.quantus.cat", + "a2": "wss://a2-planck.quantus.cat" + }, + "nativeToken": { + "symbol": "PLK", + "decimals": 12 + }, + "hasChainSpecs": false + } +]