Files
app.zeusln.zeus/backends/CoreLightningRequestHandler.ts

317 lines
11 KiB
TypeScript

import CLNRest from './CLNRest';
import AddressUtils from '../utils/AddressUtils';
const api = new CLNRest();
// Returns onchain balance of core-lightning node
export const getBalance = (data: any) => {
const opArray = data.outputs;
let confBalance = 0;
let unconfBalance = 0;
let totalBalance = 0;
for (let i = 0; i < opArray.length; i++) {
if (opArray[i].status === 'confirmed')
confBalance = confBalance + opArray[i].amount_msat / 1000;
else if (opArray[i].status === 'unconfirmed')
unconfBalance = unconfBalance + opArray[i].amount_msat / 1000;
}
totalBalance = confBalance + unconfBalance;
return {
total_balance: totalBalance,
confirmed_balance: confBalance,
unconfirmed_balance: unconfBalance
};
};
// Returns offchain balances of core-lightning node
export const getOffchainBalance = (data: any) => {
const chanArray = data.channels;
let localBalance = 0;
let remoteBalance = 0;
let pendingBalance = 0;
let inactiveBalance = 0;
for (let i = 0; i < chanArray.length; i++) {
if (
chanArray[i].state === 'CHANNELD_NORMAL' &&
chanArray[i].connected === true
) {
localBalance = localBalance + chanArray[i].our_amount_msat;
remoteBalance =
remoteBalance +
chanArray[i].amount_msat -
chanArray[i].our_amount_msat;
} else if (
chanArray[i].state === 'CHANNELD_NORMAL' &&
chanArray[i].connected === false
) {
inactiveBalance = inactiveBalance + chanArray[i].our_amount_msat;
} else if (
chanArray[i].state === 'CHANNELD_AWAITING_LOCKIN' ||
chanArray[i].state === 'DUALOPEND_AWAITING_LOCKIN'
) {
pendingBalance = pendingBalance + chanArray[i].our_amount_msat;
}
}
localBalance = localBalance / 1000;
remoteBalance = remoteBalance / 1000;
inactiveBalance = inactiveBalance / 1000;
pendingBalance = pendingBalance / 1000;
return {
balance: localBalance,
remote_balance: remoteBalance,
inactive_balance: inactiveBalance,
pending_balance: pendingBalance
};
};
// Get your peers and the channel info for core lightning node
export const listPeerChannels = async (data: any) => {
const formattedChannels = data.channels
.map((peer: any) => {
if (
peer.state === 'ONCHAIN' ||
peer.state === 'CLOSED' ||
peer.state === 'CHANNELD_AWAITING_LOCKIN'
) {
return;
}
return {
active: peer.peer_connected,
remote_pubkey: peer.peer_id,
channel_point: peer.funding_txid,
chan_id: peer.channel_id,
short_channel_id: peer.short_channel_id,
capacity: Number(peer.total_msat / 1000).toString(),
local_balance: Number(peer.to_us_msat / 1000).toString(),
remote_balance: Number(
(peer.total_msat - peer.to_us_msat) / 1000
).toString(),
total_satoshis_sent: Number(
peer.out_fulfilled_msat / 1000
).toString(),
total_satoshis_received: Number(
peer.in_fulfilled_msat / 1000
).toString(),
num_updates: (
peer.in_payments_offered + peer.out_payments_offered
).toString(),
csv_delay: peer.our_to_self_delay,
private: peer.private,
local_chan_reserve_sat: Number(
peer.our_reserve_msat / 1000
).toString(),
remote_chan_reserve_sat: Number(
peer.their_reserve_msat / 1000
).toString(),
close_address: peer.close_to_addr
};
})
.filter((n: any) => !!n);
const channelsWithAliases = await Promise.all(
formattedChannels.map(async (n: any) => {
const { nodes } = await api.getNode({ id: n.remote_pubkey });
if (nodes.length) {
n.alias = nodes[0].alias || '';
} else {
n.alias = '';
}
return n;
})
);
return { channels: channelsWithAliases };
};
// Returns a list of closed Lightning Network channels
export const listClosedChannels = (data: any) => {
const formattedClosedChannels = data.closedchannels.map((channel: any) => ({
remote_pubkey: channel.peer_id,
capacity: Number(channel.total_msat / 1000).toString(),
channel_id: channel.channel_id,
short_channel_id: channel.short_channel_id,
closing_txid: channel.last_commitment_txid,
opener: channel.opener,
closer: channel.closer,
private: channel.private,
channel_type: channel.channel_type?.names || [],
funding_txid: channel.funding_txid,
total_msat: channel.total_msat.toString(),
to_us_msat: channel.final_to_us_msat.toString(),
min_to_us_satoshis: Number(channel.min_to_us_msat / 1000).toString(),
max_to_us_satoshis: Number(channel.max_to_us_msat / 1000).toString(),
total_htlcs_sent: channel.total_htlcs_sent.toString(),
close_cause: channel.close_cause,
last_commitment_fee_satoshis: Number(
channel.last_commitment_fee_msat / 1000
).toString(),
last_stable_connection: channel.last_stable_connection
}));
return { channels: formattedClosedChannels };
};
export const listPeers = async (data: any) => {
const formattedPeers = data.peers.map((peer: any) => {
return {
id: peer.id,
connected: peer.connected,
num_channels: peer.num_channels,
netaddr: peer.netaddr,
features: peer.features
};
});
const peersWithAliases = await Promise.all(
formattedPeers.map(async (n: any) => {
const { nodes } = await api.getNode({ id: n.id });
if (nodes.length) {
n.alias = nodes[0].alias || '';
} else {
n.alias = '';
}
return n;
})
);
return { peersWithAliases };
};
// Get all chain transactions from your core-lightnig node
export const getChainTransactions = async () => {
const sqlQuery =
"SELECT account, tag, outpoint, credit_msat, debit_msat, timestamp, blockheight FROM bkpr_accountevents WHERE (tag='deposit' OR tag='to_them' OR tag='channel_open' OR tag='channel_close') ORDER BY timestamp DESC LIMIT 150";
const results = await Promise.allSettled([
api.postRequest('/v1/sql', { query: sqlQuery.trim() }),
api.postRequest('/v1/listtransactions'),
api.postRequest('/v1/getinfo')
]);
const [sqlResult, listTxsResult, getinfoResult]: any = results;
listTxsResult?.value?.transactions?.forEach((tx: any) => {
const addresses: Array<string> = [];
tx.outputs.forEach((output: any) => {
try {
const address = AddressUtils.scriptPubKeyToAddress(
output.scriptPubKey
);
if (address) addresses.push(address);
} catch (e) {}
});
tx.dest_addresses = addresses;
});
// If getinfo fails, return blank txs
if (getinfoResult.status !== 'fulfilled') {
return { transactions: [] };
}
const getinfo = getinfoResult.value;
const allTxs =
sqlResult.status === 'fulfilled' ? sqlResult.value : { rows: [] };
const walletTxs = allTxs.rows.filter(
(tx: any) =>
!!tx[1] && tx[1] === 'deposit' && !!tx[0] && tx[0] === 'wallet'
);
const externalTxs = allTxs.rows.filter(
(tx: any) =>
!!tx[1] && tx[1] === 'deposit' && !!tx[0] && tx[0] === 'external'
);
const listTxs =
listTxsResult.status === 'fulfilled'
? listTxsResult.value
: { transactions: [] };
const transactions = listTxs.transactions
.map((tx: any) => {
const withdrawal = externalTxs.find((n: any) => {
const txid = n[2] ? n[2].split(':')[0] : null;
// Check if the deposit is associated with a channel open or close
const isChannelChange = allTxs.rows.find((c: any) => {
if (!c[2]) {
return undefined;
}
return (
c[2].split(':')[0] === tx.hash &&
(c[1] === 'channel_open' || c[1] === 'channel_close')
);
});
if (isChannelChange) {
return undefined;
}
return txid === tx.hash;
});
const deposit = walletTxs.find((n: any) => {
const txid = n[2] ? n[2].split(':')[0] : null;
// Check if the deposit is associated with a channel open or close
const isChannelChange = allTxs.rows.find((c: any) => {
if (!c[2]) {
return undefined;
}
return (
c[2].split(':')[0] === tx.hash &&
(c[1] === 'channel_open' || c[1] === 'channel_close')
);
});
if (isChannelChange) {
return undefined;
}
return txid === tx.hash;
});
if (withdrawal) {
return {
amount: -Math.abs(withdrawal[3]) / 1000,
block_height: withdrawal[6],
num_confirmations: getinfo.blockheight - withdrawal[6],
time_stamp: withdrawal[5],
txid: tx.hash,
dest_addresses: tx.dest_addresses
};
}
if (deposit) {
return {
amount: deposit[3] / 1000,
block_height: deposit[6],
num_confirmations: getinfo.blockheight - deposit[6],
time_stamp: deposit[5],
txid: tx.hash,
dest_addresses: tx.dest_addresses
};
}
return null;
})
.filter((n: any) => !!n);
// Sort the transactions array by the time_stamp field (descending order)
transactions.sort((a: any, b: any) => b.time_stamp - a.time_stamp);
return {
transactions
};
};