mirror of
https://github.com/muun/recovery.git
synced 2025-11-12 06:50:18 -05:00
Update project structure and build process
This commit is contained in:
36
libwallet/newop/context.go
Normal file
36
libwallet/newop/context.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package newop
|
||||
|
||||
import "github.com/muun/libwallet/operation"
|
||||
|
||||
// PaymentContext stores data required to analyze and validate an operation
|
||||
type PaymentContext struct {
|
||||
FeeWindow *FeeWindow
|
||||
NextTransactionSize *NextTransactionSize
|
||||
ExchangeRateWindow *ExchangeRateWindow
|
||||
PrimaryCurrency string
|
||||
MinFeeRateInSatsPerVByte float64
|
||||
SubmarineSwap *SubmarineSwap
|
||||
}
|
||||
|
||||
func (c *PaymentContext) totalBalance() int64 {
|
||||
return c.NextTransactionSize.toInternalType().TotalBalance()
|
||||
}
|
||||
|
||||
func (c *PaymentContext) toBitcoinAmount(sats int64, inputCurrency string) *BitcoinAmount {
|
||||
amount := c.ExchangeRateWindow.convert(
|
||||
NewMonetaryAmountFromSatoshis(sats),
|
||||
inputCurrency,
|
||||
)
|
||||
return &BitcoinAmount{
|
||||
InSat: sats,
|
||||
InInputCurrency: amount,
|
||||
InPrimaryCurrency: c.ExchangeRateWindow.convert(amount, c.PrimaryCurrency),
|
||||
}
|
||||
}
|
||||
|
||||
func newPaymentAnalyzer(context *PaymentContext) *operation.PaymentAnalyzer {
|
||||
return operation.NewPaymentAnalyzer(
|
||||
context.FeeWindow.toInternalType(),
|
||||
context.NextTransactionSize.toInternalType(),
|
||||
)
|
||||
}
|
||||
65
libwallet/newop/context_test.go
Normal file
65
libwallet/newop/context_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testPaymentContext = createTestPaymentContext()
|
||||
|
||||
func createTestPaymentContext() *PaymentContext {
|
||||
var context = &PaymentContext{
|
||||
NextTransactionSize: &NextTransactionSize{
|
||||
ExpectedDebtInSat: 10_000,
|
||||
},
|
||||
ExchangeRateWindow: &ExchangeRateWindow{
|
||||
rates: make(map[string]float64),
|
||||
},
|
||||
FeeWindow: &FeeWindow{},
|
||||
PrimaryCurrency: "BTC",
|
||||
MinFeeRateInSatsPerVByte: 1.0,
|
||||
}
|
||||
context.NextTransactionSize.AddSizeForAmount(&SizeForAmount{
|
||||
AmountInSat: 100_000_000,
|
||||
SizeInVByte: 240,
|
||||
})
|
||||
|
||||
context.ExchangeRateWindow.AddRate("BTC", 1)
|
||||
context.ExchangeRateWindow.AddRate("USD", 32_000)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
func TestPaymentContextTotalBalance(t *testing.T) {
|
||||
|
||||
totalBalance := testPaymentContext.totalBalance()
|
||||
|
||||
if totalBalance != 99_990_000 {
|
||||
t.Fatalf("expected totalBalance to be 90_000, got %v", totalBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaymentContextToBitcoinAmount(t *testing.T) {
|
||||
|
||||
btcAmount := testPaymentContext.toBitcoinAmount(100_000, "USD")
|
||||
|
||||
if btcAmount.InSat != 100_000 {
|
||||
t.Fatalf("expected bitcoin amount in sats to remain unchanged and be 100_000, got %v", btcAmount.InSat)
|
||||
}
|
||||
|
||||
if btcAmount.InInputCurrency.Currency != "USD" {
|
||||
t.Fatalf("expected bitcoin amount input currency to be USD, got %v", btcAmount.InInputCurrency.Currency)
|
||||
}
|
||||
|
||||
if btcAmount.InInputCurrency.Value.Cmp(decimal.NewFromInt(32)) != 0 {
|
||||
t.Fatalf("expected converted amount to be 32, got %v", btcAmount.InInputCurrency.Value)
|
||||
}
|
||||
|
||||
if btcAmount.InPrimaryCurrency.Currency != "BTC" {
|
||||
t.Fatalf("expected bitcoin amount primary currency to be BTC, got %v", btcAmount.InPrimaryCurrency.Currency)
|
||||
}
|
||||
|
||||
if btcAmount.InPrimaryCurrency.Value.Cmp(decimal.NewFromFloat(0.001)) != 0 {
|
||||
t.Fatalf("expected amount in primary currency to be 0.001, got %v", btcAmount.InPrimaryCurrency.Value)
|
||||
}
|
||||
}
|
||||
39
libwallet/newop/exchange_rates.go
Normal file
39
libwallet/newop/exchange_rates.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"github.com/muun/libwallet"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// ExchangeRateWindow holds a map of exchange rates from BTC to every currency we handle
|
||||
type ExchangeRateWindow struct {
|
||||
WindowId int
|
||||
rates map[string]float64
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWindow) AddRate(currency string, rate float64) {
|
||||
if w.rates == nil {
|
||||
w.rates = make(map[string]float64)
|
||||
}
|
||||
w.rates[currency] = rate
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWindow) Rate(currency string) float64 {
|
||||
return w.rates[currency]
|
||||
}
|
||||
|
||||
func (s *ExchangeRateWindow) Currencies() *libwallet.StringList {
|
||||
var currencies []string
|
||||
for key := range s.rates {
|
||||
currencies = append(currencies, key)
|
||||
}
|
||||
return libwallet.NewStringListWithElements(currencies)
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWindow) convert(amount *MonetaryAmount, currency string) *MonetaryAmount {
|
||||
fromRate := decimal.NewFromFloat(w.Rate(amount.Currency))
|
||||
toRate := decimal.NewFromFloat(w.Rate(currency))
|
||||
value := amount.Value.Div(fromRate).Mul(toRate)
|
||||
|
||||
return &MonetaryAmount{Value: value, Currency: currency}
|
||||
}
|
||||
27
libwallet/newop/exchange_rates_test.go
Normal file
27
libwallet/newop/exchange_rates_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func TestExchangeWindowConvert(t *testing.T) {
|
||||
|
||||
window := &ExchangeRateWindow{}
|
||||
window.AddRate("BTC", 1)
|
||||
window.AddRate("USD", 32_000)
|
||||
|
||||
amount := NewMonetaryAmountFromSatoshis(100_000_000)
|
||||
|
||||
converted := window.convert(amount, "USD")
|
||||
|
||||
if converted.Currency != "USD" {
|
||||
t.Fatalf("expected converted currency to be USD, got %v", converted.Currency)
|
||||
}
|
||||
|
||||
if converted.Value.Cmp(decimal.NewFromInt(32_000)) != 0 {
|
||||
t.Fatalf("expected converted amount to be 32000, got %v", converted.Value)
|
||||
}
|
||||
|
||||
}
|
||||
26
libwallet/newop/fee_state.go
Normal file
26
libwallet/newop/fee_state.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package newop
|
||||
|
||||
const (
|
||||
FeeStateFinalFee string = "FinalFee"
|
||||
FeeStateNeedsChange string = "NeedsChange"
|
||||
FeeStateNoPossibleFee string = "NoPossibleFee"
|
||||
)
|
||||
|
||||
type FeeState struct {
|
||||
State string
|
||||
Amount *BitcoinAmount
|
||||
RateInSatsPerVByte float64
|
||||
TargetBlocks int64 // 0 if target not found
|
||||
}
|
||||
|
||||
func (f *FeeState) IsFinal() bool {
|
||||
return f.State == FeeStateFinalFee
|
||||
}
|
||||
|
||||
func (f *FeeState) NeedsChange() bool {
|
||||
return f.State == FeeStateNeedsChange
|
||||
}
|
||||
|
||||
func (f *FeeState) IsNoPossibleFee() bool {
|
||||
return f.State == FeeStateNoPossibleFee
|
||||
}
|
||||
35
libwallet/newop/fee_window.go
Normal file
35
libwallet/newop/fee_window.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package newop
|
||||
|
||||
import "github.com/muun/libwallet/operation"
|
||||
|
||||
// FeeWindow holds a map of target block to fee rate for a given time
|
||||
type FeeWindow struct {
|
||||
FastConfTarget int64
|
||||
MediumConfTarget int64
|
||||
SlowConfTarget int64
|
||||
TargetedFees map[uint]float64
|
||||
}
|
||||
|
||||
func (w *FeeWindow) PutTargetedFees(target int64, feeRateInSatsPerVByte float64) {
|
||||
if w.TargetedFees == nil {
|
||||
w.TargetedFees = make(map[uint]float64)
|
||||
}
|
||||
w.TargetedFees[uint(target)] = feeRateInSatsPerVByte
|
||||
}
|
||||
|
||||
func (w *FeeWindow) GetTargetedFees(target int64) float64 {
|
||||
if w.TargetedFees == nil {
|
||||
return 0
|
||||
}
|
||||
return w.TargetedFees[uint(target)]
|
||||
}
|
||||
|
||||
func (w *FeeWindow) nextHighestBlock(feeRate float64) int64 {
|
||||
return int64(w.toInternalType().NextHighestBlock(feeRate))
|
||||
}
|
||||
|
||||
func (w *FeeWindow) toInternalType() *operation.FeeWindow {
|
||||
return &operation.FeeWindow{
|
||||
TargetedFees: w.TargetedFees,
|
||||
}
|
||||
}
|
||||
86
libwallet/newop/money.go
Normal file
86
libwallet/newop/money.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// MonetaryAmount holds an amount of money in a certain currency
|
||||
type MonetaryAmount struct {
|
||||
Value decimal.Decimal
|
||||
Currency string
|
||||
}
|
||||
|
||||
func NewMonetaryAmountFromSatoshis(value int64) *MonetaryAmount {
|
||||
v := decimal.NewFromInt(value).Div(decimal.NewFromInt(100_000_000))
|
||||
return &MonetaryAmount{
|
||||
Value: v,
|
||||
Currency: "BTC",
|
||||
}
|
||||
}
|
||||
|
||||
func NewMonetaryAmountFromFiat(value string, currency string) *MonetaryAmount {
|
||||
v, err := decimal.NewFromString(value)
|
||||
if err != nil {
|
||||
log.Printf("could not initialize monetary amount: %v", err)
|
||||
return nil
|
||||
}
|
||||
return &MonetaryAmount{
|
||||
Value: v,
|
||||
Currency: currency,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonetaryAmount) ValueAsString() string {
|
||||
return m.Value.String()
|
||||
}
|
||||
|
||||
func (m *MonetaryAmount) String() string {
|
||||
return fmt.Sprintf("%v %v", m.Value, m.Currency) // TODO(newop): this is just a stub implementation
|
||||
}
|
||||
|
||||
func (m *MonetaryAmount) toBtc(window *ExchangeRateWindow) btcutil.Amount {
|
||||
rate := window.Rate(m.Currency)
|
||||
v := m.Value.Div(decimal.NewFromFloat(rate)).Mul(decimal.NewFromInt(100_000_000))
|
||||
v = v.RoundBank(0)
|
||||
return btcutil.Amount(v.IntPart())
|
||||
}
|
||||
|
||||
func (m *MonetaryAmount) add(n *MonetaryAmount) *MonetaryAmount {
|
||||
if m.Currency != n.Currency {
|
||||
panic("currencies do not match") // TODO(newop): replace panic and bubble up errors?
|
||||
}
|
||||
return &MonetaryAmount{
|
||||
Value: m.Value.Add(n.Value),
|
||||
Currency: m.Currency,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonetaryAmount) toBitcoinAmount(window *ExchangeRateWindow, primaryCurrency string) *BitcoinAmount {
|
||||
return &BitcoinAmount{
|
||||
InSat: int64(m.toBtc(window)),
|
||||
InInputCurrency: m,
|
||||
InPrimaryCurrency: window.convert(m, primaryCurrency),
|
||||
}
|
||||
}
|
||||
|
||||
type BitcoinAmount struct {
|
||||
InSat int64
|
||||
InPrimaryCurrency *MonetaryAmount
|
||||
InInputCurrency *MonetaryAmount
|
||||
}
|
||||
|
||||
func (a *BitcoinAmount) toBtc() btcutil.Amount {
|
||||
return btcutil.Amount(a.InSat)
|
||||
}
|
||||
|
||||
func (a *BitcoinAmount) add(b *BitcoinAmount) *BitcoinAmount {
|
||||
return &BitcoinAmount{
|
||||
InSat: a.InSat + b.InSat,
|
||||
InInputCurrency: a.InInputCurrency.add(b.InInputCurrency),
|
||||
InPrimaryCurrency: a.InPrimaryCurrency.add(b.InPrimaryCurrency),
|
||||
}
|
||||
}
|
||||
67
libwallet/newop/money_test.go
Normal file
67
libwallet/newop/money_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func TestMonetaryAmountString(t *testing.T) {
|
||||
|
||||
amount := NewMonetaryAmountFromFiat("10.54", "USD")
|
||||
if amount.ValueAsString() != "10.54" {
|
||||
t.Fatalf("expected value as string to match, got %v", amount.ValueAsString())
|
||||
}
|
||||
|
||||
if amount.String() != "10.54 USD" {
|
||||
t.Fatalf("expected value as string to match, got %v", amount.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonetaryAmountToBitcoinAmount(t *testing.T) {
|
||||
|
||||
amount := NewMonetaryAmountFromSatoshis(100_000_000)
|
||||
|
||||
window := &ExchangeRateWindow{}
|
||||
window.AddRate("BTC", 1)
|
||||
window.AddRate("USD", 32_000)
|
||||
|
||||
bitcoinAmount := amount.toBitcoinAmount(window, "USD")
|
||||
|
||||
if bitcoinAmount.InSat != 100_000_000 {
|
||||
t.Fatalf("expected sats amount to be 100000000, got %v", bitcoinAmount.InSat)
|
||||
}
|
||||
|
||||
if bitcoinAmount.InPrimaryCurrency.Currency != "USD" {
|
||||
t.Fatalf("expected converted currency to be USD, got %v", bitcoinAmount.InPrimaryCurrency.Currency)
|
||||
}
|
||||
|
||||
if bitcoinAmount.InPrimaryCurrency.Value.Cmp(decimal.NewFromInt(32_000)) != 0 {
|
||||
t.Fatalf("expected converted amount to be 32000, got %v", bitcoinAmount.InInputCurrency.Value)
|
||||
}
|
||||
|
||||
if bitcoinAmount.InInputCurrency.Currency != "BTC" {
|
||||
t.Fatalf("expected intput currency to be BTC, got %v", bitcoinAmount.InInputCurrency.Currency)
|
||||
}
|
||||
|
||||
if bitcoinAmount.InInputCurrency.Value.Cmp(decimal.NewFromInt(1)) != 0 {
|
||||
t.Fatalf("expected converted amount to be 1, got %v", bitcoinAmount.InInputCurrency.Value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMonetaryAmountAdd(t *testing.T) {
|
||||
|
||||
a := NewMonetaryAmountFromFiat("10", "USD")
|
||||
b := NewMonetaryAmountFromFiat("20", "USD")
|
||||
|
||||
sum := a.add(b)
|
||||
|
||||
if sum.Currency != "USD" {
|
||||
t.Fatalf("expected converted currency to be USD, got %v", sum.Currency)
|
||||
}
|
||||
|
||||
if sum.Value.Cmp(decimal.NewFromInt(30)) != 0 {
|
||||
t.Fatalf("expected converted amount to be 30, got %v", sum.Value)
|
||||
}
|
||||
}
|
||||
51
libwallet/newop/nts.go
Normal file
51
libwallet/newop/nts.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/muun/libwallet/operation"
|
||||
)
|
||||
|
||||
type SizeForAmount struct {
|
||||
SizeInVByte int64
|
||||
AmountInSat int64
|
||||
Outpoint string
|
||||
}
|
||||
|
||||
// NextTransactionSize is a struct used for calculating fees in terms of the
|
||||
// unspent outputs required to perform a transaction
|
||||
type NextTransactionSize struct {
|
||||
SizeProgression []SizeForAmount
|
||||
ValidAtOperationHid int64 // Just for debugging reasons
|
||||
ExpectedDebtInSat int64
|
||||
}
|
||||
|
||||
func (w *NextTransactionSize) AddSizeForAmount(item *SizeForAmount) {
|
||||
w.SizeProgression = append(w.SizeProgression, *item)
|
||||
}
|
||||
|
||||
func (w *NextTransactionSize) GetOutpoints() string {
|
||||
var sb strings.Builder
|
||||
for i, sizeForAmount := range w.SizeProgression {
|
||||
sb.WriteString(sizeForAmount.Outpoint)
|
||||
if i != len(w.SizeProgression)-1 { // avoid trailing \n at the end
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (w *NextTransactionSize) toInternalType() *operation.NextTransactionSize {
|
||||
var sizeProgression []operation.SizeForAmount
|
||||
for _, sizeForAmount := range w.SizeProgression {
|
||||
sizeProgression = append(sizeProgression, operation.SizeForAmount{
|
||||
SizeInVByte: sizeForAmount.SizeInVByte,
|
||||
AmountInSat: sizeForAmount.AmountInSat,
|
||||
})
|
||||
}
|
||||
return &operation.NextTransactionSize{
|
||||
SizeProgression: sizeProgression,
|
||||
ExpectedDebtInSat: w.ExpectedDebtInSat,
|
||||
}
|
||||
}
|
||||
1106
libwallet/newop/state.go
Normal file
1106
libwallet/newop/state.go
Normal file
File diff suppressed because it is too large
Load Diff
1398
libwallet/newop/state_test.go
Normal file
1398
libwallet/newop/state_test.go
Normal file
File diff suppressed because it is too large
Load Diff
105
libwallet/newop/swaps.go
Normal file
105
libwallet/newop/swaps.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package newop
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/muun/libwallet/fees"
|
||||
)
|
||||
|
||||
type SubmarineSwapReceiver struct {
|
||||
Alias string
|
||||
NetworkAddresses string
|
||||
PublicKey string
|
||||
}
|
||||
|
||||
const (
|
||||
DebtTypeNone = string(fees.DebtTypeNone)
|
||||
DebtTypeCollect = string(fees.DebtTypeCollect)
|
||||
DebtTypeLend = string(fees.DebtTypeLend)
|
||||
)
|
||||
|
||||
type SwapFees struct {
|
||||
RoutingFeeInSat int64
|
||||
DebtType string
|
||||
DebtAmountInSat int64
|
||||
OutputAmountInSat int64
|
||||
OutputPaddingInSat int64
|
||||
ConfirmationsNeeded int64
|
||||
}
|
||||
|
||||
func (f *SwapFees) toInternalType() *fees.SwapFees {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &fees.SwapFees{
|
||||
RoutingFee: btcutil.Amount(f.RoutingFeeInSat),
|
||||
DebtType: fees.DebtType(f.DebtType),
|
||||
DebtAmount: btcutil.Amount(f.DebtAmountInSat),
|
||||
OutputAmount: btcutil.Amount(f.OutputAmountInSat),
|
||||
OutputPadding: btcutil.Amount(f.OutputPaddingInSat),
|
||||
ConfirmationsNeeded: uint(f.ConfirmationsNeeded),
|
||||
}
|
||||
}
|
||||
|
||||
func newSwapFeesFromInternal(fees *fees.SwapFees) *SwapFees {
|
||||
return &SwapFees{
|
||||
RoutingFeeInSat: int64(fees.RoutingFee),
|
||||
DebtType: string(fees.DebtType),
|
||||
DebtAmountInSat: int64(fees.DebtAmount),
|
||||
OutputAmountInSat: int64(fees.OutputAmount),
|
||||
OutputPaddingInSat: int64(fees.OutputPadding),
|
||||
ConfirmationsNeeded: int64(fees.ConfirmationsNeeded),
|
||||
}
|
||||
}
|
||||
|
||||
type BestRouteFees struct {
|
||||
MaxCapacity int64
|
||||
FeeProportionalMillionth int64
|
||||
FeeBase int64
|
||||
}
|
||||
|
||||
func (f *BestRouteFees) toInternalType() *fees.BestRouteFees {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &fees.BestRouteFees{
|
||||
MaxCapacity: btcutil.Amount(f.MaxCapacity),
|
||||
FeeProportionalMillionth: uint64(f.FeeProportionalMillionth),
|
||||
FeeBase: btcutil.Amount(f.FeeBase),
|
||||
}
|
||||
}
|
||||
|
||||
type FundingOutputPolicies struct {
|
||||
MaximumDebtInSat int64
|
||||
PotentialCollectInSat int64
|
||||
MaxAmountInSatFor0Conf int64
|
||||
}
|
||||
|
||||
func (f *FundingOutputPolicies) toInternalType() *fees.FundingOutputPolicies {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return &fees.FundingOutputPolicies{
|
||||
MaximumDebt: btcutil.Amount(f.MaximumDebtInSat),
|
||||
PotentialCollect: btcutil.Amount(f.PotentialCollectInSat),
|
||||
MaxAmountFor0Conf: btcutil.Amount(f.MaxAmountInSatFor0Conf),
|
||||
}
|
||||
}
|
||||
|
||||
type SubmarineSwap struct {
|
||||
Receiver *SubmarineSwapReceiver
|
||||
Fees *SwapFees
|
||||
FundingOutputPolicies *FundingOutputPolicies
|
||||
BestRouteFees []*BestRouteFees
|
||||
}
|
||||
|
||||
func (s *SubmarineSwap) AddBestRouteFees(bestRouteFees *BestRouteFees) {
|
||||
s.BestRouteFees = append(s.BestRouteFees, bestRouteFees)
|
||||
}
|
||||
|
||||
func (s *SubmarineSwap) toBestRouteFeesInternalType() []fees.BestRouteFees {
|
||||
var l []fees.BestRouteFees
|
||||
for _, bestRouteFee := range s.BestRouteFees {
|
||||
l = append(l, *(bestRouteFee.toInternalType()))
|
||||
}
|
||||
return l
|
||||
}
|
||||
Reference in New Issue
Block a user