Skip to content

Commit

Permalink
feat: chain swaps cli (#136)
Browse files Browse the repository at this point in the history
* feat: chain swap cli

* fix: chain swap help description

* fix: help output missing , statement

* fix: chain swap help description from/to

* fix: createswap,createreverseswap,createchainswap help description

* fix: autoswapper -> autoswap in help outputs, comments

* fix: typo

* refactor: dont implicitly select wallets

* fix: check for state in timeout condition when querying refundable swaps

* fix: only set fees of valid outputs after transaction was created

* feat: implicitly set external pay in cli when not specifying from wallet

* chore: cleanup

* fix: check if value is less than fee when constructing transaction

* feat: return output specific errors when creating transactions

* fix: refund help description

* fix: --from-external, --to-address help output

* refactor: dont default to mainchain when creating swap

* refactor: use from and to wallet terminology everywhere

* chore: add db migration

* fix: display correct refund transaction when refunding in cli

* fix: show correct timeout in cli

* feat: recover pending chain swaps

* fix: correctly set external pay when creating submarine swap

* feat: display currency of lockup tx in cli

* fix: dont try to broadcast empty transaction

---------

Co-authored-by: Kilian <19181985+kilrau@users.noreply.github.com>
  • Loading branch information
jackstar12 and kilrau authored Apr 26, 2024
1 parent cb272cc commit eaa78bc
Show file tree
Hide file tree
Showing 20 changed files with 598 additions and 203 deletions.
16 changes: 6 additions & 10 deletions boltz/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func (transaction *BtcTransaction) FindVout(network *Network, addressToFind stri
return 0, 0, errors.New("Could not find address in transaction")
}

func (transaction *BtcTransaction) VoutValue(vout uint32) (uint64, error) {
return uint64(transaction.MsgTx().TxOut[vout].Value), nil
}

func getPrevoutFetcher(tx *wire.MsgTx, outputs []OutputDetails) txscript.PrevOutputFetcher {
previous := make(map[wire.OutPoint]*wire.TxOut)
for i, input := range tx.TxIn {
Expand Down Expand Up @@ -99,11 +103,9 @@ func btcTaprootHash(transaction Transaction, outputs []OutputDetails, index int)
)
}

func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transaction, error) {
func constructBtcTransaction(network *Network, outputs []OutputDetails, outValues map[string]uint64) (Transaction, error) {
transaction := wire.NewMsgTx(wire.TxVersion)

outValues := make(map[string]int64)

for _, output := range outputs {
// Set the highest timeout block height as locktime
if !output.Cooperative {
Expand All @@ -119,12 +121,6 @@ func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transac
input.Sequence = 0

transaction.AddTxIn(input)

value := lockupTx.MsgTx().TxOut[output.Vout].Value - int64(output.Fee)
//nolint:gosimple
existingValue, _ := outValues[output.Address]
outValues[output.Address] = existingValue + value

}

for rawAddress, value := range outValues {
Expand All @@ -141,7 +137,7 @@ func constructBtcTransaction(network *Network, outputs []OutputDetails) (Transac

transaction.AddTxOut(&wire.TxOut{
PkScript: outputScript,
Value: value,
Value: int64(value),
})
}

Expand Down
23 changes: 12 additions & 11 deletions boltz/liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ func (transaction *LiquidTransaction) FindVout(network *Network, addressToFind s

}

func (transaction *LiquidTransaction) VoutValue(vout uint32) (uint64, error) {
result, err := confidential.UnblindOutputWithKey(transaction.Outputs[vout], transaction.OurOutputBlindingKey.Serialize())
if err != nil {
return 0, err
}
return result.Value, nil
}

func (transaction *LiquidTransaction) VSize() uint64 {
witnessSize := transaction.SerializeSize(true, true) - transaction.SerializeSize(false, true)
return uint64(transaction.SerializeSize(false, true)) + uint64(math.Ceil(float64(witnessSize)/4))
Expand Down Expand Up @@ -107,7 +115,7 @@ func liquidTaprootHash(transaction *liquidtx.Transaction, network *Network, outp
return hash[:]
}

func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Transaction, error) {
func constructLiquidTransaction(network *Network, outputs []OutputDetails, outValues map[string]uint64) (Transaction, error) {
p, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
Expand All @@ -118,6 +126,7 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
}

var inPrivateBlindingKeys [][]byte
var totalFee uint64

for i, output := range outputs {
lockupTx := output.LockupTransaction.(*LiquidTransaction)
Expand Down Expand Up @@ -150,6 +159,8 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
}
inPrivateBlindingKeys = append(inPrivateBlindingKeys, lockupTx.OurOutputBlindingKey.Serialize())
}

totalFee += output.Fee
}

zkpGenerator := confidential.NewZKPGeneratorFromBlindingKeys(inPrivateBlindingKeys, nil)
Expand All @@ -159,16 +170,6 @@ func constructLiquidTransaction(network *Network, outputs []OutputDetails) (Tran
return nil, errors.New("Failed to unblind inputs: " + err.Error())
}

outValues := make(map[string]uint64)
var totalFee uint64
for i, input := range ownedInputs {
address := outputs[i].Address
//nolint:gosimple
existingValue, _ := outValues[address]
outValues[address] = existingValue + input.Value - outputs[i].Fee
totalFee += outputs[i].Fee
}

btcAsset := network.Liquid.AssetID

txOutputs := []psetv2.OutputArgs{
Expand Down
113 changes: 84 additions & 29 deletions boltz/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Transaction interface {
Serialize() (string, error)
VSize() uint64
FindVout(network *Network, address string) (uint32, uint64, error)
VoutValue(vout uint32) (uint64, error)
}

type OutputDetails struct {
Expand Down Expand Up @@ -60,53 +61,79 @@ func NewTxFromHex(currency Currency, hexString string, ourOutputBlindingKey *btc
return NewBtcTxFromHex(hexString)
}

func ConstructTransaction(network *Network, currency Currency, outputs []OutputDetails, satPerVbyte float64, boltzApi *Boltz) (Transaction, []uint64, error) {
var construct func(*Network, []OutputDetails) (Transaction, error)
type OutputResult struct {
Err error
Fee uint64
}

func ConstructTransaction(network *Network, currency Currency, outputs []OutputDetails, satPerVbyte float64, boltzApi *Boltz) (Transaction, map[string]OutputResult, error) {
construct := constructBtcTransaction
if currency == CurrencyLiquid {
construct = constructLiquidTransaction
} else if currency == CurrencyBtc {
construct = constructBtcTransaction
} else {
return nil, nil, fmt.Errorf("invalid pair: %v", currency)
}
results := make(map[string]OutputResult, len(outputs))

getOutValues := func(fee uint64) map[string]uint64 {
outValues := make(map[string]uint64)

outLen := uint64(len(outputs))
feePerOutput := fee / outLen
feeRemainder := fee % outLen

noFeeTransaction, err := construct(network, outputs)
for i := range outputs {
output := &outputs[i]
output.Fee = feePerOutput + feeRemainder
feeRemainder = 0

value, err := output.LockupTransaction.VoutValue(output.Vout)
if err == nil {
if value < output.Fee {
err = fmt.Errorf("value less than fee: %d < %d", value, output.Fee)
}
}

if err != nil {
results[output.SwapId] = OutputResult{Err: err}
continue
}

results[output.SwapId] = OutputResult{Fee: output.Fee}
outValues[output.Address] += value - output.Fee
}
return outValues
}

noFeeTransaction, err := construct(network, outputs, getOutValues(0))
if err != nil {
return nil, nil, err
}

fee := uint64(float64(noFeeTransaction.VSize()) * satPerVbyte)

outLen := uint64(len(outputs))
feePerOutput := fee / outLen
feeRemainder := fee % outLen

fees := make([]uint64, len(outputs))
for i := range outputs {
outputs[i].Fee = feePerOutput + feeRemainder
fees[i] = outputs[i].Fee
feeRemainder = 0
}

transaction, err := construct(network, outputs)
transaction, err := construct(network, outputs, getOutValues(fee))
if err != nil {
return nil, nil, err
}

var valid []OutputDetails

for i, output := range outputs {
if output.Cooperative {
if boltzApi == nil {
return nil, nil, errors.New("boltzApi is required for cooperative transactions")
err = func() error {
if output.Cooperative {
return nil
}
session, err := NewSigningSession(outputs[i].SwapTree)
serialized, err := transaction.Serialize()
if err != nil {
return nil, nil, fmt.Errorf("could not initialize signing session: %w", err)
return fmt.Errorf("could not serialize transaction: %w", err)
}

serialized, err := transaction.Serialize()
if boltzApi == nil {
return errors.New("boltzApi is required for cooperative transactions")
}

session, err := NewSigningSession(outputs[i].SwapTree)
if err != nil {
return nil, nil, fmt.Errorf("could not serialize transaction: %w", err)
return fmt.Errorf("could not initialize signing session: %w", err)
}

pubNonce := session.PublicNonce()
Expand Down Expand Up @@ -154,14 +181,42 @@ func ConstructTransaction(network *Network, currency Currency, outputs []OutputD
}()
}
if err != nil {
return nil, nil, fmt.Errorf("could not get partial signature from boltz: %w", err)
return fmt.Errorf("could not get partial signature from boltz: %w", err)
}

if err := session.Finalize(transaction, outputs, network, signature); err != nil {
return nil, nil, fmt.Errorf("could not finalize signing session: %w", err)
return fmt.Errorf("could not finalize signing session: %w", err)
}

return nil
}()
if err != nil {
if output.IsRefund() {
results[output.SwapId] = OutputResult{Err: err}
} else {
nonCoop := outputs[i]
nonCoop.Cooperative = false
valid = append(valid, nonCoop)
}
} else {
valid = append(valid, output)
}
}

if len(valid) == 0 {
return nil, results, fmt.Errorf("all outputs invalid")
}

if len(valid) < len(outputs) {
transaction, newResults, err := ConstructTransaction(network, currency, valid, satPerVbyte, boltzApi)
if err != nil {
return nil, nil, err
}
for id, result := range newResults {
results[id] = result
}
return transaction, results, nil
}

return transaction, fees, err
return transaction, results, err
}
8 changes: 4 additions & 4 deletions boltzrpc/autoswaprpc/autoswaprpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import "boltzrpc.proto";

service AutoSwap {
/*
Returns a list of swaps which are currently recommended by the autoswapper. Also works when the autoswapper is not running.
Returns a list of swaps which are currently recommended by autoswap. Also works when autoswap is not running.
*/
rpc GetSwapRecommendations (GetSwapRecommendationsRequest) returns (GetSwapRecommendationsResponse);

/*
Returns the current budget of the autoswapper and some relevant stats.
Returns the current budget of autoswap and some relevant stats.
*/
rpc GetStatus (GetStatusRequest) returns (GetStatusResponse);

Expand All @@ -23,12 +23,12 @@ service AutoSwap {
rpc ResetConfig(google.protobuf.Empty) returns (Config);

/*
Allows setting multiple json-encoded config values at once. The autoswapper will reload the configuration after this call.
Allows setting multiple json-encoded config values at once. Autoswap will reload the configuration after this call.
*/
rpc SetConfig(Config) returns (Config);

/*
Allows setting a specific value in the configuration. The autoswapper will reload the configuration after this call.
Allows setting a specific value in the configuration. Autoswap will reload the configuration after this call.
*/
rpc SetConfigValue(SetConfigValueRequest) returns (Config);

Expand Down
16 changes: 8 additions & 8 deletions boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions boltzrpc/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (boltz *Boltz) GetWallet(name string) (*boltzrpc.Wallet, error) {
return boltz.Client.GetWallet(boltz.Ctx, &boltzrpc.GetWalletRequest{Name: &name})
}

func (boltz *Boltz) GetWalletById(id int64) (*boltzrpc.Wallet, error) {
return boltz.Client.GetWallet(boltz.Ctx, &boltzrpc.GetWalletRequest{Id: &id})
}

func (boltz *Boltz) GetWallets(currency *boltzrpc.Currency, includeReadonly bool) (*boltzrpc.Wallets, error) {
return boltz.Client.GetWallets(boltz.Ctx, &boltzrpc.GetWalletsRequest{Currency: currency, IncludeReadonly: &includeReadonly})
}
Expand Down
1 change: 1 addition & 0 deletions cmd/boltzcli/boltzcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func main() {

createSwapCommand,
createReverseSwapCommand,
createChainSwapCommand,
refundSwapCommand,

autoSwapCommands,
Expand Down
Loading

0 comments on commit eaa78bc

Please sign in to comment.