WizardConnect/packages/test-cli/src/wallet.ts

152 lines
4.5 KiB
TypeScript
Raw Normal View History

2026-02-26 11:19:47 +01:00
// 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 chalk from "chalk";
import ora from "ora";
import {
WalletConnectionManager,
type WalletAdapter,
DerivationPath,
} from "@wizardconnect/wallet";
import { generateKeyExchangeCredentials, hexToBin } from "@wizardconnect/core";
import {
deriveHdPrivateNodeFromSeed,
deriveHdPrivateNodeChild,
deriveHdPath,
deriveHdPublicNode,
encodeHdPublicKey,
secp256k1,
} from "@bitauth/libauth";
// ---- Build WalletAdapter ----
async function buildAdapter(
relayPrivateKey: Uint8Array,
): Promise<WalletAdapter> {
// Use relay private key as HD seed for deterministic derivation
const hdMaster = deriveHdPrivateNodeFromSeed(relayPrivateKey);
const hdMain = deriveHdPath(hdMaster, "m/0") as any;
const hdChange = deriveHdPath(hdMaster, "m/1") as any;
const hdDefi = deriveHdPath(hdMaster, "m/7") as any;
return {
walletName: "wiz-test CLI wallet",
walletIcon: "",
getRelayPrivateKey(_uri: string): Uint8Array {
2026-02-26 11:19:47 +01:00
return relayPrivateKey;
},
getPublicKey(path: DerivationPath, index: bigint): Uint8Array {
const hdChain =
(path as number) === 1
? hdChange
: (path as number) === 7
? hdDefi
: hdMain;
const child = deriveHdPrivateNodeChild(hdChain, Number(index));
const pubKey = secp256k1.derivePublicKeyCompressed(child.privateKey);
if (typeof pubKey === "string") throw new Error(`secp256k1: ${pubKey}`);
return pubKey;
},
getXpub(path: DerivationPath): string {
const hdChain =
(path as number) === 1
? hdChange
: (path as number) === 7
? hdDefi
: hdMain;
const result = encodeHdPublicKey({
node: deriveHdPublicNode(hdChain),
network: "mainnet",
});
if (typeof result === "string")
throw new Error(`encodeHdPublicKey: ${result}`);
return result.hdPublicKey;
},
async signTransaction(_request): Promise<any> {
throw new Error("signTransaction not implemented in test wallet");
},
};
}
// ---- Main ----
export async function runWalletMode(options: {
relay: string;
uri: string;
privateKey?: string;
}): Promise<void> {
console.log(chalk.bold("\nwiz-test wallet mode"));
console.log(chalk.dim(`relay: ${options.relay}`));
console.log(chalk.dim(`uri: ${options.uri}`));
console.log();
// Derive relay private key
let relayPrivKey: Uint8Array;
if (options.privateKey) {
if (options.privateKey.length !== 64) {
console.error(chalk.red("--private-key must be 64 hex chars"));
process.exit(1);
}
relayPrivKey = hexToBin(options.privateKey);
} else {
const creds = generateKeyExchangeCredentials();
relayPrivKey = hexToBin(creds.privateKey);
console.log(chalk.dim(`generated relay key: ${creds.privateKey}`));
}
const buildSpinner = ora("Building test wallet adapter...").start();
const adapter = await buildAdapter(relayPrivKey);
buildSpinner.succeed("Wallet adapter ready");
const manager = new WalletConnectionManager(adapter);
manager.on("connectionStatusChanged", (connectionId, status) => {
const s = status.status;
if (s === "connected") {
console.log(chalk.green(`connection ${connectionId}: connected`));
} else if (s === "reconnecting") {
console.log(chalk.yellow(`connection ${connectionId}: reconnecting...`));
} else if (s === "disconnected") {
console.log(chalk.red(`connection ${connectionId}: disconnected`));
}
});
manager.on("pendingSignRequest", (request) => {
console.log(
chalk.yellow("← sign_request") +
chalk.dim(
` conn=${request.connectionId} seq=${request.request.sequence}`,
),
);
console.log(chalk.dim(" (auto-rejecting — test wallet does not sign)"));
manager
.sendSignError(
request.connectionId,
request.request.sequence,
"Test wallet cannot sign",
)
.catch(() => {});
});
console.log(chalk.dim("\nConnecting to dapp..."));
const connectionId = manager.connect(options.uri);
console.log(chalk.dim(`connection id: ${connectionId}`));
console.log(chalk.dim("Press Ctrl+C to quit\n"));
process.on("SIGINT", () => {
console.log(chalk.yellow("\nShutting down..."));
manager.disconnectAll();
process.exit(0);
});
await new Promise(() => {});
}