WizardConnect/packages/dapp/src/dapp-connection-manager.test.ts
Dagur Valberg Johannsson add06b6476
Add getSessionPaths() and restoreSessionPaths() to DappConnectionManager
Allows dapps to cache the raw PathXpub[] from wallet_ready and restore
them on subsequent page loads. restoreSessionPaths decodes the xpub strings
and populates pubkeyState so getPubkey() works without waiting for the
wallet to reconnect. Throws on invalid xpub data.
2026-03-23 08:26:12 +01:00

108 lines
3.7 KiB
TypeScript

// Copyright (C) 2026 Whiterun LLC,
// This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later.
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html
import { describe, it, expect } from "vitest";
import { encodeHdPublicKey } from "@bitauth/libauth";
import type { PathXpub } from "@wizardconnect/core";
import { DappConnectionManager } from "./dapp-connection-manager.js";
// Build a valid xpub string from a deterministic test node.
// Uses the secp256k1 generator point (a known valid public key).
function makeTestXpub(): string {
const node = {
chainCode: new Uint8Array(32).fill(0xbb),
// secp256k1 generator point (compressed) — always valid
publicKey: Uint8Array.from(
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
.match(/.{2}/g)!
.map((h) => parseInt(h, 16)),
),
childIndex: 0,
depth: 3,
parentFingerprint: new Uint8Array(4),
};
const result = encodeHdPublicKey({ node, network: "mainnet" });
if (typeof result === "string")
throw new Error("Failed to encode test xpub: " + result);
return result.hdPublicKey;
}
describe("DappConnectionManager", () => {
describe("getSessionPaths", () => {
it("returns empty array before wallet_ready", () => {
const mgr = new DappConnectionManager();
expect(mgr.getSessionPaths()).toEqual([]);
});
});
describe("restoreSessionPaths", () => {
it("restores xpubs so getPubkey works", () => {
const mgr = new DappConnectionManager();
const xpub = makeTestXpub();
const paths: PathXpub[] = [{ name: "receive", xpub }];
mgr.restoreSessionPaths(paths);
expect(mgr.hasPath(0)).toBe(true);
const pk = mgr.getPubkey(0, 0n);
expect(pk).toBeInstanceOf(Uint8Array);
expect(pk!.length).toBe(33);
});
it("makes paths available via getSessionPaths", () => {
const mgr = new DappConnectionManager();
const xpub = makeTestXpub();
const paths: PathXpub[] = [
{ name: "receive", xpub },
{ name: "change", xpub },
];
mgr.restoreSessionPaths(paths);
const stored = mgr.getSessionPaths();
expect(stored).toHaveLength(2);
expect(stored[0].name).toBe("receive");
expect(stored[1].name).toBe("change");
});
it("returns a copy, not a reference", () => {
const mgr = new DappConnectionManager();
const xpub = makeTestXpub();
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
const paths = mgr.getSessionPaths();
paths.push({ name: "change", xpub });
expect(mgr.getSessionPaths()).toHaveLength(1);
});
it("throws on invalid xpub strings", () => {
const mgr = new DappConnectionManager();
const paths: PathXpub[] = [{ name: "receive", xpub: "not-a-valid-xpub" }];
expect(() => mgr.restoreSessionPaths(paths)).toThrow("Invalid xpub");
});
it("can derive multiple child indices after restore", () => {
const mgr = new DappConnectionManager();
const xpub = makeTestXpub();
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
for (const idx of [0n, 1n, 5n, 10n]) {
const pk = mgr.getPubkey(0, idx);
expect(pk, `index ${idx}`).toBeInstanceOf(Uint8Array);
expect(pk!.length, `index ${idx}`).toBe(33);
}
});
it("does not affect paths not in the restore set", () => {
const mgr = new DappConnectionManager();
const xpub = makeTestXpub();
mgr.restoreSessionPaths([{ name: "receive", xpub }]);
expect(mgr.hasPath(0)).toBe(true); // receive = 0
expect(mgr.hasPath(1)).toBe(false); // change = 1
});
});
});