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.
108 lines
3.7 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|