Web wallet · Self-custody

QSDM Web wallet

Create or import a self-custody CELL wallet in this browser. Keys are generated and encrypted locally — nothing private is uploaded. For the full desktop wallet, tasks, mining, and recovery, use QSDM Hive (recovery docs).

QSDM audit score (live)
Wallet crypto is audited. Five cryptography checklist rows pin ML-DSA-87 keygen, AES-GCM vault encryption, and JWT/mTLS posture. See audit.html?category=cryptography.

Read this before clicking anything

1) The QSDM wallet is the only thing standing between you and your coins. If you lose the JSON keystore file or forget the passphrase, the address is permanently unrecoverable — no support team, no reset email, no validator override. Back up the file. Pick a passphrase you will not forget.

2) Verify you are on qsdm.tech (look at the address bar) before generating. A phishing clone of this page could replace the WASM binary and exfiltrate the private key the moment it's generated. Even with HTTPS, treat the wallet page like a hardware-wallet recovery screen.

3) The same keystore format is produced by the offline CLI: qsdmcli wallet new. If you'd rather not trust a webpage, use the CLI on a machine you control — the keystore opens here either way.

Sky Fang players: link your game account through QSDM Hive, not this browser page. Open Sky Fang QSDM page

Advanced tools Optional: sign a message, inspect a keystore file, or use the legacy generate / balance / send tabs. Everyday create, unlock, and send live in the wallet panel above.

Generate a fresh wallet

Choose a strong passphrase (12+ characters, mix of types). The wallet page never sees the passphrase in network traffic: it is fed to PBKDF2-HMAC-SHA-256 (600 000 iterations) inside this browser tab, the derived key encrypts the private key under AES-256-GCM, and the resulting JSON keystore is yours to download.

Waiting for WASM module…

Open an existing keystore

Drop in a wallet.json file (produced here, or by qsdmcli wallet new). The browser will decrypt it locally to confirm the passphrase is correct and the file isn't tampered with. Nothing is uploaded.

Sign a message

Decrypt the keystore and produce a ML-DSA-87 signature over an arbitrary message. The decrypted private key is held only inside the WASM call; the browser zeros the JS reference as soon as the signature is returned.

Check an address balance

Unlike the other three tabs, this one talks to the network. It sends a single GET https://api.qsdm.tech/api/v1/wallet/balance?address=<addr> and renders the response. An address is public information — it's already on chain — so the request leaks no private material, but it does correlate this browser with this address at the validator's HTTP log layer. If you want to avoid that correlation, ask a friend's node, run your own validator, or skip this tab entirely.

Address goes in → balance comes back.

Send a transaction (self-custody)

This is the only tab that spends from your wallet. Decrypt your keystore with the passphrase, fill in the recipient and amount, click Send. The transaction is built and signed inside this browser tab with your ML-DSA-87 private key — the private key never leaves the tab — and only the already-signed envelope is POSTed to api.qsdm.tech/api/v1/wallet/submit-signed. The validator verifies the signature against the embedded public key and enforces sender == hex(sha256(public_key)), so the validator can never spend on your behalf.

v0.4.1 replay protection: every send carries a per-account nonce that the validator atomically bumps inside the same transaction as the balance debit. Replays (same envelope, same nonce, twice) return HTTP 409 nonce_replay rather than double-spending. The Nonce field below auto-resolves from the validator before sign-time; you can override it manually for off-line ceremonies.

Recipient + amount + passphrase → signed envelope → validator POST.
How this works & CLI
  • Keypair generation: a ~3 MB Go WebAssembly module (wallet.wasm) runs the same cloudflare/circl ML-DSA-87 implementation that QSDM validators use to verify your transactions. It calls the browser's crypto.getRandomValues for the keygen entropy.
  • Encryption: the browser's WebCrypto API (crypto.subtle) derives the AES-256 key with PBKDF2-SHA256 (600 000 iterations, 16-byte salt), then encrypts the private key under AES-256-GCM with a fresh 12-byte nonce. The on-disk format is identical to pkg/keystore (Go) so the CLI and browser can swap keystores freely.
  • Address derivation: the QSDM address is hex(sha256(public_key)). Same shape as the validator-side pkg/wallet derivation.
  • Bounded network surface: the Generate / Open / Sign tabs only fetch the three static files (wallet.wasm, wasm_exec.js, wallet.js). They never POST the passphrase, the private key, the public key, or even the address. The Check balance and Send transaction tabs are the two exceptions — Balance does a single read-only GET against api.qsdm.tech with the address in the query string, and Send does a single POST to /api/v1/wallet/submit-signed with the already-signed transaction envelope (the body contains your public key + signature + amount + recipient; never the private key or passphrase). Both happen only after you click their button. Confirm in DevTools → Network.
  • WASM build version: loading…

Or do it from the CLI

Identical keystore format, offline. Import into QSDM Hive for normal use. Advanced operators can use qsdmminer-console --protocol=v2 (see miner quickstart).

# Build once
git clone https://github.com/blackbeardONE/QSDM
cd QSDM/QSDM/source
go build -o qsdmcli ./cmd/qsdmcli

# Generate a keystore (passphrase prompted)
./qsdmcli wallet new --out ~/.qsdm/wallet.json
./qsdmcli wallet show