// 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; }