Update project structure and build process

This commit is contained in:
Juan Pablo Civile
2025-05-13 11:10:08 -03:00
parent 124e9fa1bc
commit d9f3e925a4
277 changed files with 15321 additions and 930 deletions

View 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(),
)
}

View 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)
}
}

View 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}
}

View 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)
}
}

View 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
}

View 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
View 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),
}
}

View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

105
libwallet/newop/swaps.go Normal file
View 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
}