Skip to content

Get Balance #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bindings/lni_nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export interface NodeInfo {
network: string
blockHeight: number
blockHash: string
sendBalanceMsat: number
receiveBalanceMsat: number
feeCreditBalanceMsat: number
unsettledSendBalanceMsat: number
unsettledReceiveBalanceMsat: number
pendingOpenSendBalance: number
pendingOpenReceiveBalance: number
}
export interface Transaction {
type: string
Expand Down
4 changes: 2 additions & 2 deletions bindings/lni_nodejs/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ async function test() {

async function main() {
// phoenixd();
// cln();
cln();
// lnd();
test();
// test();
}

main();
7 changes: 7 additions & 0 deletions bindings/lni_uniffi/src/lni.udl
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ dictionary NodeInfo {
string network;
i64 block_height;
string block_hash;
i64 send_balance_msat;
i64 receive_balance_msat;
i64 fee_credit_balance_msat;
i64 unsettled_send_balance_msat;
i64 unsettled_receive_balance_msat;
i64 pending_open_send_balance;
i64 pending_open_receive_balance;
};

dictionary Transaction {
Expand Down
50 changes: 48 additions & 2 deletions crates/lni/cln/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::types::{
Bolt11Resp, Bolt12Resp, FetchInvoiceResponse, InfoResponse, InvoicesResponse,
Bolt11Resp, Bolt12Resp, ChannelWrapper, FetchInvoiceResponse, InfoResponse, InvoicesResponse,
ListOffersResponse, PayResponse,
};
use crate::types::NodeInfo;
Expand All @@ -21,16 +21,62 @@ pub fn get_info(url: String, rune: String) -> Result<NodeInfo, ApiError> {
.send()
.unwrap();
let response_text = response.text().unwrap();
println!("Raw response: {}", response_text);
// println!("Raw response: {}", response_text);
let info: InfoResponse = serde_json::from_str(&response_text)?;

// https://github.com/ZeusLN/zeus/blob/master/backends/CoreLightningRequestHandler.ts#L28
let funds_url = format!("{}/v1/listfunds", url);
let funds_response = client
.post(&funds_url)
.header("Content-Type", "application/json")
.send()
.unwrap();
let funds_response_text = funds_response.text().unwrap();
// println!("funds_response_text: {}", funds_response_text);
let channels: ChannelWrapper = serde_json::from_str(&funds_response_text)?;

let mut local_balance: i64 = 0;
let mut remote_balance: i64 = 0;
let mut unsettled_send_balance_msat: i64 = 0;
let mut unsettled_receive_balance_msat: i64 = 0;
let mut pending_open_send_balance: i64 = 0;
let mut pending_open_receive_balance: i64 = 0;
// rules and states here https://docs.corelightning.org/reference/listfunds
for channel in channels.channels.iter() {
if channel.state == "CHANNELD_NORMAL" && channel.connected {
// Active channels
local_balance += channel.our_amount_msat;
remote_balance += channel.amount_msat - channel.our_amount_msat;
} else if channel.state == "CHANNELD_NORMAL" && !channel.connected {
// Unsettled channels (previously inactive)
unsettled_send_balance_msat += channel.our_amount_msat;
unsettled_receive_balance_msat += channel.amount_msat - channel.our_amount_msat;
} else if channel.state == "CHANNELD_AWAITING_LOCKIN"
|| channel.state == "DUALOPEND_AWAITING_LOCKIN"
|| channel.state == "DUALOPEND_OPEN_INIT"
|| channel.state == "DUALOPEND_OPEN_COMMITTED"
|| channel.state == "DUALOPEND_OPEN_COMMIT_READY"
|| channel.state == "OPENINGD" {
// Pending open channels
pending_open_send_balance += channel.our_amount_msat;
pending_open_receive_balance += channel.amount_msat - channel.our_amount_msat;
}
}

let node_info = NodeInfo {
alias: info.alias,
color: info.color,
pubkey: info.id,
network: info.network,
block_height: info.blockheight,
block_hash: "".to_string(),
send_balance_msat: local_balance,
receive_balance_msat: remote_balance,
unsettled_send_balance_msat,
unsettled_receive_balance_msat,
pending_open_send_balance,
pending_open_receive_balance,
..Default::default()
};
Ok(node_info)
}
Expand Down
20 changes: 20 additions & 0 deletions crates/lni/cln/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,23 @@ pub struct Bolt12Resp {
pub struct ListOffersResponse {
pub offers: Vec<PayCode>,
}

#[derive(Debug, Deserialize)]
pub struct ChannelWrapper {
#[serde(skip)]
pub outputs: Vec<serde_json::Value>,
pub channels: Vec<Channel>,
}

#[derive(Debug, Deserialize)]
pub struct Channel {
pub peer_id: String,
pub connected: bool,
pub state: String,
pub channel_id: String,
pub short_channel_id: Option<String>,
pub our_amount_msat: i64,
pub amount_msat: i64,
pub funding_txid: String,
pub funding_output: i32,
}
49 changes: 48 additions & 1 deletion crates/lni/lnd/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::types::{
Bolt11Resp, FetchInvoiceResponse, GetInfoResponse, ListInvoiceResponse,
BalancesResponse, Bolt11Resp, FetchInvoiceResponse, GetInfoResponse, ListInvoiceResponse,
ListInvoiceResponseWrapper, LndPayInvoiceResponseWrapper,
};
use crate::types::NodeInfo;
Expand Down Expand Up @@ -38,13 +38,60 @@ pub fn get_info(url: String, macaroon: String) -> Result<NodeInfo, ApiError> {
let response_text = response_text.as_str();
let info: GetInfoResponse = serde_json::from_str(&response_text)?;

// get balance
// /v1/balance/channels
// https://lightning.engineering/api-docs/api/lnd/lightning/channel-balance/
// send_balance_msats, receive_balance_msats, pending_balance, inactive_balance
let balance_url = format!("{}/v1/balance/channels", url);
let balance_response = client.get(&balance_url).send().unwrap();
let balance_response_text = balance_response.text().unwrap();
let balance_response_text = balance_response_text.as_str();
let balance: BalancesResponse = serde_json::from_str(&balance_response_text)?;

let node_info = NodeInfo {
alias: info.alias,
color: info.color,
pubkey: info.identity_pubkey,
network: info.chains[0].network.clone(),
block_height: info.block_height,
block_hash: info.block_hash,
send_balance_msat: balance
.local_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
receive_balance_msat: balance
.remote_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
unsettled_send_balance_msat: balance
.unsettled_local_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
unsettled_receive_balance_msat: balance
.unsettled_remote_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
pending_open_send_balance: balance
.pending_open_local_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
pending_open_receive_balance: balance
.pending_open_remote_balance
.msat
.unwrap_or_default()
.parse::<i64>()
.unwrap_or_default(),
..Default::default()
};
Ok(node_info)
}
Expand Down
19 changes: 19 additions & 0 deletions crates/lni/lnd/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,22 @@ pub struct Htlc {
pub failure: Option<serde_json::Value>,
pub preimage: String,
}

#[derive(Debug, Deserialize)]
pub struct BalancesResponse {
pub balance: String,
pub pending_open_balance: String,
pub local_balance: Amount,
pub remote_balance: Amount,
pub unsettled_local_balance: Amount,
pub unsettled_remote_balance: Amount,
pub pending_open_local_balance: Amount,
pub pending_open_remote_balance: Amount,
pub custom_channel_data: String,
}

#[derive(Debug, Deserialize)]
pub struct Amount {
pub sat: Option<String>,
pub msat: Option<String>,
}
19 changes: 15 additions & 4 deletions crates/lni/phoenixd/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::types::{
PhoenixPayInvoiceResp,
};
use crate::{
ApiError, InvoiceType, NodeInfo, PayCode, PayInvoiceParams, PayInvoiceResponse, Transaction,
phoenixd::types::GetBalanceResponse, ApiError, InvoiceType, NodeInfo, PayCode, PayInvoiceParams, PayInvoiceResponse, Transaction
};
use serde_urlencoded;

Expand All @@ -14,21 +14,32 @@ use serde_urlencoded;
// https://phoenix.acinq.co/server/api

pub fn get_info(url: String, password: String) -> Result<NodeInfo, ApiError> {
let url = format!("{}/getinfo", url);
let info_url = format!("{}/getinfo", url);
let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
let response = client.get(&url).basic_auth("", Some(password)).send();

let response: Result<reqwest::blocking::Response, reqwest::Error> = client.get(&info_url).basic_auth("", Some(password.clone())).send();
let response_text = response.unwrap().text().unwrap();
println!("Raw response: {}", response_text);
println!("get node info response: {}", response_text);
let info: InfoResponse = serde_json::from_str(&response_text)?;

// /getbalance
let balance_url = format!("{}/getbalance", url);
let balance_response: Result<reqwest::blocking::Response, reqwest::Error> = client.get(&balance_url).basic_auth("", Some(password)).send();
let balance_response_text = balance_response.unwrap().text().unwrap();
println!("balance_response: {}", balance_response_text);
let balance: GetBalanceResponse = serde_json::from_str(&balance_response_text)?;

let node_info = NodeInfo {
alias: "Phoenixd".to_string(),
color: "".to_string(),
pubkey: info.node_id,
network: "bitcoin".to_string(),
block_height: 0,
block_hash: "".to_string(),
send_balance_msat: info.channels[0].balance_sat * 1000,
receive_balance_msat: info.channels[0].inbound_liquidity_sat * 1000,
fee_credit_balance_msat: balance.fee_credit_sat * 1000,
..Default::default()
};
Ok(node_info)
}
Expand Down
25 changes: 25 additions & 0 deletions crates/lni/phoenixd/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ use napi_derive::napi;
pub struct InfoResponse {
#[serde(rename = "nodeId")] // Handle JSON field `nodeId`
pub node_id: String,
pub channels: Vec<Channel>,
}

#[derive(Debug, Deserialize)]
pub struct Channel {
#[serde(rename = "state")]
pub state: String, // Normal
#[serde(rename = "channelId")]
pub channel_id: String,
#[serde(rename = "balanceSat")]
pub balance_sat: i64,
#[serde(rename = "inboundLiquiditySat")]
pub inbound_liquidity_sat: i64,
#[serde(rename = "capacitySat")]
pub capacity_sat: i64,
#[serde(rename = "fundingTxId")]
pub funding_tx_id: String,
}

#[derive(Debug, Deserialize)]
pub struct GetBalanceResponse {
#[serde(rename = "balanceSat")]
pub balance_sat: i64,
#[serde(rename = "feeCreditSat")]
pub fee_credit_sat: i64,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
30 changes: 28 additions & 2 deletions crates/lni/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ pub struct NodeInfo {
pub network: String,
pub block_height: i64,
pub block_hash: String,
pub send_balance_msat: i64, // Sum of channels send capacity
pub receive_balance_msat: i64, // Sum of channels receive capacity
pub fee_credit_balance_msat: i64, // used in Phoenixd, typically first 30,000 sats are a "fee credit" aka custodial, but cannot withdraw (balance is used for future fees). Then it opens channel when it gets above 30,000 sats.
pub unsettled_send_balance_msat: i64, // Sum of channels send unsettled balances.
pub unsettled_receive_balance_msat: i64, // Sum of channels receive unsettled balances.
pub pending_open_send_balance: i64, // Sum of channels pending open send balances.
pub pending_open_receive_balance: i64, // Sum of channels pending open receive balances.
}
impl Default for NodeInfo {
fn default() -> Self {
Self {
alias: String::new(),
color: String::new(),
pubkey: String::new(),
network: String::new(),
block_height: 0,
block_hash: String::new(),
send_balance_msat: 0,
receive_balance_msat: 0,
fee_credit_balance_msat: 0,
unsettled_send_balance_msat: 0,
unsettled_receive_balance_msat: 0,
pending_open_send_balance: 0,
pending_open_receive_balance: 0,
}
}
}

#[cfg_attr(feature = "napi_rs", napi(object))]
Expand Down Expand Up @@ -258,7 +284,7 @@ pub struct PayCode {
#[derive(Debug, Serialize, Deserialize)]
pub struct PayInvoiceParams {
pub invoice: String,
pub fee_limit_msat: Option<i64>, // mutually exclusive with fee_limit_percentage (only set one or the other)
pub fee_limit_msat: Option<i64>, // mutually exclusive with fee_limit_percentage (only set one or the other)
pub fee_limit_percentage: Option<f64>, // mutually exclusive with fee_limit_msat
pub timeout_seconds: Option<i64>,
pub amount_msats: Option<i64>, // used the specify the amount for zero amount invoices
Expand All @@ -275,7 +301,7 @@ impl Default for PayInvoiceParams {
invoice: "".to_string(),
fee_limit_msat: None,
fee_limit_percentage: None, // 0.2% is a sensible default
timeout_seconds: Some(60), // default to 60 seconds timeout
timeout_seconds: Some(60), // default to 60 seconds timeout
amount_msats: None,

max_parts: None,
Expand Down