Skip to content

Commit 7e2ea72

Browse files
authored
Get Balance (#6)
* get balance phoenixd * lnd get balance * cln list funds * update typescript
1 parent 794dae2 commit 7e2ea72

File tree

10 files changed

+219
-11
lines changed

10 files changed

+219
-11
lines changed

bindings/lni_nodejs/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ export interface NodeInfo {
5454
network: string
5555
blockHeight: number
5656
blockHash: string
57+
sendBalanceMsat: number
58+
receiveBalanceMsat: number
59+
feeCreditBalanceMsat: number
60+
unsettledSendBalanceMsat: number
61+
unsettledReceiveBalanceMsat: number
62+
pendingOpenSendBalance: number
63+
pendingOpenReceiveBalance: number
5764
}
5865
export interface Transaction {
5966
type: string

bindings/lni_nodejs/main.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ async function test() {
142142

143143
async function main() {
144144
// phoenixd();
145-
// cln();
145+
cln();
146146
// lnd();
147-
test();
147+
// test();
148148
}
149149

150150
main();

bindings/lni_uniffi/src/lni.udl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ dictionary NodeInfo {
168168
string network;
169169
i64 block_height;
170170
string block_hash;
171+
i64 send_balance_msat;
172+
i64 receive_balance_msat;
173+
i64 fee_credit_balance_msat;
174+
i64 unsettled_send_balance_msat;
175+
i64 unsettled_receive_balance_msat;
176+
i64 pending_open_send_balance;
177+
i64 pending_open_receive_balance;
171178
};
172179

173180
dictionary Transaction {

crates/lni/cln/api.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::types::{
2-
Bolt11Resp, Bolt12Resp, FetchInvoiceResponse, InfoResponse, InvoicesResponse,
2+
Bolt11Resp, Bolt12Resp, ChannelWrapper, FetchInvoiceResponse, InfoResponse, InvoicesResponse,
33
ListOffersResponse, PayResponse,
44
};
55
use crate::types::NodeInfo;
@@ -21,16 +21,62 @@ pub fn get_info(url: String, rune: String) -> Result<NodeInfo, ApiError> {
2121
.send()
2222
.unwrap();
2323
let response_text = response.text().unwrap();
24-
println!("Raw response: {}", response_text);
24+
// println!("Raw response: {}", response_text);
2525
let info: InfoResponse = serde_json::from_str(&response_text)?;
2626

27+
// https://github.com/ZeusLN/zeus/blob/master/backends/CoreLightningRequestHandler.ts#L28
28+
let funds_url = format!("{}/v1/listfunds", url);
29+
let funds_response = client
30+
.post(&funds_url)
31+
.header("Content-Type", "application/json")
32+
.send()
33+
.unwrap();
34+
let funds_response_text = funds_response.text().unwrap();
35+
// println!("funds_response_text: {}", funds_response_text);
36+
let channels: ChannelWrapper = serde_json::from_str(&funds_response_text)?;
37+
38+
let mut local_balance: i64 = 0;
39+
let mut remote_balance: i64 = 0;
40+
let mut unsettled_send_balance_msat: i64 = 0;
41+
let mut unsettled_receive_balance_msat: i64 = 0;
42+
let mut pending_open_send_balance: i64 = 0;
43+
let mut pending_open_receive_balance: i64 = 0;
44+
// rules and states here https://docs.corelightning.org/reference/listfunds
45+
for channel in channels.channels.iter() {
46+
if channel.state == "CHANNELD_NORMAL" && channel.connected {
47+
// Active channels
48+
local_balance += channel.our_amount_msat;
49+
remote_balance += channel.amount_msat - channel.our_amount_msat;
50+
} else if channel.state == "CHANNELD_NORMAL" && !channel.connected {
51+
// Unsettled channels (previously inactive)
52+
unsettled_send_balance_msat += channel.our_amount_msat;
53+
unsettled_receive_balance_msat += channel.amount_msat - channel.our_amount_msat;
54+
} else if channel.state == "CHANNELD_AWAITING_LOCKIN"
55+
|| channel.state == "DUALOPEND_AWAITING_LOCKIN"
56+
|| channel.state == "DUALOPEND_OPEN_INIT"
57+
|| channel.state == "DUALOPEND_OPEN_COMMITTED"
58+
|| channel.state == "DUALOPEND_OPEN_COMMIT_READY"
59+
|| channel.state == "OPENINGD" {
60+
// Pending open channels
61+
pending_open_send_balance += channel.our_amount_msat;
62+
pending_open_receive_balance += channel.amount_msat - channel.our_amount_msat;
63+
}
64+
}
65+
2766
let node_info = NodeInfo {
2867
alias: info.alias,
2968
color: info.color,
3069
pubkey: info.id,
3170
network: info.network,
3271
block_height: info.blockheight,
3372
block_hash: "".to_string(),
73+
send_balance_msat: local_balance,
74+
receive_balance_msat: remote_balance,
75+
unsettled_send_balance_msat,
76+
unsettled_receive_balance_msat,
77+
pending_open_send_balance,
78+
pending_open_receive_balance,
79+
..Default::default()
3480
};
3581
Ok(node_info)
3682
}

crates/lni/cln/types.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,23 @@ pub struct Bolt12Resp {
8888
pub struct ListOffersResponse {
8989
pub offers: Vec<PayCode>,
9090
}
91+
92+
#[derive(Debug, Deserialize)]
93+
pub struct ChannelWrapper {
94+
#[serde(skip)]
95+
pub outputs: Vec<serde_json::Value>,
96+
pub channels: Vec<Channel>,
97+
}
98+
99+
#[derive(Debug, Deserialize)]
100+
pub struct Channel {
101+
pub peer_id: String,
102+
pub connected: bool,
103+
pub state: String,
104+
pub channel_id: String,
105+
pub short_channel_id: Option<String>,
106+
pub our_amount_msat: i64,
107+
pub amount_msat: i64,
108+
pub funding_txid: String,
109+
pub funding_output: i32,
110+
}

crates/lni/lnd/api.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::types::{
2-
Bolt11Resp, FetchInvoiceResponse, GetInfoResponse, ListInvoiceResponse,
2+
BalancesResponse, Bolt11Resp, FetchInvoiceResponse, GetInfoResponse, ListInvoiceResponse,
33
ListInvoiceResponseWrapper, LndPayInvoiceResponseWrapper,
44
};
55
use crate::types::NodeInfo;
@@ -38,13 +38,60 @@ pub fn get_info(url: String, macaroon: String) -> Result<NodeInfo, ApiError> {
3838
let response_text = response_text.as_str();
3939
let info: GetInfoResponse = serde_json::from_str(&response_text)?;
4040

41+
// get balance
42+
// /v1/balance/channels
43+
// https://lightning.engineering/api-docs/api/lnd/lightning/channel-balance/
44+
// send_balance_msats, receive_balance_msats, pending_balance, inactive_balance
45+
let balance_url = format!("{}/v1/balance/channels", url);
46+
let balance_response = client.get(&balance_url).send().unwrap();
47+
let balance_response_text = balance_response.text().unwrap();
48+
let balance_response_text = balance_response_text.as_str();
49+
let balance: BalancesResponse = serde_json::from_str(&balance_response_text)?;
50+
4151
let node_info = NodeInfo {
4252
alias: info.alias,
4353
color: info.color,
4454
pubkey: info.identity_pubkey,
4555
network: info.chains[0].network.clone(),
4656
block_height: info.block_height,
4757
block_hash: info.block_hash,
58+
send_balance_msat: balance
59+
.local_balance
60+
.msat
61+
.unwrap_or_default()
62+
.parse::<i64>()
63+
.unwrap_or_default(),
64+
receive_balance_msat: balance
65+
.remote_balance
66+
.msat
67+
.unwrap_or_default()
68+
.parse::<i64>()
69+
.unwrap_or_default(),
70+
unsettled_send_balance_msat: balance
71+
.unsettled_local_balance
72+
.msat
73+
.unwrap_or_default()
74+
.parse::<i64>()
75+
.unwrap_or_default(),
76+
unsettled_receive_balance_msat: balance
77+
.unsettled_remote_balance
78+
.msat
79+
.unwrap_or_default()
80+
.parse::<i64>()
81+
.unwrap_or_default(),
82+
pending_open_send_balance: balance
83+
.pending_open_local_balance
84+
.msat
85+
.unwrap_or_default()
86+
.parse::<i64>()
87+
.unwrap_or_default(),
88+
pending_open_receive_balance: balance
89+
.pending_open_remote_balance
90+
.msat
91+
.unwrap_or_default()
92+
.parse::<i64>()
93+
.unwrap_or_default(),
94+
..Default::default()
4895
};
4996
Ok(node_info)
5097
}

crates/lni/lnd/types.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,22 @@ pub struct Htlc {
220220
pub failure: Option<serde_json::Value>,
221221
pub preimage: String,
222222
}
223+
224+
#[derive(Debug, Deserialize)]
225+
pub struct BalancesResponse {
226+
pub balance: String,
227+
pub pending_open_balance: String,
228+
pub local_balance: Amount,
229+
pub remote_balance: Amount,
230+
pub unsettled_local_balance: Amount,
231+
pub unsettled_remote_balance: Amount,
232+
pub pending_open_local_balance: Amount,
233+
pub pending_open_remote_balance: Amount,
234+
pub custom_channel_data: String,
235+
}
236+
237+
#[derive(Debug, Deserialize)]
238+
pub struct Amount {
239+
pub sat: Option<String>,
240+
pub msat: Option<String>,
241+
}

crates/lni/phoenixd/api.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::types::{
33
PhoenixPayInvoiceResp,
44
};
55
use crate::{
6-
ApiError, InvoiceType, NodeInfo, PayCode, PayInvoiceParams, PayInvoiceResponse, Transaction,
6+
phoenixd::types::GetBalanceResponse, ApiError, InvoiceType, NodeInfo, PayCode, PayInvoiceParams, PayInvoiceResponse, Transaction
77
};
88
use serde_urlencoded;
99

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

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

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

25+
// /getbalance
26+
let balance_url = format!("{}/getbalance", url);
27+
let balance_response: Result<reqwest::blocking::Response, reqwest::Error> = client.get(&balance_url).basic_auth("", Some(password)).send();
28+
let balance_response_text = balance_response.unwrap().text().unwrap();
29+
println!("balance_response: {}", balance_response_text);
30+
let balance: GetBalanceResponse = serde_json::from_str(&balance_response_text)?;
31+
2532
let node_info = NodeInfo {
2633
alias: "Phoenixd".to_string(),
2734
color: "".to_string(),
2835
pubkey: info.node_id,
2936
network: "bitcoin".to_string(),
3037
block_height: 0,
3138
block_hash: "".to_string(),
39+
send_balance_msat: info.channels[0].balance_sat * 1000,
40+
receive_balance_msat: info.channels[0].inbound_liquidity_sat * 1000,
41+
fee_credit_balance_msat: balance.fee_credit_sat * 1000,
42+
..Default::default()
3243
};
3344
Ok(node_info)
3445
}

crates/lni/phoenixd/types.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@ use napi_derive::napi;
88
pub struct InfoResponse {
99
#[serde(rename = "nodeId")] // Handle JSON field `nodeId`
1010
pub node_id: String,
11+
pub channels: Vec<Channel>,
12+
}
13+
14+
#[derive(Debug, Deserialize)]
15+
pub struct Channel {
16+
#[serde(rename = "state")]
17+
pub state: String, // Normal
18+
#[serde(rename = "channelId")]
19+
pub channel_id: String,
20+
#[serde(rename = "balanceSat")]
21+
pub balance_sat: i64,
22+
#[serde(rename = "inboundLiquiditySat")]
23+
pub inbound_liquidity_sat: i64,
24+
#[serde(rename = "capacitySat")]
25+
pub capacity_sat: i64,
26+
#[serde(rename = "fundingTxId")]
27+
pub funding_tx_id: String,
28+
}
29+
30+
#[derive(Debug, Deserialize)]
31+
pub struct GetBalanceResponse {
32+
#[serde(rename = "balanceSat")]
33+
pub balance_sat: i64,
34+
#[serde(rename = "feeCreditSat")]
35+
pub fee_credit_sat: i64,
1136
}
1237

1338
#[derive(Debug, Serialize, Deserialize)]

crates/lni/types.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@ pub struct NodeInfo {
2626
pub network: String,
2727
pub block_height: i64,
2828
pub block_hash: String,
29+
pub send_balance_msat: i64, // Sum of channels send capacity
30+
pub receive_balance_msat: i64, // Sum of channels receive capacity
31+
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.
32+
pub unsettled_send_balance_msat: i64, // Sum of channels send unsettled balances.
33+
pub unsettled_receive_balance_msat: i64, // Sum of channels receive unsettled balances.
34+
pub pending_open_send_balance: i64, // Sum of channels pending open send balances.
35+
pub pending_open_receive_balance: i64, // Sum of channels pending open receive balances.
36+
}
37+
impl Default for NodeInfo {
38+
fn default() -> Self {
39+
Self {
40+
alias: String::new(),
41+
color: String::new(),
42+
pubkey: String::new(),
43+
network: String::new(),
44+
block_height: 0,
45+
block_hash: String::new(),
46+
send_balance_msat: 0,
47+
receive_balance_msat: 0,
48+
fee_credit_balance_msat: 0,
49+
unsettled_send_balance_msat: 0,
50+
unsettled_receive_balance_msat: 0,
51+
pending_open_send_balance: 0,
52+
pending_open_receive_balance: 0,
53+
}
54+
}
2955
}
3056

3157
#[cfg_attr(feature = "napi_rs", napi(object))]
@@ -258,7 +284,7 @@ pub struct PayCode {
258284
#[derive(Debug, Serialize, Deserialize)]
259285
pub struct PayInvoiceParams {
260286
pub invoice: String,
261-
pub fee_limit_msat: Option<i64>, // mutually exclusive with fee_limit_percentage (only set one or the other)
287+
pub fee_limit_msat: Option<i64>, // mutually exclusive with fee_limit_percentage (only set one or the other)
262288
pub fee_limit_percentage: Option<f64>, // mutually exclusive with fee_limit_msat
263289
pub timeout_seconds: Option<i64>,
264290
pub amount_msats: Option<i64>, // used the specify the amount for zero amount invoices
@@ -275,7 +301,7 @@ impl Default for PayInvoiceParams {
275301
invoice: "".to_string(),
276302
fee_limit_msat: None,
277303
fee_limit_percentage: None, // 0.2% is a sensible default
278-
timeout_seconds: Some(60), // default to 60 seconds timeout
304+
timeout_seconds: Some(60), // default to 60 seconds timeout
279305
amount_msats: None,
280306

281307
max_parts: None,

0 commit comments

Comments
 (0)