1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 06:35:11 +01:00

(gui) User can define a preferred display unit

On startup FxApp reads the configuration from `eclair.conf` and sets
a unit to which all the amounts in the UI must be converted.

This unit is a CoinUnit object. Only `sat`, `mbtc` and `btc` are accepted.
`msat` is not accepted because it's an internal accounting unit which
should be invisible to the user. Default unit is `btc`

The gui does not expose any ui feature to update this unit at runtime.
This commit is contained in:
Dominique 2018-01-22 03:28:45 +01:00 committed by dpad85
parent 7d4edceecf
commit c248c2cdc3
13 changed files with 218 additions and 78 deletions

View file

@ -32,7 +32,7 @@
</HBox>
<ProgressBar fx:id="balanceBar" minHeight="4.0" prefHeight="4.0" maxWidth="1.7976931348623157E308"
progress="0.0" snapToPixel="false" focusTraversable="false"
progress="0.0" snapToPixel="false"
GridPane.columnSpan="4" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
<Label styleClass="text-muted" text="Funding tx id" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
@ -43,12 +43,12 @@
<TextField fx:id="nodeId" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="3"/>
<Label styleClass="text-muted" text="Your balance (milliBTC)" GridPane.rowIndex="4"/>
<Label styleClass="text-muted" text="Your balance" GridPane.rowIndex="4"/>
<TextField fx:id="amountUs" text="N/A" focusTraversable="false" editable="false"
styleClass="noteditable"
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label styleClass="text-muted" text="Capacity (milliBTC)" GridPane.rowIndex="5"/>
<Label styleClass="text-muted" text="Capacity" GridPane.rowIndex="5"/>
<TextField fx:id="capacity" text="N/A" focusTraversable="false" editable="false"
styleClass="noteditable"
GridPane.columnIndex="1" GridPane.rowIndex="5"/>

View file

@ -50,10 +50,10 @@
}
.channel .grid {
-fx-padding: 5px;
-fx-vgap: 5px;
-fx-padding: 10px;
-fx-vgap: 3px;
-fx-hgap: 5px;
-fx-font-size: 11px;
-fx-font-size: 12px;
}
.channel-separator {

View file

@ -46,7 +46,7 @@
styleClass="noteditable, description-text" text="N/A"
prefHeight="80.0" maxHeight="80.0" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Label text="Amount (msat)" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE" GridPane.rowIndex="6"/>
<Label fx:id="amountFieldLabel" text="Amount" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE" GridPane.rowIndex="6"/>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="6">
<children>
<TextField fx:id="amountField"/>

View file

@ -15,13 +15,13 @@ import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor._
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.ElectrumEvent
import fr.acinq.eclair.channel.ChannelEvent
import fr.acinq.eclair.gui.controllers.{MainController, NotificationsController}
import fr.acinq.eclair.gui.utils.{BtcUnit, CoinUnit, CoinUtils}
import fr.acinq.eclair.payment.PaymentEvent
import fr.acinq.eclair.router.NetworkEvent
import grizzled.slf4j.Logging
import scala.concurrent.Promise
import scala.util.{Failure, Success}
import scala.util.{Failure, Success, Try}
import scala.concurrent.ExecutionContext.Implicits.global
@ -66,6 +66,14 @@ class FxApp extends Application with Logging {
val datadir = new File(getParameters.getUnnamed.get(0))
implicit val system = ActorSystem("system")
val setup = new Setup(datadir)
val unitConf = setup.config.getString("gui.unit")
FxApp.unit = Try(CoinUtils.getUnitFromString(unitConf, accept_msat = false)) match {
case Failure(_) => logger.warn(s"$unitConf is not a valid gui unit, must be sat, mbtc or btc. Defaulting to btc.")
BtcUnit
case Success(u) => u
}
val guiUpdater = setup.system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller), "gui-updater", SupervisorStrategy.Resume))
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
@ -135,4 +143,9 @@ class FxApp extends Application with Logging {
}
})
}
}
object FxApp {
private var unit: CoinUnit = BtcUnit
def getUnit = FxApp.unit
}

View file

@ -52,8 +52,8 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
def updateBalance(channelPaneController: ChannelPaneController, commitments: Commitments) = {
val spec = commitments.localCommit.spec
channelPaneController.capacity.setText(s"${CoinUtils.MILLI_BTC_FORMAT.format(millisatoshi2millibtc(MilliSatoshi(spec.totalFunds)).amount)}")
channelPaneController.amountUs.setText(s"${CoinUtils.MILLI_BTC_FORMAT.format(millisatoshi2millibtc(MilliSatoshi(spec.toLocalMsat)).amount)}")
channelPaneController.capacity.setText(CoinUtils.formatAmountInUserUnit(MilliSatoshi(spec.totalFunds), withUnit = true))
channelPaneController.amountUs.setText(CoinUtils.formatAmountInUserUnit(MilliSatoshi(spec.toLocalMsat), withUnit = true))
channelPaneController.balanceBar.setProgress(spec.toLocalMsat.toDouble / spec.totalFunds)
}

View file

@ -1,14 +1,13 @@
package fr.acinq.eclair.gui
import java.io.{File, FileWriter}
import java.text.NumberFormat
import java.util.Locale
import akka.pattern.ask
import akka.util.Timeout
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair._
import fr.acinq.eclair.gui.controllers._
import fr.acinq.eclair.gui.utils.CoinUtils
import fr.acinq.eclair.io.{NodeURI, Peer}
import fr.acinq.eclair.payment._
import grizzled.slf4j.Logging
@ -71,7 +70,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
} yield res)
.onComplete {
case Success(_: PaymentSucceeded) =>
val message = s"${NumberFormat.getInstance(Locale.getDefault).format(amountMsat / 1000)} satoshis"
val message = CoinUtils.formatAmountInUserUnit(MilliSatoshi(amountMsat), withUnit = true)
notification("Payment Sent", message, NOTIFICATION_SUCCESS)
case Success(PaymentFailed(_, failures)) =>
val message = s"${
@ -90,7 +89,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
def receive(amountMsat_opt: Option[MilliSatoshi], description: String): Future[String] = for {
kit <- fKit
res <- (kit.paymentHandler ? ReceivePayment(amountMsat_opt, description)).mapTo[PaymentRequest].map(PaymentRequest.write(_))
res <- (kit.paymentHandler ? ReceivePayment(amountMsat_opt, description)).mapTo[PaymentRequest].map(PaymentRequest.write)
} yield res
@ -118,6 +117,6 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
* @param showAppName true if you want the notification title to be preceded by "Eclair - ". True by default
*/
def notification(title: String, message: String, notificationType: NotificationType = NOTIFICATION_NONE, showAppName: Boolean = true) = {
notifsController.map(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
notifsController.foreach(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
}
}

View file

@ -28,13 +28,13 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
var contextMenu: ContextMenu = _
private def buildChannelContextMenu = {
private def buildChannelContextMenu() = {
Platform.runLater(new Runnable() {
override def run = {
override def run() = {
contextMenu = ContextMenuUtils.buildCopyContext(List(
new CopyAction("Copy Channel Id", channelId.getText),
new CopyAction("Copy Peer Pubkey", theirNodeIdValue),
new CopyAction("Copy Tx Id", txId.getText())
CopyAction("Copy Channel Id", channelId.getText),
CopyAction("Copy Peer Pubkey", theirNodeIdValue),
CopyAction("Copy Tx Id", txId.getText())
))
contextMenu.getStyleClass.add("context-channel")
channelId.setContextMenu(contextMenu)
@ -47,20 +47,20 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
})
}
@FXML def initialize = {
@FXML def initialize() = {
channelId.textProperty.addListener(new ChangeListener[String] {
override def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String) = buildChannelContextMenu
override def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String) = buildChannelContextMenu()
})
buildChannelContextMenu
buildChannelContextMenu()
}
@FXML def openChannelContext(event: ContextMenuEvent) {
contextMenu.show(channelId, event.getScreenX, event.getScreenY)
event.consume
event.consume()
}
@FXML def closeChannelContext(event: MouseEvent) {
if (contextMenu != null) contextMenu.hide
if (contextMenu != null) contextMenu.hide()
}
def updateRemoteNodeAlias(alias: String) {

View file

@ -2,14 +2,13 @@ package fr.acinq.eclair.gui.controllers
import java.lang.Boolean
import javafx.beans.value.{ChangeListener, ObservableValue}
import javafx.collections.FXCollections
import javafx.event.ActionEvent
import javafx.fxml.FXML
import javafx.scene.control._
import javafx.stage.Stage
import fr.acinq.eclair.channel.{Channel, ChannelFlags}
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.{FxApp, Handlers}
import fr.acinq.eclair.gui.utils.{CoinUtils, GUIValidators}
import fr.acinq.eclair.io.{NodeURI, Peer}
import grizzled.slf4j.Logging
@ -33,8 +32,8 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Lo
@FXML var button: Button = _
@FXML def initialize = {
unit.setItems(FXCollections.observableArrayList(CoinUtils.SATOSHI_LABEL, CoinUtils.MILLI_BTC_LABEL, CoinUtils.BTC_LABEL))
unit.setValue(CoinUtils.MILLI_BTC_LABEL)
unit.setItems(CoinUtils.FX_UNITS_ARRAY)
unit.setValue(FxApp.getUnit.label)
simpleConnection.selectedProperty.addListener(new ChangeListener[Boolean] {
override def changed(observable: ObservableValue[_ <: Boolean], oldValue: Boolean, newValue: Boolean) = {
@ -96,5 +95,5 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Lo
pushMsatError.setText("")
}
@FXML def handleClose(event: ActionEvent) = stage.close
@FXML def handleClose(event: ActionEvent) = stage.close()
}

View file

@ -1,7 +1,6 @@
package fr.acinq.eclair.gui.controllers
import javafx.application.Platform
import javafx.collections.FXCollections
import javafx.event.ActionEvent
import javafx.fxml.FXML
import javafx.scene.control._
@ -10,8 +9,8 @@ import javafx.scene.layout.GridPane
import javafx.stage.Stage
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.utils.{CoinUtils, ContextMenuUtils, GUIValidators, QRCodeUtils}
import fr.acinq.eclair.gui.{FxApp, Handlers}
import fr.acinq.eclair.gui.utils._
import fr.acinq.eclair.payment.PaymentRequest
import grizzled.slf4j.Logging
@ -35,8 +34,8 @@ class ReceivePaymentController(val handlers: Handlers, val stage: Stage) extends
@FXML var paymentRequestQRCode: ImageView = _
@FXML def initialize = {
unit.setItems(FXCollections.observableArrayList(CoinUtils.MILLI_SATOSHI_LABEL, CoinUtils.SATOSHI_LABEL, CoinUtils.MILLI_BTC_LABEL))
unit.setValue(CoinUtils.MILLI_BTC_LABEL)
unit.setItems(CoinUtils.FX_UNITS_ARRAY)
unit.setValue(FxApp.getUnit.label)
resultBox.managedProperty().bind(resultBox.visibleProperty())
stage.sizeToScene()
}

View file

@ -8,7 +8,8 @@ import javafx.scene.input.KeyCode.{ENTER, TAB}
import javafx.scene.input.KeyEvent
import javafx.stage.Stage
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.{FxApp, Handlers}
import fr.acinq.eclair.gui.utils.CoinUtils
import fr.acinq.eclair.payment.PaymentRequest
import grizzled.slf4j.Logging
@ -25,22 +26,27 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
@FXML var nodeIdField: TextField = _
@FXML var descriptionLabel: Label = _
@FXML var descriptionField: TextArea = _
@FXML var amountFieldLabel: Label = _
@FXML var amountField: TextField = _
@FXML var amountFieldError: Label = _
@FXML var paymentHashField: TextField = _
@FXML var sendButton: Button = _
@FXML def initialize(): Unit = {
// set the user preferred unit
amountFieldLabel.setText(s"Amount (${FxApp.getUnit.label})")
// ENTER or TAB events in the paymentRequest textarea instead fire or focus sendButton
paymentRequest.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler[KeyEvent] {
def handle(event: KeyEvent) = {
event.getCode match {
case ENTER =>
sendButton.fire
event.consume
sendButton.fire()
event.consume()
case TAB =>
sendButton.requestFocus()
event.consume
event.consume()
case _ =>
}
}
@ -75,7 +81,7 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
}
private def setUIFields(pr: PaymentRequest) = {
pr.amount.foreach(amount => amountField.setText(amount.amount.toString))
pr.amount.foreach(amount => amountField.setText(CoinUtils.rawAmountInUserUnit(amount).bigDecimal.toPlainString))
pr.description match {
case Left(s) => descriptionField.setText(s)
case Right(hash) =>
@ -87,10 +93,10 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Lo
}
@FXML def handleSend(event: ActionEvent): Unit = {
(Try(amountField.getText().toLong), readPaymentRequest()) match {
(Try(CoinUtils.convertStringAmountToMsat(amountField.getText(), FxApp.getUnit.code)), readPaymentRequest()) match {
case (Success(amountMsat), Success(pr)) =>
// we always override the payment request amount with the one from the UI
Try(handlers.send(Some(amountMsat), pr)) match {
Try(handlers.send(Some(amountMsat.amount), pr)) match {
case Success(_) => stage.close()
case Failure(f) => paymentRequestError.setText(s"Invalid Payment Request: ${f.getMessage}")
}

View file

@ -1,26 +1,58 @@
package fr.acinq.eclair.gui.utils
import java.text.DecimalFormat
import java.text.{DecimalFormat, NumberFormat}
import javafx.collections.FXCollections
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi, _}
import fr.acinq.eclair.gui.FxApp
import grizzled.slf4j.Logging
import fr.acinq.bitcoin._
import scala.util.{Failure, Success, Try}
sealed trait CoinUnit {
def code: String
def shortLabel: String
def label: String
}
case object MSatUnit extends CoinUnit {
override def code: String = "msat"
override def shortLabel: String = "mSat"
override def label: String = "MilliSatoshi"
}
case object SatUnit extends CoinUnit {
override def code: String = "sat"
override def shortLabel: String = "sat"
override def label: String = "Satoshi"
}
case object BitsUnit extends CoinUnit {
override def code: String = "bits"
override def shortLabel: String = "bits"
override def label: String = "Bits"
}
case object MBtcUnit extends CoinUnit {
override def code: String = "mbtc"
override def shortLabel: String = "mBTC"
override def label: String = "MilliBitcoin"
}
case object BtcUnit extends CoinUnit {
override def code: String = "btc"
override def shortLabel: String = "BTC"
override def label: String = "Bitcoin"
}
object CoinUtils extends Logging {
/**
* Always 5 decimals
*/
val MILLI_BTC_PATTERN = "###,##0.00000"
/**
* Localized formatter for milli-bitcoin amounts. Uses `MILLI_BTC_PATTERN`.
*/
val MILLI_BTC_PATTERN = "###,##0.00######"
val BTC_PATTERN = "###,##0.000########"
val SATOSHI_FORMAT = NumberFormat.getInstance()
val MILLI_BTC_FORMAT: DecimalFormat = new DecimalFormat(MILLI_BTC_PATTERN)
val BTC_FORMAT: DecimalFormat = new DecimalFormat(BTC_PATTERN)
val MILLI_SATOSHI_LABEL = "milliSatoshi"
val SATOSHI_LABEL = "satoshi"
val MILLI_BTC_LABEL = "milliBTC"
val BTC_LABEL = "BTC"
val FX_UNITS_ARRAY = FXCollections.observableArrayList(SatUnit.label, MBtcUnit.label, BtcUnit.label)
/**
* Converts a string amount denominated in a bitcoin unit to a Millisatoshi amount. The amount might be truncated if
@ -41,15 +73,106 @@ object CoinUtils extends Logging {
}
logger.debug(s"amount=$amountDecimal with unit=$unit")
// note: we can't use the fr.acinq.bitcoin._ conversion methods because they truncate the sub-satoshi part
unit match {
case MILLI_SATOSHI_LABEL => MilliSatoshi(amountDecimal.longValue())
case SATOSHI_LABEL => MilliSatoshi((amountDecimal * 1000).longValue())
case MILLI_BTC_LABEL => MilliSatoshi((amountDecimal * 1000 * 100000).longValue())
case BTC_LABEL => MilliSatoshi((amountDecimal * 1000 * 100000 * 1000).longValue())
case _ => throw new IllegalArgumentException("unknown unit")
getUnitFromString(unit) match {
case MSatUnit => MilliSatoshi(amountDecimal.longValue())
case SatUnit => MilliSatoshi((amountDecimal * 1000).longValue())
case MBtcUnit => MilliSatoshi((amountDecimal * 1000 * 100000).longValue())
case BtcUnit => MilliSatoshi((amountDecimal * 1000 * 100000 * 1000).longValue())
case _ => throw new IllegalArgumentException("unhandled unit")
}
}
def convertStringAmountToSat(amount: String, unit: String): Satoshi =
millisatoshi2satoshi(CoinUtils.convertStringAmountToMsat(amount, unit))
/**
* Only BtcUnit, MBtcUnit and SatUnit are supported.
* @param unit
* @return
*/
def getUnitFromString(unit: String, accept_msat: Boolean = true): CoinUnit = unit.toLowerCase() match {
case u if accept_msat && (u == MSatUnit.code || u == MSatUnit.label.toLowerCase()) => MSatUnit
case u if u == SatUnit.code || u == SatUnit.label.toLowerCase() => SatUnit
case u if u == MBtcUnit.code || u == MBtcUnit.label.toLowerCase() => MBtcUnit
case u if u == BtcUnit.code.toLowerCase() || u == BtcUnit.label => BtcUnit
case u => throw new IllegalArgumentException(s"unhandled unit=$u")
}
def convertAmountToUserUnit(amount: BtcAmount): BtcAmount = {
val unit = FxApp.getUnit
(amount, unit) match {
// amount is msat
case (a: MilliSatoshi, MSatUnit) => a
case (a: MilliSatoshi, SatUnit) => millisatoshi2satoshi(a)
case (a: MilliSatoshi, MBtcUnit) => millisatoshi2millibtc(a)
case (a: MilliSatoshi, BtcUnit) => millisatoshi2btc(a)
// amount is satoshi
case (a: Satoshi, MSatUnit) => satoshi2millisatoshi(a)
case (a: Satoshi, SatUnit) => a
case (a: Satoshi, MBtcUnit) => satoshi2millibtc(a)
case (a: Satoshi, BtcUnit) => satoshi2btc(a)
// amount is mbtc
case (a: MilliBtc, MSatUnit) => millibtc2millisatoshi(a)
case (a: MilliBtc, SatUnit) => millibtc2satoshi(a)
case (a: MilliBtc, MBtcUnit) => a
case (a: MilliBtc, BtcUnit) => millibtc2btc(a)
// amount is mbtc
case (a: Btc, MSatUnit) => btc2millisatoshi(a)
case (a: Btc, SatUnit) => btc2satoshi(a)
case (a: Btc, MBtcUnit) => btc2millibtc(a)
case (a: Btc, BtcUnit) => a
case (a, _) =>
throw new IllegalArgumentException(s"unhandled conversion from $amount to $unit")
}
}
/**
* Converts the amount to the user preferred unit and returns a localized formatted String.
* This method is useful for read only displays.
*
* @param amount BtcAmount
* @param withUnit if true, append the user unit shortLabel (mBTC, BTC, mSat...)
* @return formatted amount
*/
def formatAmountInUserUnit(amount: BtcAmount, withUnit: Boolean = false): String = {
Try(convertAmountToUserUnit(amount) match {
case a: MilliSatoshi => SATOSHI_FORMAT.format(a.amount)
case a: Satoshi => SATOSHI_FORMAT.format(a.amount)
case a: MilliBtc => MILLI_BTC_FORMAT.format(a.amount)
case a: Btc => BTC_FORMAT.format(a.amount)
case a => throw new IllegalArgumentException(s"unhandled unit $a")
}) match {
case Success(s) => if (withUnit) s"$s ${FxApp.getUnit.shortLabel}" else s
case Failure(t) => logger.error("can not convert amount to user unit", t)
amount.toString
}
}
/**
* Converts the amount to the user preferred unit and returns the Long value.
* This method is useful to feed numeric text input without formatting.
*
* Returns -1 if the given amount can not be converted.
*
* @param amount BtcAmount
* @return Long value of the BtcAmount
*/
def rawAmountInUserUnit(amount: BtcAmount): BigDecimal = {
Try(convertAmountToUserUnit(amount) match {
case a: MilliSatoshi => BigDecimal(a.amount)
case a: Satoshi => BigDecimal(a.amount)
case a: MilliBtc => a.amount
case a: Btc => a.amount
case a => throw new IllegalArgumentException(s"unhandled unit $a")
}) match {
case Success(l) => l
case Failure(t) => logger.error("can not convert amount to user unit", t)
-1
}
}
}

View file

@ -9,28 +9,28 @@ import org.scalatest.junit.JUnitRunner
class CoinUtilsSpec extends FunSuite {
test("Convert string amount to the correct BtcAmount") {
val am_btc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.BTC_LABEL)
val am_btc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", BtcUnit.code)
assert(am_btc == MilliSatoshi(100000000000L))
val am_mbtc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.MILLI_BTC_LABEL)
val am_mbtc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MBtcUnit.code)
assert(am_mbtc == MilliSatoshi(100000000L))
val am_sat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.SATOSHI_LABEL)
val am_sat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", SatUnit.code)
assert(am_sat == MilliSatoshi(1000))
val am_msat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", CoinUtils.MILLI_SATOSHI_LABEL)
val am_msat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MSatUnit.code)
assert(am_msat == MilliSatoshi(1))
val am_zero: MilliSatoshi = CoinUtils.convertStringAmountToMsat("0", CoinUtils.MILLI_BTC_LABEL)
val am_zero: MilliSatoshi = CoinUtils.convertStringAmountToMsat("0", MBtcUnit.code)
assert(am_zero == MilliSatoshi(0))
}
test("Convert decimal string amount to the correct BtcAmount") {
val am_btc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789876", CoinUtils.BTC_LABEL)
val am_btc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789876", BtcUnit.code)
assert(am_btc_dec == MilliSatoshi(123456789876L))
val am_mbtc_dec_nozero: MilliSatoshi = CoinUtils.convertStringAmountToMsat(".25", CoinUtils.MILLI_BTC_LABEL)
val am_mbtc_dec_nozero: MilliSatoshi = CoinUtils.convertStringAmountToMsat(".25", MBtcUnit.code)
assert(am_mbtc_dec_nozero == MilliSatoshi(25000000L))
val am_mbtc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", CoinUtils.MILLI_BTC_LABEL)
val am_mbtc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", MBtcUnit.code)
assert(am_mbtc_dec == MilliSatoshi(123456789L))
val am_sat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", CoinUtils.SATOSHI_LABEL)
val am_sat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", SatUnit.code)
assert(am_sat_dec == MilliSatoshi(1234))
val am_msat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.234", CoinUtils.MILLI_SATOSHI_LABEL)
val am_msat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.234", MSatUnit.code)
assert(am_msat_dec == MilliSatoshi(1))
}
@ -43,20 +43,21 @@ class CoinUtilsSpec extends FunSuite {
}
test("Convert string amount with a non numerical amount") {
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.abcd", CoinUtils.MILLI_BTC_LABEL))
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.abcd", MBtcUnit.code))
}
test("Convert string amount with an empty amount") {
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("", CoinUtils.MILLI_BTC_LABEL))
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("", MBtcUnit.code))
}
test("Convert string amount with a invalid numerical amount") {
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.23.45", CoinUtils.MILLI_BTC_LABEL))
intercept[NumberFormatException](CoinUtils.convertStringAmountToMsat("1.23.45", MBtcUnit.code))
}
test("Convert string amount with a negative numerical amount") {
intercept[IllegalArgumentException](CoinUtils.convertStringAmountToMsat("-1", CoinUtils.MILLI_BTC_LABEL))
intercept[IllegalArgumentException](CoinUtils.convertStringAmountToMsat("-1", MBtcUnit.code))
}
}

View file

@ -81,7 +81,7 @@
<appender-ref ref="CYAN"/>
</logger>
<logger name="fr.acinq.eclair.gui" level="ERROR" additivity="false">
<logger name="fr.acinq.eclair.gui" level="WARN" additivity="false">
<appender-ref ref="MAGENTA"/>
</logger>