Skip to content

Commit

Permalink
Merge branch 'lightning-fiat' into staging-ln
Browse files Browse the repository at this point in the history
  • Loading branch information
Beerosagos committed Feb 27, 2024
2 parents 2d7a237 + 40976c9 commit 83836fe
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 130 deletions.
8 changes: 8 additions & 0 deletions backend/accounts/balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ type Balance struct {
incoming coin.Amount
}

// FormattedAccountBalance includes the total and incoming balance of an account.
type FormattedAccountBalance struct {
HasAvailable bool `json:"hasAvailable"`
Available coin.FormattedAmount `json:"available"`
HasIncoming bool `json:"hasIncoming"`
Incoming coin.FormattedAmount `json:"incoming"`
}

// NewBalance creates a new balance with the given amounts.
func NewBalance(available coin.Amount, incoming coin.Amount) *Balance {
return &Balance{
Expand Down
11 changes: 10 additions & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,17 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe

backend.banners = banners.NewBanners()
backend.banners.Observe(backend.Notify)
btcCoin, err := backend.Coin(coin.CodeBTC)
if err != nil {
return nil, err
}

backend.lightning = lightning.NewLightning(backend.config,
backend.arguments.CacheDirectoryPath(),
backend.Keystore, backend.httpClient,
backend.ratesUpdater,
btcCoin)

backend.lightning = lightning.NewLightning(backend.config, backend.arguments.CacheDirectoryPath(), backend.Keystore, backend.httpClient)
backend.lightning.Observe(backend.Notify)

return backend, nil
Expand Down
63 changes: 28 additions & 35 deletions backend/coins/btc/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,9 @@ func (handlers *Handlers) Uninit() {
handlers.account = nil
}

// FormattedAmount with unit and conversions.
type FormattedAmount struct {
Amount string `json:"amount"`
Unit string `json:"unit"`
Conversions map[string]string `json:"conversions"`
}

func (handlers *Handlers) formatAmountAsJSON(amount coin.Amount, isFee bool) FormattedAmount {
func (handlers *Handlers) formatAmountAsJSON(amount coin.Amount, isFee bool) coin.FormattedAmount {
accountCoin := handlers.account.Coin()
return FormattedAmount{
return coin.FormattedAmount{
Amount: accountCoin.FormatAmount(amount, isFee),
Unit: accountCoin.GetFormatUnit(isFee),
Conversions: coin.Conversions(
Expand All @@ -114,9 +107,9 @@ func (handlers *Handlers) formatAmountAsJSON(amount coin.Amount, isFee bool) For
}
}

func (handlers *Handlers) formatAmountAtTimeAsJSON(amount coin.Amount, timeStamp *time.Time) *FormattedAmount {
func (handlers *Handlers) formatAmountAtTimeAsJSON(amount coin.Amount, timeStamp *time.Time) *coin.FormattedAmount {
accountCoin := handlers.account.Coin()
return &FormattedAmount{
return &coin.FormattedAmount{
Amount: accountCoin.FormatAmount(amount, false),
Unit: accountCoin.GetFormatUnit(false),
Conversions: coin.ConversionsAtTime(
Expand All @@ -130,30 +123,30 @@ func (handlers *Handlers) formatAmountAtTimeAsJSON(amount coin.Amount, timeStamp
}
}

func (handlers *Handlers) formatBTCAmountAsJSON(amount btcutil.Amount, isFee bool) FormattedAmount {
func (handlers *Handlers) formatBTCAmountAsJSON(amount btcutil.Amount, isFee bool) coin.FormattedAmount {
return handlers.formatAmountAsJSON(coin.NewAmountFromInt64(int64(amount)), isFee)
}

// Transaction is the info returned per transaction by the /transactions and /transaction endpoint.
type Transaction struct {
TxID string `json:"txID"`
InternalID string `json:"internalID"`
NumConfirmations int `json:"numConfirmations"`
NumConfirmationsComplete int `json:"numConfirmationsComplete"`
Type string `json:"type"`
Status accounts.TxStatus `json:"status"`
Amount FormattedAmount `json:"amount"`
AmountAtTime *FormattedAmount `json:"amountAtTime"`
Fee FormattedAmount `json:"fee"`
Time *string `json:"time"`
Addresses []string `json:"addresses"`
Note string `json:"note"`
TxID string `json:"txID"`
InternalID string `json:"internalID"`
NumConfirmations int `json:"numConfirmations"`
NumConfirmationsComplete int `json:"numConfirmationsComplete"`
Type string `json:"type"`
Status accounts.TxStatus `json:"status"`
Amount coin.FormattedAmount `json:"amount"`
AmountAtTime *coin.FormattedAmount `json:"amountAtTime"`
Fee coin.FormattedAmount `json:"fee"`
Time *string `json:"time"`
Addresses []string `json:"addresses"`
Note string `json:"note"`

// BTC specific fields.
VSize int64 `json:"vsize"`
Size int64 `json:"size"`
Weight int64 `json:"weight"`
FeeRatePerKb FormattedAmount `json:"feeRatePerKb"`
VSize int64 `json:"vsize"`
Size int64 `json:"size"`
Weight int64 `json:"weight"`
FeeRatePerKb coin.FormattedAmount `json:"feeRatePerKb"`

// ETH specific fields
Gas uint64 `json:"gas"`
Expand All @@ -172,12 +165,12 @@ func (handlers *Handlers) ensureAccountInitialized(h func(*http.Request) (interf
// getTxInfoJSON encodes a given transaction in JSON.
// If `detail` is false, Coin related details, fees and historical fiat amount won't be included.
func (handlers *Handlers) getTxInfoJSON(txInfo *accounts.TransactionData, detail bool) Transaction {
var feeString FormattedAmount
var feeString coin.FormattedAmount
if txInfo.Fee != nil {
feeString = handlers.formatAmountAsJSON(*txInfo.Fee, true)
}
var formattedTime *string
var amountAtTime *FormattedAmount
var amountAtTime *coin.FormattedAmount
if txInfo.Timestamp != nil {
t := txInfo.Timestamp.Format(time.RFC3339)
formattedTime = &t
Expand Down Expand Up @@ -344,11 +337,11 @@ func (handlers *Handlers) getAccountBalance(*http.Request) (interface{}, error)
if err != nil {
return nil, err
}
return map[string]interface{}{
"hasAvailable": balance.Available().BigInt().Sign() > 0,
"available": handlers.formatAmountAsJSON(balance.Available(), false),
"hasIncoming": balance.Incoming().BigInt().Sign() > 0,
"incoming": handlers.formatAmountAsJSON(balance.Incoming(), false),
return accounts.FormattedAccountBalance{
HasAvailable: balance.Available().BigInt().Sign() > 0,
Available: handlers.formatAmountAsJSON(balance.Available(), false),
HasIncoming: balance.Incoming().BigInt().Sign() > 0,
Incoming: handlers.formatAmountAsJSON(balance.Incoming(), false),
}, nil
}

Expand Down
7 changes: 7 additions & 0 deletions backend/coins/coin/amount.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ type Amount struct {
n *big.Int
}

// FormattedAmount with unit and conversions.
type FormattedAmount struct {
Amount string `json:"amount"`
Unit string `json:"unit"`
Conversions map[string]string `json:"conversions"`
}

// NewAmount creates a new amount.
func NewAmount(amount *big.Int) Amount {
return Amount{n: new(big.Int).Set(amount)}
Expand Down
11 changes: 8 additions & 3 deletions backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func NewHandlers(
getAPIRouterNoError(apiRouter)("/lightning/activate-node", handlers.postLightningActivateNode).Methods("POST")
getAPIRouterNoError(apiRouter)("/lightning/deactivate-node", handlers.postLightningDeactivateNode).Methods("POST")
getAPIRouterNoError(apiRouter)("/lightning/node-info", handlers.getLightningNodeInfo).Methods("GET")
getAPIRouterNoError(apiRouter)("/lightning/balance", handlers.getLightningBalance).Methods("GET")
getAPIRouterNoError(apiRouter)("/lightning/list-payments", handlers.getLightningListPayments).Methods("GET")
getAPIRouterNoError(apiRouter)("/lightning/open-channel-fee", handlers.getLightningOpenChannelFee).Methods("GET")
getAPIRouterNoError(apiRouter)("/lightning/parse-input", handlers.getLightningParseInput).Methods("GET")
Expand Down Expand Up @@ -703,7 +704,7 @@ func (handlers *Handlers) postBtcFormatUnit(r *http.Request) interface{} {

// getAccountsBalanceHandler returns the balance of all the accounts, grouped by keystore and coin.
func (handlers *Handlers) getAccountsBalance(*http.Request) (interface{}, error) {
totalAmount := make(map[string]map[coin.Code]accountHandlers.FormattedAmount)
totalAmount := make(map[string]map[coin.Code]coin.FormattedAmount)
accountsByKeystore, err := handlers.backend.AccountsByKeystore()
if err != nil {
return nil, err
Expand Down Expand Up @@ -744,13 +745,13 @@ func (handlers *Handlers) getAccountsBalance(*http.Request) (interface{}, error)
util.FormatBtcAsSat(handlers.backend.Config().AppConfig().Backend.BtcUnit))
}

totalAmount[rootFingerprint] = make(map[coin.Code]accountHandlers.FormattedAmount)
totalAmount[rootFingerprint] = make(map[coin.Code]coin.FormattedAmount)
for k, v := range totalPerCoin {
currentCoin, err := handlers.backend.Coin(k)
if err != nil {
return nil, err
}
totalAmount[rootFingerprint][k] = accountHandlers.FormattedAmount{
totalAmount[rootFingerprint][k] = coin.FormattedAmount{
Amount: currentCoin.FormatAmount(coin.NewAmount(v), false),
Unit: currentCoin.GetFormatUnit(false),
Conversions: conversionsPerCoin[k],
Expand Down Expand Up @@ -1437,6 +1438,10 @@ func (handlers *Handlers) getLightningNodeInfo(r *http.Request) interface{} {
return handlers.backend.Lightning().GetNodeInfo(r)
}

func (handlers *Handlers) getLightningBalance(r *http.Request) interface{} {
return handlers.backend.Lightning().GetBalance(r)
}

func (handlers *Handlers) getLightningListPayments(r *http.Request) interface{} {
return handlers.backend.Lightning().GetListPayments(r)
}
Expand Down
30 changes: 29 additions & 1 deletion backend/lightning/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"net/http"

"github.com/breez/breez-sdk-go/breez_sdk"
"github.com/digitalbitbox/bitbox-wallet-app/backend/accounts"
"github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin"
)

// PostLightningActivateNode handles the POST request to activate the lightning node.
Expand All @@ -42,7 +44,7 @@ func (lightning *Lightning) PostLightningDeactivateNode(r *http.Request) interfa
return responseDto{Success: true}
}

// GetNodeInfo handles the GET request retrieve the node info.
// GetNodeInfo handles the GET request to retrieve the node info.
func (lightning *Lightning) GetNodeInfo(_ *http.Request) interface{} {
if lightning.sdkService == nil {
return responseDto{Success: false, ErrorMessage: "BreezServices not initialized"}
Expand All @@ -56,6 +58,32 @@ func (lightning *Lightning) GetNodeInfo(_ *http.Request) interface{} {
return responseDto{Success: true, Data: toNodeStateDto(nodeState)}
}

// GetBalance handles the GET request to retrieve the node balance and its fiat conversions.
func (lightning *Lightning) GetBalance(_ *http.Request) interface{} {
balance, err := lightning.Balance()
if err != nil {
return responseDto{Success: false, ErrorMessage: err.Error()}
}

formatAsSats := lightning.backendConfig.AppConfig().Backend.BtcUnit == coin.BtcUnitSats
btcCoin := lightning.btcCoin

formattedAvailableAmount := coin.FormattedAmount{
Amount: btcCoin.FormatAmount(balance.Available(), false),
Unit: btcCoin.GetFormatUnit(false),
Conversions: coin.Conversions(balance.Available(), btcCoin, false, lightning.ratesUpdater, formatAsSats),
}

return responseDto{
Success: true,
Data: accounts.FormattedAccountBalance{
HasAvailable: balance.Available().BigInt().Sign() > 0,
Available: formattedAvailableAmount,
HasIncoming: false,
Incoming: coin.FormattedAmount{},
}}
}

// GetListPayments handles the GET request to list payments.
func (lightning *Lightning) GetListPayments(r *http.Request) interface{} {
if lightning.sdkService == nil {
Expand Down
56 changes: 46 additions & 10 deletions backend/lightning/lightning.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ import (
"strings"

"github.com/breez/breez-sdk-go/breez_sdk"
"github.com/digitalbitbox/bitbox-wallet-app/backend/accounts"
"github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/types"
"github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin"
"github.com/digitalbitbox/bitbox-wallet-app/backend/config"
"github.com/digitalbitbox/bitbox-wallet-app/backend/keystore"
"github.com/digitalbitbox/bitbox-wallet-app/backend/rates"
"github.com/digitalbitbox/bitbox-wallet-app/backend/util"
"github.com/digitalbitbox/bitbox-wallet-app/util/errp"
"github.com/digitalbitbox/bitbox-wallet-app/util/logging"
Expand All @@ -45,31 +48,40 @@ const (
type Lightning struct {
observable.Implementation

config *config.Config
backendConfig *config.Config
cacheDirectoryPath string
getKeystore func() keystore.Keystore
synced bool

log *logrus.Entry
sdkService *breez_sdk.BlockingBreezServices
httpClient *http.Client
log *logrus.Entry
sdkService *breez_sdk.BlockingBreezServices
httpClient *http.Client
ratesUpdater *rates.RateUpdater
btcCoin coin.Coin
}

// NewLightning creates a new instance of the Lightning struct.
func NewLightning(config *config.Config, cacheDirectoryPath string, getKeystore func() keystore.Keystore, httpClient *http.Client) *Lightning {
func NewLightning(config *config.Config,
cacheDirectoryPath string,
getKeystore func() keystore.Keystore,
httpClient *http.Client,
ratesUpdater *rates.RateUpdater,
btcCoin coin.Coin) *Lightning {
return &Lightning{
config: config,
backendConfig: config,
cacheDirectoryPath: cacheDirectoryPath,
getKeystore: getKeystore,
log: logging.Get().WithGroup("lightning"),
synced: false,
httpClient: httpClient,
ratesUpdater: ratesUpdater,
btcCoin: btcCoin,
}
}

// Activate first creates a mnemonic from the keystore entropy then connects to instance.
func (lightning *Lightning) Activate() error {
lightningConfig := lightning.config.LightningConfig()
lightningConfig := lightning.backendConfig.LightningConfig()

if len(lightningConfig.Accounts) > 0 {
return errp.New("Lightning accounts already configured")
Expand Down Expand Up @@ -137,7 +149,7 @@ func (lightning *Lightning) Disconnect() {

// Deactivate disconnects the instance and changes the config to inactive.
func (lightning *Lightning) Deactivate() error {
lightningConfig := lightning.config.LightningConfig()
lightningConfig := lightning.backendConfig.LightningConfig()

if len(lightningConfig.Accounts) == 0 {
return nil
Expand All @@ -154,13 +166,37 @@ func (lightning *Lightning) Deactivate() error {
return nil
}

// CheckActive returns an error if the lightning service not has been activated.
func (lightning *Lightning) CheckActive() error {
lightningConfig := lightning.backendConfig.LightningConfig()
if len(lightningConfig.Accounts) == 0 || lightning.sdkService == nil {
return errp.New("Lightning not initialized")
}
return nil
}

// Balance returns the balance of the lightning account.
func (lightning *Lightning) Balance() (*accounts.Balance, error) {
if err := lightning.CheckActive(); err != nil {
return nil, err
}

nodeInfo, err := lightning.sdkService.NodeInfo()
if err != nil {
return nil, err
}

amount := coin.NewAmountFromInt64(int64(nodeInfo.ChannelsBalanceMsat / 1000))
return accounts.NewBalance(amount, coin.Amount{}), nil
}

func accountBreezFolder(accountCode types.Code) string {
return strings.Join([]string{"breez-", string(accountCode)}, "")
}

// connect initializes the connection configuration and calls connect to create a Breez SDK instance.
func (lightning *Lightning) connect(registerNode bool) error {
lightningConfig := lightning.config.LightningConfig()
lightningConfig := lightning.backendConfig.LightningConfig()

if len(lightningConfig.Accounts) > 0 && lightning.sdkService == nil {
initializeLogging(lightning.log)
Expand Down Expand Up @@ -232,7 +268,7 @@ func (lightning *Lightning) connect(registerNode bool) error {
}

func (lightning *Lightning) setLightningConfig(config config.LightningConfig) error {
if err := lightning.config.SetLightningConfig(config); err != nil {
if err := lightning.backendConfig.SetLightningConfig(config); err != nil {
lightning.log.WithError(err).Warn("Error updating lightning config")
return errp.New("Error updating lightning config")
}
Expand Down
6 changes: 5 additions & 1 deletion frontends/web/src/api/lightning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { apiGet, apiPost } from '../utils/request';
import { AccountCode } from './account';
import { AccountCode, IBalance } from './account';
import { TSubscriptionCallback, subscribeEndpoint } from './subscribe';
import qs from 'query-string';

Expand Down Expand Up @@ -902,6 +902,10 @@ export const postDeactivateNode = async (): Promise<void> => {
return postApiResponse<void, undefined>('lightning/deactivate-node', undefined, 'Error calling postDeactivateNode');
};

export const getLightningBalance = async (): Promise<IBalance> => {
return getApiResponse<IBalance>('lightning/balance', 'Error calling getLightningBalance');
};

/**
* Breez SDK API interface
*/
Expand Down
Loading

0 comments on commit 83836fe

Please sign in to comment.