Skip to content

Commit

Permalink
Merge pull request #2342 from get10101/feat/web-open-channel-dialog
Browse files Browse the repository at this point in the history
feat(webapp): Create DLC Channel Dialog
  • Loading branch information
bonomat authored Apr 1, 2024
2 parents 3d1d621 + 6bb4684 commit 0fe4f52
Show file tree
Hide file tree
Showing 50 changed files with 915 additions and 185 deletions.
25 changes: 1 addition & 24 deletions mobile/native/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::calculations;
use crate::channel_trade_constraints;
use crate::channel_trade_constraints::TradeConstraints;
use crate::commons::api::Price;
use crate::config;
use crate::config::api::Config;
Expand Down Expand Up @@ -546,30 +547,6 @@ pub async fn force_close_channel() -> Result<()> {
ln_dlc::close_channel(true).await
}

pub struct TradeConstraints {
/// Max margin the local party can use
///
/// This depends on whether the user has a channel or not. If he has a channel, then his
/// channel balance is the max amount, otherwise his on-chain balance dictates the max amount
pub max_local_margin_sats: u64,
/// Max amount the counterparty is willing to put.
///
/// This depends whether the user has a channel or not, i.e. if he has a channel then the max
/// amount is what the counterparty has in the channel, otherwise, it's a fixed amount what
/// the counterparty is willing to provide.
pub max_counterparty_margin_sats: u64,
/// The leverage the coordinator will take
pub coordinator_leverage: f32,
/// Smallest allowed amount of contracts
pub min_quantity: u64,
/// If true it means that the user has a channel and hence the max amount is limited by what he
/// has in the channel. In the future we can consider splice in and allow the user to use more
/// than just his channel balance.
pub is_channel_balance: bool,
/// Smallest allowed margin
pub min_margin: u64,
}

pub fn channel_trade_constraints() -> Result<SyncReturn<TradeConstraints>> {
let trade_constraints = channel_trade_constraints::channel_trade_constraints()?;
Ok(SyncReturn(trade_constraints))
Expand Down
25 changes: 24 additions & 1 deletion mobile/native/src/channel_trade_constraints.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
use crate::api::TradeConstraints;
use crate::ln_dlc;
use anyhow::Context;
use anyhow::Result;

pub struct TradeConstraints {
/// Max margin the local party can use
///
/// This depends on whether the user has a channel or not. If he has a channel, then his
/// channel balance is the max amount, otherwise his on-chain balance dictates the max amount
pub max_local_margin_sats: u64,
/// Max amount the counterparty is willing to put.
///
/// This depends whether the user has a channel or not, i.e. if he has a channel then the max
/// amount is what the counterparty has in the channel, otherwise, it's a fixed amount what
/// the counterparty is willing to provide.
pub max_counterparty_margin_sats: u64,
/// The leverage the coordinator will take
pub coordinator_leverage: f32,
/// Smallest allowed amount of contracts
pub min_quantity: u64,
/// If true it means that the user has a channel and hence the max amount is limited by what he
/// has in the channel. In the future we can consider splice in and allow the user to use more
/// than just his channel balance.
pub is_channel_balance: bool,
/// Smallest allowed margin
pub min_margin: u64,
}

pub fn channel_trade_constraints() -> Result<TradeConstraints> {
let lsp_config =
crate::state::try_get_lsp_config().context("We can't trade without LSP config")?;
Expand Down
2 changes: 1 addition & 1 deletion mobile/native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub mod schema;
pub mod state;

mod backup;
mod channel_trade_constraints;
pub mod channel_trade_constraints;
mod cipher;
mod destination;
mod dlc_channel;
Expand Down
4 changes: 2 additions & 2 deletions webapp/frontend/lib/auth/login_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get_10101/auth/auth_service.dart';
import 'package:get_10101/services/auth_service.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/text_input_field.dart';
import 'package:get_10101/common/version_service.dart';
import 'package:get_10101/services/version_service.dart';
import 'package:get_10101/trade/trade_screen.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/settings/channel_service.dart';
import 'package:get_10101/services/channel_service.dart';
import 'package:get_10101/settings/dlc_channel.dart';

class ChannelChangeNotifier extends ChangeNotifier {
Expand Down Expand Up @@ -41,6 +41,21 @@ class ChannelChangeNotifier extends ChangeNotifier {

List<DlcChannel>? getChannels() => _channels;

DlcChannel? getOpenChannel() {
if (_channels == null) return null;
try {
return _channels!.firstWhere(
(channel) =>
(channel.signedChannelState == SignedChannelState.established ||
channel.signedChannelState == SignedChannelState.settled) &&
channel.channelState == ChannelState.signed,
);
} catch (e) {
// If no element satisfies the condition, we return null
return null;
}
}

@override
void dispose() {
super.dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/order_service.dart';
import 'package:get_10101/services/order_service.dart';

class OrderChangeNotifier extends ChangeNotifier {
final OrderService service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/position_service.dart';
import 'package:get_10101/services/position_service.dart';

class PositionChangeNotifier extends ChangeNotifier {
final PositionService service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:async';

import 'package:flutter/material.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/quote_service.dart';
import 'package:get_10101/services/quote_service.dart';

class QuoteChangeNotifier extends ChangeNotifier {
final QuoteService service;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/services/trade_constraints_service.dart';

class TradeConstraintsChangeNotifier extends ChangeNotifier {
final TradeConstraintsService service;

TradeConstraints? _tradeConstraints;

TradeConstraintsChangeNotifier(this.service) {
_refresh();
}

void _refresh() async {
try {
final tradeConstraints = await service.getTradeConstraints();
_tradeConstraints = tradeConstraints;
super.notifyListeners();
} catch (error) {
logger.e(error);
}
}

TradeConstraints? get tradeConstraints => _tradeConstraints;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:get_10101/common/balance.dart';
import 'package:get_10101/common/payment.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/wallet/wallet_service.dart';
import 'package:get_10101/services/wallet_service.dart';

class WalletChangeNotifier extends ChangeNotifier {
final WalletService service;
Expand Down
39 changes: 39 additions & 0 deletions webapp/frontend/lib/common/amount_text_field.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/common/model.dart';

class AmountTextField extends StatefulWidget {
const AmountTextField(
{super.key, required this.label, required this.value, this.suffixIcon, this.error});

final Amount value;
final String label;
final Widget? suffixIcon;
final String? error;

@override
State<AmountTextField> createState() => _AmountTextState();
}

class _AmountTextState extends State<AmountTextField> {
@override
Widget build(BuildContext context) {
String value = widget.value.formatted();

return InputDecorator(
decoration: InputDecoration(
contentPadding: const EdgeInsets.fromLTRB(12, 24, 12, 17),
border: const OutlineInputBorder(),
labelText: widget.label,
labelStyle: const TextStyle(color: Colors.black87),
errorStyle: TextStyle(
color: Colors.red[900],
),
errorText: widget.error,
filled: true,
suffixIcon: widget.suffixIcon,
fillColor: tenTenOnePurple.shade50.withOpacity(0.3)),
child: Text(value, style: const TextStyle(fontSize: 16)),
);
}
}
11 changes: 11 additions & 0 deletions webapp/frontend/lib/common/amount_text_input_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class AmountInputField extends StatelessWidget {
this.onTap,
this.textAlign = TextAlign.left,
this.suffixIcon,
this.heightPadding,
this.widthPadding,
});

final TextEditingController? controller;
Expand All @@ -38,6 +40,8 @@ class AmountInputField extends StatelessWidget {
final InputDecoration? decoration;
final TextAlign textAlign;
final Widget? suffixIcon;
final double? heightPadding;
final double? widthPadding;

final String? Function(String?)? validator;

Expand All @@ -62,6 +66,13 @@ class AmountInputField extends StatelessWidget {
color: Colors.red[900],
),
suffixIcon: isLoading ? const CircularProgressIndicator() : suffixIcon,
contentPadding: (heightPadding != null || widthPadding != null)
? EdgeInsets.only(
top: heightPadding! / 2,
bottom: heightPadding! / 2,
left: widthPadding! / 2,
right: widthPadding! / 2)
: null,
),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
Expand Down
2 changes: 1 addition & 1 deletion webapp/frontend/lib/common/balance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ class Balance {
: offChain = Amount.zero(),
onChain = Amount.zero();

Amount total() => offChain.add(onChain);
Amount total() => offChain + onChain;
}
40 changes: 40 additions & 0 deletions webapp/frontend/lib/common/calculations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:get_10101/common/model.dart';
import 'package:get_10101/services/quote_service.dart';

Amount calculateFee(Usd? quantity, BestQuote? quote, bool isLong) {
if (quote?.fee == null || quote?.fee == 0 || quantity == null) {
return Amount.zero();
}

return Amount(
(calculateMargin(quantity, quote!, Leverage.one(), isLong).sats * quote.fee!).toInt());
}

Amount calculateMargin(Usd quantity, BestQuote quote, Leverage leverage, bool isLong) {
if (isLong && quote.ask != null) {
if (quote.ask!.asDouble == 0) {
return Amount.zero();
}
return Amount.fromBtc(quantity.asDouble / (quote.ask!.asDouble * leverage.asDouble));
} else if (!isLong && quote.bid != null) {
if (quote.bid!.asDouble == 0) {
return Amount.zero();
}
return Amount.fromBtc(quantity.asDouble / (quote.bid!.asDouble * leverage.asDouble));
} else {
return Amount.zero();
}
}

Amount calculateLiquidationPrice(
Usd quantity, BestQuote quote, Leverage leverage, double maintenanceMargin, bool isLong) {
if (isLong && quote.ask != null) {
return Amount((quote.bid!.asDouble * leverage.asDouble) ~/
(leverage.asDouble + 1.0 + (maintenanceMargin * leverage.asDouble)));
} else if (!isLong && quote.bid != null) {
return Amount((quote.ask!.asDouble * leverage.asDouble) ~/
(leverage.asDouble - 1.0 + (maintenanceMargin * leverage.asDouble)));
} else {
return Amount.zero();
}
}
48 changes: 2 additions & 46 deletions webapp/frontend/lib/common/channel_state_label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,7 @@ class SignedChannelStateLabel extends StatelessWidget {
Widget build(BuildContext context) {
Widget label = _buildLabel("Unknown state", Colors.green.shade300);
if (channel != null && channel!.signedChannelState != null) {
switch (channel!.signedChannelState) {
case SignedChannelState.established:
case SignedChannelState.settled:
label = _buildLabel("Active", Colors.green.shade300);
break;
case SignedChannelState.settledOffered:
case SignedChannelState.settledReceived:
case SignedChannelState.settledAccepted:
case SignedChannelState.settledConfirmed:
case SignedChannelState.renewOffered:
case SignedChannelState.renewAccepted:
case SignedChannelState.renewConfirmed:
case SignedChannelState.renewFinalized:
label = _buildLabel("Pending", Colors.green.shade300);
break;
case SignedChannelState.closing:
case SignedChannelState.collaborativeCloseOffered:
label = _buildLabel("Closing", Colors.orange.shade300);
break;
case null:
// nothing
}
label = _buildLabel(channel!.signedChannelState!.nameU, Colors.green.shade300);
}
return label;
}
Expand All @@ -46,30 +25,7 @@ class ChannelStateLabel extends StatelessWidget {
Widget build(BuildContext context) {
Widget label = _buildLabel("Unknown state", Colors.green.shade300);
if (channel != null) {
switch (channel!.channelState) {
case ChannelState.offered:
label = _buildLabel("Offered", Colors.grey.shade300);
case ChannelState.accepted:
label = _buildLabel("Accepted", Colors.grey.shade300);
case ChannelState.signed:
label = _buildLabel("Signed", Colors.grey.shade300);
case ChannelState.closing:
label = _buildLabel("Closing", Colors.grey.shade300);
case ChannelState.closed:
label = _buildLabel("Closed", Colors.grey.shade300);
case ChannelState.counterClosed:
label = _buildLabel("Counter closed", Colors.grey.shade300);
case ChannelState.closedPunished:
label = _buildLabel("Closed punished", Colors.grey.shade300);
case ChannelState.collaborativelyClosed:
label = _buildLabel("Collaboratively closed", Colors.grey.shade300);
case ChannelState.failedAccept:
label = _buildLabel("Failed", Colors.grey.shade300);
case ChannelState.failedSign:
label = _buildLabel("Failed", Colors.grey.shade300);
case ChannelState.cancelled:
label = _buildLabel("Cancelled", Colors.grey.shade300);
}
label = _buildLabel(channel!.channelState.nameU, Colors.green.shade300);
}
return label;
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/frontend/lib/common/currency_selection_widget.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:bitcoin_icons/bitcoin_icons.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get_10101/common/currency_change_notifier.dart';
import 'package:get_10101/change_notifier/currency_change_notifier.dart';
import 'package:provider/provider.dart';

class CurrencySelectionScreen extends StatelessWidget {
Expand Down
Loading

0 comments on commit 0fe4f52

Please sign in to comment.