Amends DESIGN-integrated-wallet.md §0 ("one account per profile in v1") with
a formal path to supporting external key imports (BIP39 seeds and raw WIFs)
alongside the primary HD purpose-subtree. Motivated by the user's 15-wallet
Deviant chipnet keystore now encrypted under the same master-password model
that Theseus is designed around.
Key discipline:
- SEPARATE storage file (wallet-imports.enc), same PBKDF2/AES-256-GCM crypto,
same master password, distinct KDF salt. Imports cannot compromise the
primary seed and vice versa; corruption of one file does not damage the
other.
- The imports schema mirrors Deviant's chipnet-keystore/1 shape (cashaddr /
label / category / source / path / seed / wif) so import is a 1:1 field
copy, not a translation.
- Every import stores the raw seed + BIP44 path (or the raw WIF) and derives
addresses DIRECTLY via HDKey.fromMasterSeed, bypassing the vault's
purposes/wallet HKDF subtree. This is essential: without the bypass,
pasting a mnemonic re-hashes the seed through HKDF and produces DIFFERENT
addresses than the source wallet (see memory
bchwallet-vault-root-derivation).
- No bcnr.importWallet() ever; imports are settings-page only.
- Sign modal grows an account picker with category chips; per-origin
"recently used" pre-selection with red-flag on category change.
Also introduces:
- bcnr.requestAccount({ account?, category? }) — backwards compatible
- bcnr.getAccounts({ category? })
- Stale-import detection (source-file cashaddr comparison on unlock)
- Sequencing: M.1 (generic import) → M.2 (import from Deviant keystore) →
M.3 (API extensions) → M.4 (stale detection). M.1 is load-bearing;
everything else composes.
Companion to Option A which shipped separately as the cross-repo fall-through
in Argus/src/lib/wallet.js.
436 lines
21 KiB
Markdown
436 lines
21 KiB
Markdown
# Theseus wallet — multi-account amendment
|
||
|
||
Amendment to [`DESIGN-integrated-wallet.md`](DESIGN-integrated-wallet.md) §0
|
||
("Multi-account UX. One account per Theseus profile in v1."). Proposes lifting
|
||
that restriction under a specific discipline that keeps the single-seed HKDF
|
||
purpose-subtree model intact.
|
||
|
||
Status: **draft** — not accepted, not scheduled. Written 2026-09-09 as the
|
||
formal path to "Theseus recognises the master password and shows all of my
|
||
existing wallets, including externally-generated ones", raised while migrating
|
||
the Deviant `Keys/chipnet-keystore.json` to master-password encryption.
|
||
|
||
Companion documents:
|
||
- [`DESIGN-integrated-wallet.md`](DESIGN-integrated-wallet.md) — the v1 design this amends
|
||
- [`DESIGN-password-manager.md`](DESIGN-password-manager.md) — the crypto discipline (PBKDF2, HKDF, purpose subtrees)
|
||
- [`../Argus/src/lib/password-vault.js`](../Argus/src/lib/password-vault.js) — the implemented vault primitives
|
||
- [`../Argus/src/lib/wallet.js`](../Argus/src/lib/wallet.js) — the just-landed cross-repo loader (Option A) that resolves `loadWallet('sirius.x')` from either SilentMode's own `wallets.json` or Deviant's encrypted `Keys/chipnet-keystore.json`
|
||
|
||
---
|
||
|
||
## 0. Why amend now
|
||
|
||
Three facts changed since §0 was written:
|
||
|
||
1. **The user has 15 chipnet wallets already** — 5 HD seeds plus 10 single-key
|
||
WIFs — spread across Deviant (`Keys/chipnet-keystore.json`, encrypted with
|
||
PBKDF2/AES-256-GCM and the master password) and SilentMode
|
||
(`Argus/wallets.json`, plaintext). Consolidating them under Theseus is the
|
||
natural next step now that both projects use the same master password.
|
||
2. **`password-vault.js` already ships** the primitives that make this cheap:
|
||
`unlockVault(path, pw)` returns a state object; `seedToPurposeRoot(seed,
|
||
purpose)` derives an HKDF subtree deterministically; `bytesToHex/hexToBytes`
|
||
are the wire format. There is no new crypto to design — only new state to
|
||
serialise.
|
||
3. **The Deviant wallet dApp (`shells/app/`) already has multi-account UX**,
|
||
including `MultiKeyStore.WalletSummary { id, label, network, cashaddr, kind:
|
||
'independent' | 'hd-account' }`. The alternative to amending Theseus is to
|
||
host that dApp inside Theseus. This document argues the amendment is
|
||
*additionally* worth doing, not a replacement for that path.
|
||
|
||
The core question §0 punted on was not "is multi-account complicated?" — it was
|
||
"does supporting external key material contaminate the single-seed model?"
|
||
This document answers: **no, if we keep external material in a distinct storage
|
||
lane with a separate origin tier in every UI surface.**
|
||
|
||
---
|
||
|
||
## 1. What "multi-account" means here — three distinct notions
|
||
|
||
We need to keep these apart because they have different threat models:
|
||
|
||
| Kind | Storage | Origin | Recovery | Example |
|
||
|---|---|---|---|---|
|
||
| **Primary account (unchanged)** | HKDF subtree under `silentmode/wallet/0/<index>` | Derived from the profile's single seed | Recover the seed → recover every primary account | The wallet a fresh Theseus profile creates for you |
|
||
| **Primary sub-accounts (new)** | HKDF subtree at index N under the same purpose root | Same seed | Same seed | "Trading", "Savings", "GMX game bets" — different indexes off the same profile seed |
|
||
| **External accounts (new)** | Separately encrypted blob keyed by the same master password | Independent (imported seed OR raw WIF) | Recover the *external material* — the profile seed does NOT recover them | Deviant's `sirius.x`, `bns-beacon`, the a-g throughput test WIFs |
|
||
|
||
The confusion §0 avoided was mixing categories 1 and 3 in one bag. The
|
||
amendment keeps them **in separate files** on disk. The user sees one unified
|
||
UI, the crypto keeps them completely isolated.
|
||
|
||
---
|
||
|
||
## 2. Threat-model deltas from §1 of the base doc
|
||
|
||
### 2.1 New surface: importing external key material
|
||
|
||
Every import path is a potential exfiltration event. Three attack shapes:
|
||
|
||
- **Shoulder-surf on paste** — someone reads the seed / WIF as it's typed or
|
||
pasted. Mitigation: never render the imported string; the modal shows only a
|
||
masked derived cashaddr after the import completes ("Import a wallet ending
|
||
in …abc123?"). No "show me the WIF I just imported" affordance.
|
||
- **Import from clipboard by malicious page** — a page holding
|
||
`navigator.clipboard.readText()` permission could feed material INTO the
|
||
import flow. Mitigation: **import is a settings-page action only**, never
|
||
reachable from `window.bcnr` or WizardConnect. There is no
|
||
`bcnr.importWallet()` method. Ever.
|
||
- **Import from disk by malicious extension** — Theseus itself can't fully
|
||
defend against this (§1.4 base doc), but the imported material lives in a
|
||
separately-encrypted file so a compromised in-memory purpose-root leak does
|
||
not leak the imports.
|
||
|
||
### 2.2 New surface: rendering many wallets in one UI
|
||
|
||
The user's clicks now cost more, because a wrong-account signature is
|
||
irrecoverable. Mitigations:
|
||
|
||
- **Explicit signer selection per sign-request.** The sign modal shows
|
||
`Signing as: [Sirius.x · BNS name buyer ▾]` with the account picker OPEN by
|
||
default (not collapsed). If a page requested a specific account via
|
||
`bcnr.requestAccount({ account: 'sirius.x' })`, the modal still shows every
|
||
account the user could have chosen — the requested one is pre-selected and
|
||
the picker collapsed, but expanding it is one click.
|
||
- **Category badges everywhere the account appears.** The wallet metadata
|
||
already carries a `category` field (`bns`, `bns-infra`, `chipnet-test`,
|
||
`operational`, …). The modal chip is coloured by category so `sirius.x`
|
||
(BNS) is visually distinct from `gmx` (operational) even if the user only
|
||
glanced at the modal.
|
||
- **"Recently used" is per-origin.** `deviant.bch` remembers which account you
|
||
last signed with — but it does not silently reuse it; it pre-selects it in
|
||
the modal, and a category change ("last time you used a `bns` account here,
|
||
now the page wants an `operational` one") gets a red warning banner.
|
||
|
||
### 2.3 What we still do NOT defend against (unchanged from base doc §1.4)
|
||
|
||
Imports of a seed the user already leaked, RCE on Theseus itself, keylogger.
|
||
These remain out-of-scope; the amendment does not widen the scope, only the
|
||
attack surface within it.
|
||
|
||
---
|
||
|
||
## 3. Storage model
|
||
|
||
### 3.1 The primary vault stays as designed
|
||
|
||
`<userData>/wallet.enc` (from `DESIGN-integrated-wallet.md` §6) holds the
|
||
profile's single seed. Every primary sub-account is derived on demand via
|
||
`seedToPurposeRoot(seed, "wallet/0")` → BIP32-style index. Nothing about the
|
||
existing design changes.
|
||
|
||
### 3.2 New file: `<userData>/wallet-imports.enc`
|
||
|
||
Same crypto envelope as `wallet.enc` (PBKDF2-SHA256 → AES-256-GCM), same
|
||
master password, separate KDF salt and IV. This is the ONLY place external key
|
||
material lives. Format after decryption:
|
||
|
||
```json
|
||
{
|
||
"version": "silentmode/wallet-imports/1",
|
||
"generated": "2026-09-09T12:00:00Z",
|
||
"accounts": {
|
||
"sirius-x": {
|
||
"kind": "seed",
|
||
"seed": "…",
|
||
"path": "m/44'/145'/0'/0/0",
|
||
"cashaddr": "bchtest:qzy92wznza359wzqz5r732wylfyrgl4djsl2agdp0p",
|
||
"label": "Sirius.x · BNS name buyer",
|
||
"category": "bns",
|
||
"source": "imported-from-D:/Dev/Deviant/Keys/chipnet-keystore.json#sirius-x",
|
||
"createdAt": 1725840000000
|
||
},
|
||
"gmx": {
|
||
"kind": "wif",
|
||
"wif": "…",
|
||
"cashaddr": "bchtest:qz040z8hza8n96llrk5kewp5dmyr5rd7csj8rasu9d",
|
||
"label": "GMX platform operator",
|
||
"category": "operational",
|
||
"source": "imported-from-D:/Dev/Deviant/Keys/chipnet-keystore.json#gmx",
|
||
"createdAt": 1725840000001
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
The `accounts` shape deliberately mirrors the fields already used by Deviant's
|
||
`chipnet-keystore/1` format (`cashaddr`, `label`, `category`, `source`, `path`,
|
||
`seed`, `wif`) so a Deviant → Theseus import is a one-to-one field mapping,
|
||
not a schema translation.
|
||
|
||
**Why a separate file, not one file with two sections?** Blast radius. A bug
|
||
that corrupts `wallet-imports.enc` cannot destroy the primary seed. A revoke
|
||
"remove all imports" clears one file — the primary vault is untouched. This
|
||
is the same discipline `DESIGN-password-manager.md` uses to keep the password
|
||
vault distinct from the seed vault: distinct files, same master password.
|
||
|
||
### 3.3 One-way pointer: primary → imports, never the other way
|
||
|
||
The primary vault gets a new small field:
|
||
|
||
```json
|
||
{ …existing wallet.enc payload…, "importsExist": true }
|
||
```
|
||
|
||
...updated on every import/revoke. Loading the primary vault tells Theseus
|
||
whether it should also try to decrypt `wallet-imports.enc`. The **imports
|
||
file NEVER references the primary** — an attacker who exfiltrates
|
||
`wallet-imports.enc` learns nothing about the profile seed, not even its
|
||
existence.
|
||
|
||
### 3.4 What the master password unlocks in one session
|
||
|
||
On `unlockVault(masterPw)`:
|
||
|
||
1. Derive `vaultKey` (PBKDF2 with `wallet.enc`'s salt).
|
||
2. Decrypt `wallet.enc` → get profile seed → derive `purposes/wallet/0`,
|
||
`purposes/passwords/0`, `purposes/messenger/0`.
|
||
3. If `importsExist`: derive `vaultKey'` (PBKDF2 with
|
||
`wallet-imports.enc`'s DIFFERENT salt — deliberately not the same key
|
||
material) and decrypt.
|
||
|
||
Both keys are held in RAM for the session; both are wiped on auto-lock. A
|
||
malicious page that gets an in-memory read of the wallet purpose root learns
|
||
nothing about imports.
|
||
|
||
---
|
||
|
||
## 4. UX
|
||
|
||
### 4.1 Settings > Wallet — the accounts panel
|
||
|
||
Replaces the base doc's "Account row: address, balance, backup phrase".
|
||
|
||
```
|
||
── Accounts ─────────────────────────────────────────────────────
|
||
[+ Create sub-account] [+ Import external wallet]
|
||
|
||
● Primary [rename ▎revoke]
|
||
bchtest:qxxx…yyy · category "primary"
|
||
|
||
● Trading (primary sub-account #1) [rename ▎revoke]
|
||
bchtest:qaaa…bbb · category "primary"
|
||
|
||
── Imported ─────────────────────────────────────────────────
|
||
● Sirius.x · BNS name buyer [rename ▎revoke]
|
||
bchtest:qzy…dp0p · category "bns"
|
||
source: Deviant/Keys/chipnet-keystore.json#sirius-x
|
||
|
||
● BNS TLD Beacon (.asm/.x) [rename ▎revoke]
|
||
bchtest:qzc…pmerh · category "bns-infra"
|
||
source: Deviant/Keys/chipnet-keystore.json#bns-tld-beacon
|
||
|
||
● GMX platform operator [rename ▎revoke]
|
||
bchtest:qz0…rasu9d · category "operational"
|
||
source: Deviant/Keys/chipnet-keystore.json#gmx
|
||
─────────────────────────────────────────────────────────────────
|
||
```
|
||
|
||
- Primary and sub-accounts always appear above imports. This is not just
|
||
cosmetic — it reflects that the profile seed is the wallet's identity.
|
||
- Imports are grouped by `category` when there are more than 5. `bns`,
|
||
`bns-infra`, `operational`, `chipnet-test` collapse into collapsible groups.
|
||
- Revoke removes an import (from `wallet-imports.enc`); it does NOT touch the
|
||
on-chain material and warns clearly: "The on-chain wallet still exists. Its
|
||
seed/WIF is only removed from this Theseus profile."
|
||
- **No affordance to view seed / WIF material after import.** Recovery lives
|
||
in the SOURCE (Deviant's keystore, or wherever the user got it from). §2.1
|
||
shoulder-surf mitigation.
|
||
|
||
### 4.2 Sign-request modal — the account picker
|
||
|
||
Same modal as base doc §5, extended:
|
||
|
||
- Account row becomes `Signing as: [dropdown ▾]`.
|
||
- Dropdown lists every unlocked account, grouped: Primary → sub-accounts → imports.
|
||
- Each row shows label + truncated cashaddr + category chip.
|
||
- Origin's "recently used" account is pre-selected (§2.2 above).
|
||
- If the calling page passed `bcnr.requestAccount({ account: 'sirius.x' })`
|
||
and the account exists, it's pre-selected. If it doesn't exist, the modal
|
||
says "This site is asking for an account you don't have. Sign as:" and the
|
||
user picks from what they do have.
|
||
|
||
### 4.3 Import flow
|
||
|
||
Two entry points, both in Settings > Wallet:
|
||
|
||
1. **"Import external wallet" → paste seed OR WIF.**
|
||
The pasted string never renders back; the modal shows only what will be
|
||
stored (`kind`, derived `cashaddr` truncated, `label` input, `category`
|
||
picker).
|
||
|
||
2. **"Import from Deviant keystore" (if `D:/Dev/Deviant/Keys/chipnet-keystore.json`
|
||
is reachable).** Detects the file, prompts for its master password (which
|
||
must equal Theseus's — refuses otherwise with a clear message: "The Deviant
|
||
keystore uses a different master password. Change one of them first."),
|
||
decrypts, shows a preview list of every entry (label + cashaddr + category
|
||
only), user picks which to import. Bulk-select shortcuts by category
|
||
("all `bns`", "all `operational`").
|
||
|
||
The "different master password" rejection is a deliberate ergonomic
|
||
constraint: we choose the pain of "unify your passwords once" over the pain
|
||
of "which password unlocks what in this profile".
|
||
|
||
---
|
||
|
||
## 5. API surface for pages (deltas from §3 of base doc)
|
||
|
||
Backwards-compatible additions only:
|
||
|
||
```ts
|
||
bcnr.requestAccount({ account?: string, category?: string }): Promise<string | null>
|
||
// If neither `account` nor `category` given: current behaviour (returns cashaddr
|
||
// of the default account, or null if not granted).
|
||
// If `account`: requests a specific labelled account. Modal pre-selects it; if
|
||
// it doesn't exist, the modal falls back to a picker over what the user has.
|
||
// If `category`: requests any account in that category (e.g. "bns"). Useful
|
||
// for the SilentMode registrar UI which knows it wants a BNS-holding wallet
|
||
// but not which one.
|
||
|
||
bcnr.getAccounts({ category?: string }): Promise<Array<{
|
||
cashaddr: string;
|
||
label: string;
|
||
category: string;
|
||
}>>
|
||
// Returns granted accounts. Optionally filtered by category. Note: the user
|
||
// grants per-account, not per-category — this method never leaks accounts the
|
||
// origin was not granted access to.
|
||
```
|
||
|
||
The `bcnr.signMessage` / `bcnr.sendPayment` / `bcnr.registerName` signatures
|
||
gain an optional `account?: string` parameter that pre-selects an account in
|
||
the modal. Non-passing preserves current behaviour.
|
||
|
||
**Not added:** no method to import, revoke, or rename. All state-modifying
|
||
account operations are settings-page only (§2.1).
|
||
|
||
---
|
||
|
||
## 6. Compatibility with Option A (the cross-repo loader that shipped 2026-09-09)
|
||
|
||
Option A lets `Argus/src/lib/wallet.js:loadWallet('sirius.x')` transparently
|
||
read Deviant's encrypted keystore. That path is authoritative today and stays
|
||
authoritative after this amendment ships:
|
||
|
||
- Theseus imports are **copies**, not sources of truth. Revoking an import in
|
||
Theseus does not affect Deviant's keystore or SilentMode's `wallets.json`.
|
||
- If the user rotates a key in Deviant's keystore, the corresponding Theseus
|
||
import goes stale. Theseus should detect this by comparing derived
|
||
`cashaddr` on unlock — a mismatch surfaces a banner: "The Deviant source of
|
||
Sirius.x has changed since this import. [Re-import] or [Remove]."
|
||
- The two systems remain independent by design. This is not a bug; it is the
|
||
same discipline that keeps password-vault seeds separate from wallet seeds
|
||
(§3.4 base doc).
|
||
|
||
---
|
||
|
||
## 7. Sequencing — where this slots into the base roadmap
|
||
|
||
Adds to the base doc's §7:
|
||
|
||
| Phase | What ships | Depends on |
|
||
|---|---|---|
|
||
| B.3d | Sub-account creation (primary purpose subtree, indexes 1..N) — `Trading` / `Savings` labels | B.3a shipped |
|
||
| **M.1** | Import external seed / WIF, `wallet-imports.enc` file, account picker in sign modal | B.3b + `password-vault.js` shipped ✓ |
|
||
| **M.2** | Import from Deviant keystore (file detection + bulk-select UX) | M.1 + `chipnet-keystore/2-encrypted` format stable ✓ |
|
||
| **M.3** | `bcnr.requestAccount({ account, category })` + `getAccounts({ category })` API extensions | M.1 |
|
||
| **M.4** | Stale-import detection (source-file cashaddr comparison) | M.2 |
|
||
|
||
M.1 is the load-bearing phase — everything else composes on top of the
|
||
`wallet-imports.enc` file. M.2 through M.4 are refinements the user's specific
|
||
setup makes worthwhile.
|
||
|
||
Do NOT ship M.2 (Deviant integration) before M.1 (generic import). The
|
||
generic-first path keeps the design honest — if the amendment cannot be used
|
||
by a user who never touches Deviant, we've over-fitted.
|
||
|
||
---
|
||
|
||
## 8. Open questions — decide before writing M.1 code
|
||
|
||
1. **What's the primary account's "category"?** Options: `primary` (mirrors
|
||
the model), leave the field null, or use the user-picked purpose ("Personal
|
||
spending"). *Recommend `primary` — it's the one category name that never
|
||
collides with an import.*
|
||
|
||
2. **How does WizardConnect surface multi-account?** External pairings via
|
||
`wiz://` — the `@wizardconnect/wallet@0.2.2` SDK's WalletAdapter has one
|
||
`getXpub(path)` and one `signTransaction(request)`. Neither takes an
|
||
account parameter. Options: pair-per-account (each account has its own
|
||
`wiz://` URI); pair-per-profile with account selection at sign time (like
|
||
in-page dApps); or accept that WizardConnect only exposes the primary
|
||
account. *Recommend option 3 for M.1 (only-primary), revisit when a user
|
||
asks. Multi-account WizardConnect is a real cross-cutting design that
|
||
deserves its own doc.*
|
||
|
||
3. **Do we let the profile primary seed be *replaced* by an import?** The
|
||
proposal above says no — imports are always in the imports file, and the
|
||
primary purpose subtrees stay tied to the profile seed. A user who wants
|
||
"my Deviant seed as the profile seed" would create a fresh profile and
|
||
restore that seed at profile-creation. *Recommend keeping the restriction
|
||
— it makes the sub-account / import distinction fundamental rather than
|
||
optional.*
|
||
|
||
4. **Auto-lock behaviour with imports.** Auto-lock re-encrypts the primary
|
||
purpose root under a session key (§6 base doc). Do we do the same for the
|
||
imports? Or wipe the imports outright and force a re-decrypt on unlock?
|
||
*Recommend the same treatment as primary — session-key re-encryption. The
|
||
cost is symmetric.*
|
||
|
||
5. **Export.** Can the user export an imported wallet's key back out of
|
||
Theseus? *Recommend no by default (§4.1 no-view-material rule), but a
|
||
"Show recovery WIF for this import" behind master-password reprompt AND
|
||
"why is this dangerous" panel (like the base doc §8.6 for the profile
|
||
seed) is a reasonable escape hatch for users who need to move a wallet
|
||
between machines.*
|
||
|
||
6. **Sub-account discovery.** If a user restores their profile seed on a new
|
||
Theseus install, do we auto-scan chipnet for used sub-account addresses?
|
||
*Recommend a "Scan for used sub-accounts" button in settings, not
|
||
automatic. Scanning is a chain-load event; make the user ask for it.*
|
||
|
||
7. **Category taxonomy.** The Deviant keystore uses `bns`, `bns-infra`,
|
||
`chipnet-test`, `operational`, `primary`, `hd-general`. Should Theseus
|
||
enforce a fixed set or allow free-form strings? *Recommend free-form
|
||
strings with the six above as autocomplete suggestions. Constraining now
|
||
would force a v2 migration when a user coins a new category.*
|
||
|
||
---
|
||
|
||
## 9. What this amendment does NOT decide
|
||
|
||
- The wallet-imports file format after v1. Version-tag it now; iterate as
|
||
needed. The `version: "silentmode/wallet-imports/1"` field exists precisely
|
||
so a v2 migration is a well-defined event, not a guess.
|
||
- The Deviant-side story. Deviant's `chipnet-keystore.json` continues to be
|
||
read by Deviant scripts (via `scripts/lib/chipnet-keystore.mjs`) and by
|
||
SilentMode's `Argus/src/lib/wallet.js` (via the Option A fall-through). This
|
||
amendment adds a THIRD reader (Theseus) — it does not consolidate the two
|
||
existing readers.
|
||
- The mobile story. Ariadne (mobile companion, base doc §9) is out of scope.
|
||
When it lands, the same `wallet-imports.enc` format ports over — that's the
|
||
point of designing it version-tagged and portable.
|
||
|
||
---
|
||
|
||
## 10. Suggested acceptance path
|
||
|
||
If the reader agrees with this amendment:
|
||
|
||
1. Merge M.1 into the roadmap between B.3b and B.4 in
|
||
`ROADMAP-identity-wallet.md`.
|
||
2. Move the base doc's §0 "Multi-account UX. One account per Theseus profile
|
||
in v1." to §9 "What this design does NOT decide, in v1 — this is now
|
||
addressed by [`DESIGN-wallet-multi-account-amendment.md`](DESIGN-wallet-multi-account-amendment.md)".
|
||
3. Address the open questions in §8 above in the roadmap discussion, not by
|
||
editing this file — the design is stable, only the answers are open.
|
||
|
||
If the reader disagrees, the fallback path is what the base doc already
|
||
implies: **host the Deviant wallet dApp inside Theseus, once `window.bcnr`
|
||
ships** (base doc §4.4 — "External dApp using WizardConnect = Nostr relay",
|
||
minus the Nostr relay because the dApp is loaded IN Theseus, not paired
|
||
externally). That path requires no Theseus code changes beyond what B.2–B.4
|
||
already deliver, and it inherits Deviant's own multi-account UX for free. The
|
||
trade-off is that "my wallet" and "the Deviant wallet loaded in my wallet
|
||
browser" become two things instead of one; this amendment is the case for
|
||
one.
|