Delay unilateral exit and support send to onchain address (#117)

* add delay on redeem close + forfeit close

* increase default round lifetime (16 minutes min)

* add sequence to final pset

* update CLI and server to support delayed vtxos oncahin

* rename future to "locked"

* add configurable EXIT_DELAY variable

* renaming

* rename "close" --> "closure"

* rename "close" to "closure"

* error message config.go
This commit is contained in:
Louis Singer
2024-02-22 16:47:52 +01:00
committed by GitHub
parent 7c8ee7ab12
commit a95a829b20
30 changed files with 1244 additions and 719 deletions

View File

@@ -4,10 +4,13 @@ import (
"bytes"
"encoding/json"
"fmt"
"math"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/urfave/cli/v2"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/psetv2"
)
type receiver struct {
@@ -15,6 +18,11 @@ type receiver struct {
Amount uint64 `json:"amount"`
}
func (r *receiver) IsOnchain() bool {
_, err := address.ToOutputScript(r.To)
return err == nil
}
var (
receiversFlag = cli.StringFlag{
Name: "receivers",
@@ -22,7 +30,7 @@ var (
}
toFlag = cli.StringFlag{
Name: "to",
Usage: "ark address of the recipient",
Usage: "address of the recipient",
}
amountFlag = cli.Uint64Flag{
Name: "amount",
@@ -63,6 +71,33 @@ func sendAction(ctx *cli.Context) error {
return fmt.Errorf("no receivers specified")
}
onchainReceivers := make([]receiver, 0)
offchainReceivers := make([]receiver, 0)
for _, receiver := range receiversJSON {
if receiver.IsOnchain() {
onchainReceivers = append(onchainReceivers, receiver)
} else {
offchainReceivers = append(offchainReceivers, receiver)
}
}
if len(onchainReceivers) > 0 {
if err := sendOnchain(ctx, onchainReceivers); err != nil {
return err
}
}
if len(offchainReceivers) > 0 {
if err := sendOffchain(ctx, offchainReceivers); err != nil {
return err
}
}
return nil
}
func sendOffchain(ctx *cli.Context, receivers []receiver) error {
offchainAddr, _, err := getAddress()
if err != nil {
return err
@@ -76,7 +111,7 @@ func sendAction(ctx *cli.Context) error {
receiversOutput := make([]*arkv1.Output, 0)
sumOfReceivers := uint64(0)
for _, receiver := range receiversJSON {
for _, receiver := range receivers {
_, _, aspKey, err := common.DecodeAddress(receiver.To)
if err != nil {
return fmt.Errorf("invalid receiver address: %s", err)
@@ -163,11 +198,169 @@ func sendAction(ctx *cli.Context) error {
return err
}
if err := printJSON(map[string]interface{}{
return printJSON(map[string]interface{}{
"pool_txid": poolTxID,
})
}
func sendOnchain(ctx *cli.Context, receivers []receiver) error {
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return err
}
_, net := getNetwork()
targetAmount := uint64(0)
for _, receiver := range receivers {
targetAmount += receiver.Amount
if receiver.Amount < DUST {
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount, DUST)
}
script, err := address.ToOutputScript(receiver.To)
if err != nil {
return err
}
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: net.AssetID,
Amount: receiver.Amount,
Script: script,
},
}); err != nil {
return err
}
}
selected, delayedSelected, change, err := coinSelectOnchain(targetAmount, nil)
if err != nil {
return err
}
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
return err
}
if change > 0 {
_, changeAddr, err := getAddress()
if err != nil {
return err
}
changeScript, err := address.ToOutputScript(changeAddr)
if err != nil {
return err
}
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: net.AssetID,
Amount: change,
Script: changeScript,
},
}); err != nil {
return err
}
}
utx, err := updater.Pset.UnsignedTx()
if err != nil {
return err
}
vBytes := utx.VirtualSize()
feeAmount := uint64(math.Ceil(float64(vBytes) * 0.5))
if change > feeAmount {
updater.Pset.Outputs[len(updater.Pset.Outputs)-1].Value = change - feeAmount
} else if change == feeAmount {
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
} else { // change < feeAmount
if change > 0 {
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
}
// reselect the difference
selected, delayedSelected, newChange, err := coinSelectOnchain(
feeAmount-change,
append(selected, delayedSelected...),
)
if err != nil {
return err
}
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
return err
}
if newChange > 0 {
_, changeAddr, err := getAddress()
if err != nil {
return err
}
changeScript, err := address.ToOutputScript(changeAddr)
if err != nil {
return err
}
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: net.AssetID,
Amount: newChange,
Script: changeScript,
},
}); err != nil {
return err
}
}
}
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: net.AssetID,
Amount: feeAmount,
},
}); err != nil {
return err
}
return nil
prvKey, err := privateKeyFromPassword()
if err != nil {
return err
}
explorer := NewExplorer()
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
return err
}
if err := psetv2.FinalizeAll(updater.Pset); err != nil {
return err
}
extracted, err := psetv2.Extract(pset)
if err != nil {
return err
}
hex, err := extracted.ToHex()
if err != nil {
return err
}
txid, err := explorer.Broadcast(hex)
if err != nil {
return err
}
return printJSON(map[string]interface{}{
"txid": txid,
})
}