# Tier 2 harness [quantus/extension#7](https://git.lair.cafe/quantus/extension/issues/7) tier 2: a dapp that asks the **extension** to sign, then assembles and submits the extrinsic itself. The point is not the UI. Every intermediate value is printed — the call, the `extra`, the payload that was signed, the signature that came back, the assembled extrinsic, and the round trip decoded from it. When a node rejects something, that display is the debugger, and it is exactly what a real app hides behind a success toast. Nothing here uses `@polkadot/api`. `@quantus/codec` produces every byte, from metadata the node generated by running `Metadata_metadata` against the runtime WASM; the WebSocket is a JSON-RPC transport and decodes nothing. See [quantus/api#1](https://git.lair.cafe/quantus/api/issues/1) for why. ## Run it ```sh yarn build # the extension, into packages/extension/build yarn tier2 # bundles scripts/tier2/src/harness.js python3 -m http.server 8731 ``` Then load `packages/extension/build` as an unpacked extension — `about:debugging#/runtime/this-firefox` → *Load Temporary Add-on*, or `chrome://extensions` with developer mode on → *Load unpacked* — and open . It has to be served over HTTP. The extension does not inject into `file://` pages, so `web3Enable` returns nothing there. ## A funded account The dev genesis endows three accounts from trivial seeds (`primitives/dilithium-crypto/src/pair.rs` in the chain): | account | seed | scheme | | --- | --- | --- | | `crystal_alice` | 32 × `0x00` | ML-DSA-87 | | `dilithium_bob` | 32 × `0x01` | ML-DSA-87 | | `crystal_charlie` | 32 × `0x02` | ML-DSA-87 | Import one with *Import account from pre-existing seed* and the raw hex seed `0x0000…00`. On Heisenberg `crystal_alice` is `qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7`. Two things that makes it worth using early: these are **ML-DSA-87**, the legacy scheme, where new accounts are ML-DSA-65; and it is the **non-HD** path, a raw 32-byte seed straight into keygen with no derivation, which is a different code path from the mnemonic one. ## What the buttons do - **connect** — fetches the runtime version, genesis hash and metadata, and lists the signed extensions the runtime declares. Two of them, `ReversibleTransactionExtension` and `WormholeProofRecorderExtension`, exist on no other chain. - **list accounts** — `web3Enable` then `web3Accounts`. The first call raises an authorization request in the extension. - **signRaw** — the bytes channel. Signs under the **empty** FIPS 204 context, so the result can never be replayed as an extrinsic. - **build, sign and submit** — provides this chain's metadata to the extension, builds the call, asks the extension to sign, assembles the extrinsic and submits it. That metadata step is not optional. The extension **refuses** to sign an extrinsic for a chain it has no metadata for, because without the runtime's own description of its signed extensions it would be guessing at the payload — and a signature over the wrong bytes comes back from a node as `BadProof`, which is also what it says about a wrong key. ## Tier 1 `../tier1/submit.mjs` does the same thing with no browser and no extension, signing with the forked keyring directly. It is the faster loop, and the one that answers "is this a crypto bug or a plumbing bug". ```sh node scripts/tier1/submit.mjs QUANTUS_WS=wss://… QUANTUS_DEST=qzk… node scripts/tier1/submit.mjs ```