Files
app.zeusln.zeus/backends/LightningNodeConnect.ts
T

755 lines
28 KiB
TypeScript

import { NativeModules, NativeEventEmitter } from 'react-native';
import LNC from '../zeus_modules/@lightninglabs/lnc-rn';
import { lnrpc, walletrpc } from '../zeus_modules/@lightninglabs/lnc-core';
import { settingsStore, nodeInfoStore } from '../stores/Stores';
import CredentialStore from './LNC/credentialStore';
import OpenChannelRequest from '../models/OpenChannelRequest';
import Base64Utils from '../utils/Base64Utils';
import { snakeize } from '../utils/DataFormatUtils';
import VersionUtils from '../utils/VersionUtils';
import { Hash as sha256Hash } from 'fast-sha256';
import BigNumber from 'bignumber.js';
const ADDRESS_TYPES = [
'WITNESS_PUBKEY_HASH',
'NESTED_PUBKEY_HASH',
'UNUSED_WITNESS_PUBKEY_HASH',
'UNUSED_NESTED_PUBKEY_HASH',
'TAPROOT_PUBKEY',
'UNUSED_TAPROOT_PUBKEY'
];
const NEXT_ADDR_MAP: any = {
WITNESS_PUBKEY_HASH: 0,
NESTED_PUBKEY_HASH: 1,
UNUSED_WITNESS_PUBKEY_HASH: 2,
UNUSED_NESTED_PUBKEY_HASH: 3,
TAPROOT_PUBKEY: 4,
UNUSED_TAPROOT_PUBKEY: 5
};
export default class LightningNodeConnect {
lnc: any;
listener: any;
permOpenChannel: boolean;
permSendCoins: boolean;
permSendLN: boolean;
permNewAddress: boolean;
permImportAccount: boolean;
permForwardingHistory: boolean;
permSignMessage: boolean;
initLNC = async () => {
const { pairingPhrase, mailboxServer, customMailboxServer } =
settingsStore;
this.lnc = new LNC({
credentialStore: await new CredentialStore(
pairingPhrase
).initialize()
});
this.lnc.credentials.pairingPhrase = pairingPhrase;
this.lnc.credentials.serverHost =
mailboxServer === 'custom-defined'
? customMailboxServer
: mailboxServer;
return await this.lnc.credentials.load(pairingPhrase);
};
connect = async () => await this.lnc.connect();
checkPerms = async () => {
// ZEUS-3642: we are temporarily returning all perms
// as true until resolved
// https://github.com/ZeusLN/zeus/issues/3642
//
// this.permOpenChannel = await this.lnc.hasPerms(
// 'lnrpc.Lightning.OpenChannel'
// );
// this.permSendCoins = await this.lnc.hasPerms(
// 'lnrpc.Lightning.SendCoins'
// );
// this.permSendLN = await this.lnc.hasPerms(
// 'routerrpc.Router.SendPaymentV2'
// );
// this.permNewAddress = await this.lnc.hasPerms(
// 'lnrpc.Lightning.NewAddress'
// );
// this.permImportAccount = await this.lnc.hasPerms(
// 'walletrpc.WalletKit.ImportAccount'
// );
// this.permForwardingHistory = await this.lnc.hasPerms(
// 'lnrpc.Lightning.ForwardingHistory'
// );
// this.permSignMessage = await this.lnc.hasPerms(
// 'signrpc.Signer.SignMessage'
// );
this.permOpenChannel = true;
this.permSendCoins = true;
this.permSendLN = true;
this.permNewAddress = true;
this.permImportAccount = true;
this.permForwardingHistory = true;
this.permSignMessage = true;
};
isConnected = async () => await this.lnc.isConnected();
disconnect = () => this.lnc && this.lnc.disconnect();
getTransactions = async () =>
await this.lnc.lnd.lightning
.getTransactions({})
.then((data: lnrpc.TransactionDetails) => {
const formatted = snakeize(data);
return {
transactions: formatted.transactions.reverse()
};
});
getChannels = async () =>
await this.lnc.lnd.lightning
.listChannels({})
.then((data: lnrpc.ListChannelsResponse) => snakeize(data));
getPendingChannels = async () =>
await this.lnc.lnd.lightning
.pendingChannels({})
.then((data: lnrpc.PendingChannelsResponse) => snakeize(data));
getClosedChannels = async () =>
await this.lnc.lnd.lightning
.closedChannels({})
.then((data: lnrpc.ClosedChannelsResponse) => snakeize(data));
getChannelInfo = async (chanId: string) => {
const request: lnrpc.ChanInfoRequest = { chanId, chanPoint: '' };
return await this.lnc.lnd.lightning
.getChanInfo(request)
.then((data: lnrpc.ChannelEdge) => snakeize(data));
};
getBlockchainBalance = async (req: lnrpc.WalletBalanceRequest) =>
await this.lnc.lnd.lightning
.walletBalance(req)
.then((data: lnrpc.WalletBalanceResponse) => snakeize(data));
getLightningBalance = async (req: lnrpc.ChannelBalanceRequest) =>
await this.lnc.lnd.lightning
.channelBalance(req)
.then((data: lnrpc.ChannelBalanceResponse) => snakeize(data));
sendCoins = async (data: any) =>
await this.lnc.lnd.lightning
.sendCoins({
addr: data.addr,
sat_per_vbyte: data.sat_per_vbyte,
amount: data.amount,
spend_unconfirmed: data.spend_unconfirmed,
send_all: data.send_all,
outpoints: data.outpoints
})
.then((data: lnrpc.SendCoinsResponse) => snakeize(data));
sendCustomMessage = async (data: any) =>
await this.lnc.lnd.lightning
.sendCustomMessage({
peer: Base64Utils.hexToBase64(data.peer),
type: data.type,
data: Base64Utils.hexToBase64(data.data)
})
.then((data: lnrpc.SendCustomMessageResponse) => snakeize(data));
subscribeCustomMessages = () =>
this.lnc.lnd.lightning.subscribeCustomMessages({});
getMyNodeInfo = async () =>
await this.lnc.lnd.lightning
.getInfo({})
.then((data: lnrpc.GetInfoResponse) => snakeize(data));
getNetworkInfo = async () =>
await this.lnc.lnd.lightning
.getNetworkInfo({})
.then((data: lnrpc.NetworkInfo) => snakeize(data));
getInvoices = async (
params: { limit?: number; reversed?: boolean } = {
limit: 500,
reversed: true
}
) =>
await this.lnc.lnd.lightning
.listInvoices({
reversed:
params?.reversed !== undefined ? params.reversed : true,
...(params?.limit && {
num_max_invoices: params.limit
})
})
.then((data: lnrpc.ListInvoiceResponse) => snakeize(data));
createInvoice = async (data: any) =>
await this.lnc.lnd.lightning
.addInvoice({
memo: data.memo,
value_msat: data.value_msat || Number(data.value) * 1000,
expiry: data.expiry_seconds,
is_amp: data.is_amp,
is_blinded: data.is_blinded,
private: data.private,
r_preimage: data.preimage
? Base64Utils.hexToBase64(data.preimage)
: undefined,
route_hints: data.route_hints
})
.then((data: lnrpc.AddInvoiceResponse) => snakeize(data));
getPayments = async (
params: {
maxPayments?: number;
reversed?: boolean;
} = {
maxPayments: 500,
reversed: true
}
) =>
await this.lnc.lnd.lightning
.listPayments({
include_incomplete: true,
...(params?.maxPayments && {
max_payments: params.maxPayments
}),
reversed:
params?.reversed !== undefined ? params.reversed : true
})
.then((data: lnrpc.ListPaymentsResponse) => snakeize(data));
getNewAddress = async (data: any) =>
await this.lnc.lnd.lightning
.newAddress({
type: ADDRESS_TYPES[data.type] || data.type,
account: data.account || 'default'
})
.then((data: walletrpc.AddrRequest) => snakeize(data));
getNewChangeAddress = async (data: any) =>
await this.lnc.lnd.walletKit
.nextAddr({
type: NEXT_ADDR_MAP[data.type],
account: data.account || 'default',
change: true
})
.then((data: walletrpc.AddrResponse) => snakeize(data));
openChannelSync = async (data: OpenChannelRequest) => {
let request: any = {
private: data.privateChannel,
scid_alias: data.scidAlias,
local_funding_amount: data.local_funding_amount || 0,
min_confs: data.min_confs,
node_pubkey_string: data.node_pubkey_string,
sat_per_vbyte: data.sat_per_vbyte,
spend_unconfirmed: data.spend_unconfirmed,
close_address: data.close_address
};
if (data.fundMax) {
request.fund_max = true;
delete request.local_funding_amount;
}
if (data.simpleTaprootChannel) {
request.commitment_type = lnrpc.CommitmentType['SIMPLE_TAPROOT'];
}
if (data.utxos && data.utxos.length > 0) {
request.outpoints = data.utxos.map((utxo: string) => {
const [txid_str, output_index] = utxo.split(':');
return {
txid_str,
output_index: Number(output_index)
};
});
}
return await this.lnc.lnd.lightning
.openChannelSync(request)
.then((data: lnrpc.ChannelPoint) => snakeize(data));
};
openChannelStream = (data: OpenChannelRequest) => {
let request: any = {
private: data.privateChannel || false,
scid_alias: data.scidAlias,
local_funding_amount: data.local_funding_amount,
min_confs: data.min_confs,
node_pubkey: Base64Utils.hexToBase64(data.node_pubkey_string),
sat_per_vbyte: !data.funding_shim ? data.sat_per_vbyte : undefined,
spend_unconfirmed: data.spend_unconfirmed,
funding_shim: data.funding_shim
};
if (data.fundMax) {
request.fund_max = true;
}
if (data.simpleTaprootChannel) {
request.commitment_type = lnrpc.CommitmentType['SIMPLE_TAPROOT'];
}
if (data.utxos && data.utxos.length > 0) {
request.outpoints = data.utxos.map((utxo: string) => {
const [txid_str, output_index] = utxo.split(':');
return {
txid_str,
output_index: Number(output_index)
};
});
}
if (data.funding_shim) {
request.funding_shim = data.funding_shim;
delete request.sat_per_vbyte;
}
const streamingCall = this.lnc.lnd.lightning.openChannel(request);
const { LncModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(LncModule);
return new Promise((resolve, reject) => {
this.listener = eventEmitter.addListener(
streamingCall,
(event: any) => {
if (event.result && event.result !== 'EOF') {
let result;
try {
result = JSON.parse(event.result);
resolve({ result });
this.listener.remove();
} catch (e) {
try {
result = JSON.parse(event);
} catch (e2) {
result = event.result || event;
}
reject(result);
this.listener.remove();
}
}
}
);
});
};
connectPeer = async (data: any) =>
await this.lnc.lnd.lightning
.connectPeer(data)
.then((data: lnrpc.ConnectPeerRequest) => snakeize(data));
decodePaymentRequest = async (urlParams?: Array<string>) =>
await this.lnc.lnd.lightning
.decodePayReq({ pay_req: urlParams && urlParams[0] })
.then((data: lnrpc.PayReq) => snakeize(data));
payLightningInvoice = (data: any) => {
if (data.pubkey) delete data.pubkey;
return this.lnc.lnd.router.sendPaymentV2({
...data,
allow_self_payment: true
});
};
closeChannel = async (urlParams?: Array<string>) => {
let params: any = {
channel_point: {
funding_txid_str: urlParams && urlParams[0],
output_index: urlParams && urlParams[1] && Number(urlParams[1])
},
force: urlParams && urlParams[2]
};
if (urlParams && urlParams[3] && !urlParams[2]) {
params.sat_per_vbyte = Number(urlParams[3]);
}
if (urlParams && urlParams[4]) {
params.delivery_address = urlParams[4];
}
return this.lnc.lnd.lightning.closeChannel(params);
};
abandonChannel = async (
urlParams?: Array<string | boolean | undefined>
) => {
const fundingTxidStr =
urlParams && typeof urlParams[0] === 'string'
? urlParams[0]
: undefined;
const outputIndex =
urlParams && typeof urlParams[1] === 'string'
? Number(urlParams[1])
: 0;
const params: any = {
channelPoint: {
fundingTxidStr,
fundingTxidBytes: undefined,
outputIndex
}
};
// Only include boolean parameters if they are explicitly set (not undefined)
if (urlParams && urlParams[2]) {
params.pendingFundingShimOnly = Boolean(urlParams[2]);
}
if (urlParams && urlParams[3]) {
params.iKnowWhatIAmDoing = Boolean(urlParams[3]);
}
return this.lnc.lnd.lightning.abandonChannel(params);
};
getNodeInfo = async (urlParams?: Array<string>) =>
await this.lnc.lnd.lightning
.getNodeInfo({ pub_key: urlParams && urlParams[0] })
.then((data: lnrpc.NodeInfo) => snakeize(data));
getFees = async () =>
await this.lnc.lnd.lightning
.feeReport({})
.then((data: lnrpc.FeeReportResponse) => snakeize(data));
setFees = async (data: any) => {
// handle commas in place of decimals
const base_fee_msat = data.base_fee_msat.replace(/,/g, '.');
const fee_rate = data.fee_rate.replace(/,/g, '.');
let params;
if (data.global) {
params = {
base_fee_msat,
fee_rate: `${Number(fee_rate) / 100}`,
...(this.supportInboundFees() && {
inboundFee: {
...(data.base_fee_msat_inbound !== '' && {
base_fee_msat: data.base_fee_msat_inbound
}),
...(data.fee_rate_inbound !== '' &&
data.fee_rate_inbound !== undefined && {
fee_rate_ppm: `${new BigNumber(
data.fee_rate_inbound
)
.multipliedBy(10000)
.toFixed(0)}`
})
}
}),
global: true,
time_lock_delta: Number(data.time_lock_delta),
min_htlc_msat: data.min_htlc
? `${Number(data.min_htlc) * 1000}`
: null,
max_htlc_msat: data.max_htlc
? `${Number(data.max_htlc) * 1000}`
: null,
min_htlc_msat_specified: data.min_htlc ? true : false
};
} else {
params = {
base_fee_msat,
fee_rate: `${Number(fee_rate) / 100}`,
...(this.supportInboundFees() && {
inboundFee: {
...(data.base_fee_msat_inbound !== '' && {
base_fee_msat: data.base_fee_msat_inbound
}),
...(data.fee_rate_inbound !== '' &&
data.fee_rate_inbound !== undefined && {
fee_rate_ppm: `${new BigNumber(
data.fee_rate_inbound
)
.multipliedBy(10000)
.toFixed(0)}`
})
}
}),
chan_point: {
funding_txid_str: data.chan_point.funding_txid_str,
output_index: data.chan_point.output_index
},
time_lock_delta: Number(data.time_lock_delta),
min_htlc_msat: data.min_htlc
? `${Number(data.min_htlc) * 1000}`
: null,
max_htlc_msat: data.max_htlc
? `${Number(data.max_htlc) * 1000}`
: null,
min_htlc_msat_specified: data.min_htlc ? true : false
};
}
return await this.lnc.lnd.lightning
.updateChannelPolicy(params)
.then((data: lnrpc.PolicyUpdateResponse) => snakeize(data));
};
getRoutes = async (urlParams?: Array<string>) =>
await this.lnc.lnd.lightning
.queryRoutes({
pub_key: urlParams && urlParams[0],
amt: urlParams && urlParams[1] && Number(urlParams[1])
})
.then((data: lnrpc.QueryRoutesResponse) => snakeize(data));
getForwardingHistory = async (
hours = 24,
chanIdIn?: string,
chanIdOut?: string
) => {
const req: any = {
numMaxEvents: 10000000,
startTime: Math.round(
new Date(Date.now() - hours * 60 * 60 * 1000).getTime() / 1000
).toString(),
endTime: Math.round(new Date().getTime() / 1000).toString(),
indexOffset: 0
};
if (this.supports('v0.20.0')) {
if (chanIdIn) {
req.incomingChanIds = [chanIdIn];
}
if (chanIdOut) {
req.outgoingChanIds = [chanIdOut];
}
}
return await this.lnc.lnd.lightning
.forwardingHistory(req)
.then((data: lnrpc.ForwardingHistoryResponse) => snakeize(data));
};
// Coin Control
fundPsbt = async (req: walletrpc.FundPsbtRequest) =>
await this.lnc.lnd.walletKit
.fundPsbt(req)
.then((data: walletrpc.FundPsbtResponse) => snakeize(data));
signPsbt = async (req: walletrpc.SignPsbtRequest) =>
await this.lnc.lnd.walletKit
.signPsbt(req)
.then((data: walletrpc.SignPsbtResponse) => snakeize(data));
finalizePsbt = async (req: walletrpc.FinalizePsbtRequest) =>
await this.lnc.lnd.walletKit
.finalizePsbt(req)
.then((data: walletrpc.FinalizePsbtResponse) => snakeize(data));
publishTransaction = async (req: any) => {
if (req.tx_hex) req.tx_hex = Base64Utils.hexToBase64(req.tx_hex);
return await this.lnc.lnd.walletKit
.publishTransaction(req)
.then((data: walletrpc.PublishResponse) => snakeize(data));
};
fundingStateStep = async (req: any) => {
// Finalize
if (req.psbt_finalize?.final_raw_tx)
req.psbt_finalize.final_raw_tx = Base64Utils.hexToBase64(
req.psbt_finalize.final_raw_tx
);
return await this.lnc.lnd.lightning
.fundingStateStep(req)
.then((data: lnrpc.FundingStateStepResp) => snakeize(data));
};
getUTXOs = async (req: walletrpc.ListUnspentRequest) =>
await this.lnc.lnd.walletKit
.listUnspent(req)
.then((data: walletrpc.ListUnspentResponse) => snakeize(data));
bumpFee = async (req: walletrpc.BumpFeeRequest) =>
await this.lnc.lnd.walletKit
.bumpFee(snakeize(req))
.then((data: walletrpc.BumpFeeResponse) => snakeize(data));
bumpForceCloseFee = async (req: walletrpc.BumpForceCloseFeeRequest) =>
await this.lnc.lnd.walletKit
.bumpForceCloseFee(snakeize(req))
.then((data: walletrpc.BumpForceCloseFeeResponse) =>
snakeize(data)
);
listAccounts = async () =>
await this.lnc.lnd.walletKit
.listAccounts({})
.then((data: walletrpc.ListAccountsResponse) => snakeize(data));
listAddresses = async () =>
await this.lnc.lnd.walletKit
.listAddresses({})
.then((data: walletrpc.ListAddressesResponse) => snakeize(data));
importAccount = async (req: walletrpc.ImportAccountRequest) =>
await this.lnc.lnd.walletKit
.importAccount(req)
.then((data: walletrpc.ImportAccountResponse) => snakeize(data));
signMessage = async (message: string) =>
await this.lnc.lnd.lightning
.signMessage({ msg: Base64Utils.utf8ToBase64(message) })
.then((data: lnrpc.SignMessageResponse) => snakeize(data));
verifyMessage = async (req: lnrpc.VerifyMessageRequest) =>
await this.lnc.lnd.lightning
.verifyMessage({
msg:
typeof req.msg === 'string'
? Base64Utils.utf8ToBase64(req.msg)
: req.msg,
signature: req.signature
})
.then((data: lnrpc.VerifyMessageResponse) => snakeize(data));
signMessageWithAddr = async (msg: string, addr: string) =>
await this.lnc.lnd.walletKit
.signMessageWithAddr({
msg:
typeof msg === 'string'
? Base64Utils.utf8ToBase64(msg)
: Base64Utils.bytesToBase64(msg as Uint8Array),
addr
})
.then((data: walletrpc.SignMessageWithAddrResponse) =>
snakeize(data)
);
verifyMessageWithAddr = async (
msg: string,
signature: string,
addr: string
) =>
await this.lnc.lnd.walletKit
.verifyMessageWithAddr({
msg:
typeof msg === 'string'
? Base64Utils.utf8ToBase64(msg)
: Base64Utils.bytesToBase64(msg as Uint8Array),
signature,
addr
})
.then((data: walletrpc.VerifyMessageWithAddrResponse) =>
snakeize(data)
);
lnurlAuth = async (r_hash: string) => {
const signed = await this.signMessage(r_hash);
return {
signature: new sha256Hash()
.update(Base64Utils.stringToUint8Array(signed.signature))
.digest()
};
};
lookupInvoice = async (data: any) =>
await this.lnc.lnd.lightning
.lookupInvoice({ r_hash: Base64Utils.hexToBase64(data.r_hash) })
.then((data: lnrpc.Invoice) => snakeize(data));
subscribeInvoice = (r_hash: string) =>
this.lnc.lnd.invoices.subscribeSingleInvoice({ r_hash });
subscribeInvoices = () => this.lnc.lnd.lightning.subscribeInvoices();
subscribeTransactions = () =>
this.lnc.lnd.lightning.subscribeTransactions();
supports = (minVersion: string, eosVersion?: string) => {
const { nodeInfo } = nodeInfoStore;
const { version } = nodeInfo;
const { isSupportedVersion } = VersionUtils;
return isSupportedVersion(version, minVersion, eosVersion);
};
listPeers = async () => {
try {
const res = await this.lnc.lnd.lightning.listPeers({});
return res.peers ?? [];
} catch (err) {
console.error('listPeers error:', err);
}
};
disconnectPeer = async (pubkey: string) => {
try {
await this.lnc.lnd.lightning.disconnectPeer({ pubKey: pubkey });
return true;
} catch (error) {
console.error(`Error disconnecting peer ${pubkey}:`, error);
return null;
}
};
getWatchtowerInfo = async (pubkey: string) =>
await this.lnc.lnd.watchtowerClient
.getTowerInfo({
pubkey,
includeSessions: true
})
.then((data: any) => snakeize(data));
listWatchtowers = async (includeSessions = true) =>
await this.lnc.lnd.watchtowerClient
.listTowers({
includeSessions,
excludeExhaustedSessions: false
})
.then((data: any) => snakeize(data));
removeWatchtower = async (pubkey: string, address?: string) =>
await this.lnc.lnd.watchtowerClient
.removeTower({
pubkey,
address
})
.then((data: any) => snakeize(data));
deactivateWatchtower = async (pubkey: string) =>
await this.lnc.lnd.watchtowerClient
.deactivateTower({
pubkey
})
.then((data: any) => snakeize(data));
terminateWatchtowerSession = async (sessionId: string) =>
await this.lnc.lnd.watchtowerClient
.terminateSession({
sessionId
})
.then((data: any) => snakeize(data));
getWatchtowerStats = async () =>
await this.lnc.lnd.watchtowerClient
.stats({})
.then((data: any) => snakeize(data));
getWatchtowerPolicy = async (policyType: string) =>
await this.lnc.lnd.watchtowerClient
.policy({
policyType
})
.then((data: any) => snakeize(data));
supportsWatchtowerClient = () => true;
supportsPeers = () => true;
supportsMessageSigning = () => this.permSignMessage;
supportsAddressMessageSigning = () => true;
supportsLnurlAuth = () => true;
supportsOnchainBalance = () => true;
supportsOnchainSends = () => this.permSendCoins;
supportsOnchainReceiving = () => this.permNewAddress;
supportsLightningSends = () => this.permSendLN;
supportsKeysend = () => true;
supportsChannelManagement = () => this.permOpenChannel;
supportsPendingChannels = () => true;
supportsClosedChannels = () => true;
supportsMPP = () => this.supports('v0.10.0');
supportsAMP = () => this.supports('v0.13.0');
supportsCoinControl = () => this.permNewAddress;
supportsChannelCoinControl = () =>
this.permNewAddress && this.supports('v0.17.0');
supportsHopPicking = () => this.permOpenChannel;
supportsAccounts = () => this.permImportAccount;
supportsRouting = () => this.permForwardingHistory;
supportsNodeInfo = () => true;
supportsWithdrawalRequests = () => false;
singleFeesEarnedTotal = () => false;
supportsAddressTypeSelection = () => true;
supportsNestedSegWit = () => true;
supportsTaproot = () => this.supports('v0.15.0');
supportsBumpFee = () => true;
supportsFlowLSP = () => false;
supportsNetworkInfo = () => true;
supportsSimpleTaprootChannels = () => this.supports('v0.17.0');
supportsCustomPreimages = () => true;
supportsSweep = () => true;
supportsOnchainSendMax = () => this.supports('v0.18.3');
supportsOnchainBatching = () => true;
supportsChannelBatching = () => true;
supportsChannelFundMax = () => true;
supportsLSPScustomMessage = () => true;
supportsLSPS1rest = () => true;
supportsOffers = () => false;
supportsBolt11BlindedRoutes = () => this.supports('v0.18.3');
supportsAddressesWithDerivationPaths = () => this.supports('v0.18.0');
isLNDBased = () => true;
supportsForwardingHistory = () => true;
supportInboundFees = () => this.supports('v0.18.0');
supportsCashuWallet = () => false;
supportsSettingInvoiceExpiration = () => true;
supportsNostrWalletConnectService = () => true;
}