Skip to content

Commit

Permalink
Merge pull request #1803 from get10101/feat/show-dlc-channel-funding-…
Browse files Browse the repository at this point in the history
…in-app

feat: show dlc-channel opening tx in tx history
  • Loading branch information
bonomat authored Jan 8, 2024
2 parents 77282fa + 80966df commit e19d0c2
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Feat: update coordinator API to show more details on pending channel balance
- Feat: show dlc-channel balance instead of ln-balance in app and in coordinator's API
- Chore: don't allow multiple dlc-channels per user
- Feat: show dlc-channel opening transaction in transaction history

## [1.7.4] - 2023-12-20

Expand Down
38 changes: 38 additions & 0 deletions mobile/lib/features/wallet/domain/wallet_history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ abstract class WalletHistoryItemData {
contracts: type.contracts);
}

if (item.walletType is rust.WalletHistoryItemType_DlcChannelFunding) {
rust.WalletHistoryItemType_DlcChannelFunding type =
item.walletType as rust.WalletHistoryItemType_DlcChannelFunding;

return DlcChannelFundingData(
flow: flow,
amount: amount,
status: status,
timestamp: timestamp,
reservedFeeSats: Amount(type.reservedFeeSats ?? 0),
fundingTxid: type.fundingTxid,
confirmations: type.confirmations,
ourChannelInputAmountSats: Amount(type.ourChannelInputAmountSats),
);
}

rust.WalletHistoryItemType_Lightning type =
item.walletType as rust.WalletHistoryItemType_Lightning;

Expand Down Expand Up @@ -165,3 +181,25 @@ class TradeData extends WalletHistoryItemData {
return TradeHistoryItem(data: this);
}
}

class DlcChannelFundingData extends WalletHistoryItemData {
final Amount reservedFeeSats;
final String fundingTxid;
final int confirmations;
final Amount ourChannelInputAmountSats;

DlcChannelFundingData(
{required super.flow,
required super.amount,
required super.status,
required super.timestamp,
required this.reservedFeeSats,
required this.confirmations,
required this.ourChannelInputAmountSats,
required this.fundingTxid});

@override
WalletHistoryItem toWidget() {
return DlcChannelFundingHistoryItem(data: this);
}
}
37 changes: 37 additions & 0 deletions mobile/lib/features/wallet/wallet_history_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,40 @@ class OnChainPaymentHistoryItem extends WalletHistoryItem {
return true;
}
}

class DlcChannelFundingHistoryItem extends WalletHistoryItem {
@override
final DlcChannelFundingData data;

const DlcChannelFundingHistoryItem({super.key, required this.data});

@override
List<Widget> getDetails() {
final details = [
HistoryDetail(
label: "Transaction ID",
value: data.fundingTxid,
displayWidget: TransactionIdText(data.fundingTxid)),
HistoryDetail(label: "Confirmations", value: data.confirmations.toString()),
HistoryDetail(label: "Channel input", value: formatSats(data.ourChannelInputAmountSats)),
HistoryDetail(label: "Reserved fee", value: formatSats(data.reservedFeeSats)),
];

return details;
}

@override
IconData getFlowIcon() {
return iconForFlow(data.flow);
}

@override
String getTitle() {
return "Channel opening";
}

@override
bool isOnChain() {
return true;
}
}
9 changes: 9 additions & 0 deletions mobile/native/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ pub enum WalletHistoryItemType {
contracts: u64,
direction: String,
},
DlcChannelFunding {
funding_txid: String,
// This fee represents the total fee reserved for all off-chain transactions, i.e. for the
// fund/buffer/cet/refund. Only the part for the fund tx has been paid for now
reserved_fee_sats: Option<u64>,
confirmations: u64,
// The amount we hold in the channel
our_channel_input_amount_sats: u64,
},
}

#[derive(Clone, Debug, Default)]
Expand Down
104 changes: 82 additions & 22 deletions mobile/native/src/ln_dlc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use bdk::bitcoin::Txid;
use bdk::bitcoin::XOnlyPublicKey;
use bdk::BlockTime;
use bdk::FeeRate;
use bdk::TransactionDetails;
use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::PublicKey;
use bitcoin::secp256k1::Secp256k1;
Expand Down Expand Up @@ -112,6 +113,9 @@ const CHECK_OPEN_ORDERS_INTERVAL: Duration = Duration::from_secs(60);
const ON_CHAIN_SYNC_INTERVAL: Duration = Duration::from_secs(300);
const WAIT_FOR_CONNECTING_TO_COORDINATOR: Duration = Duration::from_secs(2);

/// Defines a constant from which we treat a transaction as confirmed
const NUMBER_OF_CONFIRMATION_FOR_BEING_CONFIRMED: u64 = 1;

/// The weight estimate of the funding transaction
///
/// This weight estimate assumes two inputs.
Expand Down Expand Up @@ -481,7 +485,53 @@ fn keep_wallet_balance_and_history_up_to_date(node: &Node) -> Result<()> {
.context("Failed to get wallet histories")?;

let blockchain_height = node.get_blockchain_height()?;
let on_chain = on_chain.iter().map(|details| {

// Get all channel related transactions (channel opening/closing). If we have seen a transaction
// in the wallet it means the transaction has been published. If so, we remove it from
// [`on_chain`] and add it as it's own WalletHistoryItem so that it can be displayed nicely.
let dlc_channels = node.inner.list_dlc_channels()?;

let dlc_channel_funding_tx_details = on_chain.iter().filter_map(|details| {
match dlc_channels
.iter()
.find(|item| item.fund_tx.txid() == details.txid)
{
None => None,
Some(channel) => {
let (timestamp, n_confirmations) =
extract_timestamp_and_blockheight(blockchain_height, details);

let status = if n_confirmations >= NUMBER_OF_CONFIRMATION_FOR_BEING_CONFIRMED {
Status::Confirmed
} else {
Status::Pending
};

Some(WalletHistoryItem {
flow: PaymentFlow::Outbound,
amount_sats: details.sent - details.received,
timestamp,
status,
wallet_type: WalletHistoryItemType::DlcChannelFunding {
funding_txid: details.txid.to_string(),
// this is not 100% correct as fees are not exactly divided by 2. The fee a
// user has to pay depends on his final address.
reserved_fee_sats: details.fee.map(|fee| fee / 2),
confirmations: n_confirmations,
our_channel_input_amount_sats: channel.own_params.collateral,
},
})
}
}
});

let on_chain = on_chain.iter().filter(|tx| {
!dlc_channels
.iter()
.any(|channel| channel.fund_tx.txid() == tx.txid)
});

let on_chain = on_chain.map(|details| {
let net_sats = details.received as i64 - details.sent as i64;

let (flow, amount_sats) = if net_sats >= 0 {
Expand All @@ -490,27 +540,10 @@ fn keep_wallet_balance_and_history_up_to_date(node: &Node) -> Result<()> {
(PaymentFlow::Outbound, net_sats.unsigned_abs())
};

let (timestamp, n_confirmations) = match details.confirmation_time {
Some(BlockTime { timestamp, height }) => (
timestamp,
// This is calculated manually to avoid wasteful requests to esplora,
// since we can just cache the blockchain height as opposed to fetching it for each
// block as with `LnDlcWallet::get_transaction_confirmations`
blockchain_height
.checked_sub(height as u64)
.unwrap_or_default(),
),

None => {
(
// Unconfirmed transactions should appear towards the top of the history
OffsetDateTime::now_utc().unix_timestamp() as u64,
0,
)
}
};
let (timestamp, n_confirmations) =
extract_timestamp_and_blockheight(blockchain_height, details);

let status = if n_confirmations >= 3 {
let status = if n_confirmations >= NUMBER_OF_CONFIRMATION_FOR_BEING_CONFIRMED {
Status::Confirmed
} else {
Status::Pending
Expand Down Expand Up @@ -632,7 +665,7 @@ fn keep_wallet_balance_and_history_up_to_date(node: &Node) -> Result<()> {
}
});

let history = chain![on_chain, off_chain, trades]
let history = chain![on_chain, off_chain, trades, dlc_channel_funding_tx_details]
.sorted_by(|a, b| b.timestamp.cmp(&a.timestamp))
.collect();

Expand All @@ -646,6 +679,33 @@ fn keep_wallet_balance_and_history_up_to_date(node: &Node) -> Result<()> {
Ok(())
}

fn extract_timestamp_and_blockheight(
blockchain_height: u64,
details: &TransactionDetails,
) -> (u64, u64) {
match details.confirmation_time {
Some(BlockTime { timestamp, height }) => (
timestamp,
// This is calculated manually to avoid wasteful requests to esplora,
// since we can just cache the blockchain height as opposed to fetching it
// for each block as with
// `LnDlcWallet::get_transaction_confirmations`
blockchain_height
.checked_sub(height as u64)
.unwrap_or_default(),
),

None => {
(
// Unconfirmed transactions should appear towards the top of the
// history
OffsetDateTime::now_utc().unix_timestamp() as u64,
0,
)
}
}
}

pub fn get_unused_address() -> String {
state::get_node().inner.get_unused_address().to_string()
}
Expand Down

0 comments on commit e19d0c2

Please sign in to comment.