WizardConnect/packages/core/src/wallet-relay.ts
2026-03-06 11:38:09 +01:00

111 lines
3.1 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 { RelayClient } from "./relay-client.js";
import {
RelayUpdatePayload,
RelayStatusCallback,
initiateRelay,
} from "./relay-handler.js";
import { decodeKeyExchangeURI, DEFAULT_RELAY_PORT } from "./key-exchange.js";
import { hexToBin } from "@bitauth/libauth";
import { deriveNostrPublicKeyBytes } from "./utilnostr.js";
export interface WalletRelayOptions {
uri: string;
walletPrivateKey: Uint8Array;
explicitRelayUrls?: string[];
reconnectInterval?: number;
maxReconnectAttempts?: number;
}
export interface WalletRelayResult {
client: RelayClient;
dappPublicKey: Uint8Array;
walletPublicKey: Uint8Array;
secret: string;
cleanup: () => void;
}
export function initiateWalletRelay(
statusCallback: RelayStatusCallback,
options: WalletRelayOptions,
): WalletRelayResult {
let decoded;
try {
decoded = decodeKeyExchangeURI(options.uri);
} catch (err: any) {
throw new Error(`Failed to decode connection URI: ${err.message}`, {
cause: err,
});
}
const dappPublicKeyHex = decoded.publicKey;
const secret = decoded.secret;
const dappPublicKeyNostr = hexToBin(dappPublicKeyHex);
const hostname = decoded.hostname;
const protocol = decoded.protocol;
const port = decoded.port;
const relayUrl = `${protocol}://${hostname}${port === (protocol === "wss" ? DEFAULT_RELAY_PORT : 80) ? "" : `:${port}`}`;
const walletPublicKeyNostr = deriveNostrPublicKeyBytes(
options.walletPrivateKey,
);
let relayClient: RelayClient | null = null;
const wrappedCallback: RelayStatusCallback = (
payload: RelayUpdatePayload,
) => {
if (!relayClient) {
relayClient = payload.client;
}
if (payload.status.status === "connected") {
// Set dapp's pubkey so outbound messages are addressed correctly.
// The wallet's own pubkey + secret are delivered via wallet_ready.
relayClient!.setPairedPublicKey(dappPublicKeyNostr);
}
statusCallback(payload);
};
const relayUrls: string[] = [relayUrl];
if (options.explicitRelayUrls && options.explicitRelayUrls.length > 0) {
for (const explicitUrl of options.explicitRelayUrls) {
if (!relayUrls.includes(explicitUrl)) {
relayUrls.push(explicitUrl);
}
}
}
const cleanup = initiateRelay(
wrappedCallback,
options.walletPrivateKey,
dappPublicKeyNostr,
{
explicitRelayUrls: relayUrls,
reconnectInterval: options.reconnectInterval,
maxReconnectAttempts: options.maxReconnectAttempts,
},
);
const result: WalletRelayResult = {
get client() {
if (!relayClient) {
throw new Error(
"Relay client not yet initialized. Wait for connection status callback.",
);
}
return relayClient;
},
dappPublicKey: dappPublicKeyNostr,
walletPublicKey: walletPublicKeyNostr,
secret,
cleanup,
};
return result;
}