mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 20:24:21 +01:00
[Client] Add vtxo expiration details to balance & Fix coin selection
* add expiry details in balance command * coin selection: sort vtxos by olderFirst * rename type * balance: add next expiration * add next expiration in offchain_balance json * print duration in nextExpiration * fix dust coin selection * refactor sort
This commit is contained in:
@@ -1,18 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var expiryDetailsFlag = cli.BoolFlag{
|
||||
Name: "expiry-details",
|
||||
Usage: "show cumulative balance by expiry time",
|
||||
Value: false,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
var balanceCommand = cli.Command{
|
||||
Name: "balance",
|
||||
Usage: "Print balance of the Ark wallet",
|
||||
Action: balanceAction,
|
||||
Flags: []cli.Flag{&expiryDetailsFlag},
|
||||
}
|
||||
|
||||
func balanceAction(ctx *cli.Context) error {
|
||||
expiryDetails := ctx.Bool("expiry-details")
|
||||
|
||||
client, cancel, err := getClientFromState(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -30,26 +43,30 @@ func balanceAction(ctx *cli.Context) error {
|
||||
chRes := make(chan balanceRes, 2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
balance, err := getOffchainBalance(ctx, client, offchainAddr)
|
||||
explorer := NewExplorer()
|
||||
balance, amountByExpiration, err := getOffchainBalance(ctx, explorer, client, offchainAddr, true)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{0, 0, err}
|
||||
chRes <- balanceRes{0, 0, nil, err}
|
||||
return
|
||||
}
|
||||
chRes <- balanceRes{balance, 0, nil}
|
||||
|
||||
chRes <- balanceRes{balance, 0, amountByExpiration, nil}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
balance, err := getOnchainBalance(onchainAddr)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{0, 0, err}
|
||||
chRes <- balanceRes{0, 0, nil, err}
|
||||
return
|
||||
}
|
||||
chRes <- balanceRes{0, balance, nil}
|
||||
chRes <- balanceRes{0, balance, nil, nil}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
details := make([]map[string]interface{}, 0)
|
||||
offchainBalance, onchainBalance := uint64(0), uint64(0)
|
||||
nextExpiration := int64(0)
|
||||
count := 0
|
||||
for res := range chRes {
|
||||
if res.err != nil {
|
||||
@@ -61,20 +78,74 @@ func balanceAction(ctx *cli.Context) error {
|
||||
if res.onchainBalance > 0 {
|
||||
onchainBalance = res.onchainBalance
|
||||
}
|
||||
if res.amountByExpiration != nil {
|
||||
for timestamp, amount := range res.amountByExpiration {
|
||||
if nextExpiration == 0 || timestamp < nextExpiration {
|
||||
nextExpiration = timestamp
|
||||
}
|
||||
|
||||
fancyTime := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
|
||||
details = append(
|
||||
details,
|
||||
map[string]interface{}{
|
||||
"expiry_time": fancyTime,
|
||||
"amount": amount,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
count++
|
||||
if count == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return printJSON(map[string]interface{}{
|
||||
"offchain_balance": offchainBalance,
|
||||
"onchain_balance": onchainBalance,
|
||||
})
|
||||
response := make(map[string]interface{})
|
||||
response["onchain_balance"] = onchainBalance
|
||||
|
||||
offchainBalanceJSON := map[string]interface{}{
|
||||
"total": offchainBalance,
|
||||
}
|
||||
|
||||
fancyTimeExpiration := ""
|
||||
if nextExpiration != 0 {
|
||||
t := time.Unix(nextExpiration, 0)
|
||||
if t.Before(time.Now().Add(48 * time.Hour)) {
|
||||
// print the duration instead of the absolute time
|
||||
until := time.Until(t)
|
||||
seconds := math.Abs(until.Seconds())
|
||||
minutes := math.Abs(until.Minutes())
|
||||
hours := math.Abs(until.Hours())
|
||||
|
||||
if hours < 1 {
|
||||
if minutes < 1 {
|
||||
fancyTimeExpiration = fmt.Sprintf("%d seconds", int(seconds))
|
||||
} else {
|
||||
fancyTimeExpiration = fmt.Sprintf("%d minutes", int(minutes))
|
||||
}
|
||||
} else {
|
||||
fancyTimeExpiration = fmt.Sprintf("%d hours", int(hours))
|
||||
}
|
||||
} else {
|
||||
fancyTimeExpiration = t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
offchainBalanceJSON["next_expiration"] = fancyTimeExpiration
|
||||
}
|
||||
|
||||
if expiryDetails {
|
||||
offchainBalanceJSON["details"] = details
|
||||
}
|
||||
|
||||
response["offchain_balance"] = offchainBalanceJSON
|
||||
|
||||
return printJSON(response)
|
||||
}
|
||||
|
||||
type balanceRes struct {
|
||||
offchainBalance uint64
|
||||
onchainBalance uint64
|
||||
amountByExpiration map[int64]uint64
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -16,10 +17,15 @@ type vtxo struct {
|
||||
txid string
|
||||
vout uint32
|
||||
poolTxid string
|
||||
expireAt *time.Time
|
||||
}
|
||||
|
||||
func getVtxos(
|
||||
ctx *cli.Context, client arkv1.ArkServiceClient, addr string,
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
client arkv1.ArkServiceClient,
|
||||
addr string,
|
||||
withExpiration bool,
|
||||
) ([]vtxo, error) {
|
||||
response, err := client.ListVtxos(ctx.Context, &arkv1.ListVtxosRequest{
|
||||
Address: addr,
|
||||
@@ -38,6 +44,29 @@ func getVtxos(
|
||||
})
|
||||
}
|
||||
|
||||
if !withExpiration {
|
||||
return vtxos, nil
|
||||
}
|
||||
|
||||
redeemBranches, err := getRedeemBranches(ctx, explorer, client, vtxos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for vtxoTxid, branch := range redeemBranches {
|
||||
expiration, err := branch.ExpireAt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, vtxo := range vtxos {
|
||||
if vtxo.txid == vtxoTxid {
|
||||
vtxos[i].expireAt = expiration
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vtxos, nil
|
||||
}
|
||||
|
||||
|
||||
137
client/common.go
137
client/common.go
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -150,6 +151,15 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||
notSelected := make([]vtxo, 0)
|
||||
selectedAmount := uint64(0)
|
||||
|
||||
// sort vtxos by expiration (older first)
|
||||
sort.SliceStable(vtxos, func(i, j int) bool {
|
||||
if vtxos[i].expireAt == nil || vtxos[j].expireAt == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return vtxos[i].expireAt.Before(*vtxos[j].expireAt)
|
||||
})
|
||||
|
||||
for _, vtxo := range vtxos {
|
||||
if selectedAmount >= amount {
|
||||
notSelected = append(notSelected, vtxo)
|
||||
@@ -177,17 +187,30 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||
}
|
||||
|
||||
func getOffchainBalance(
|
||||
ctx *cli.Context, client arkv1.ArkServiceClient, addr string,
|
||||
) (uint64, error) {
|
||||
vtxos, err := getVtxos(ctx, client, addr)
|
||||
ctx *cli.Context, explorer Explorer, client arkv1.ArkServiceClient, addr string, withExpiration bool,
|
||||
) (uint64, map[int64]uint64, error) {
|
||||
amountByExpiration := make(map[int64]uint64, 0)
|
||||
|
||||
vtxos, err := getVtxos(ctx, explorer, client, addr, withExpiration)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, nil, err
|
||||
}
|
||||
var balance uint64
|
||||
for _, vtxo := range vtxos {
|
||||
balance += vtxo.amount
|
||||
|
||||
if withExpiration {
|
||||
expiration := vtxo.expireAt.Unix()
|
||||
|
||||
if _, ok := amountByExpiration[expiration]; !ok {
|
||||
amountByExpiration[expiration] = 0
|
||||
}
|
||||
return balance, nil
|
||||
|
||||
amountByExpiration[expiration] += vtxo.amount
|
||||
}
|
||||
}
|
||||
|
||||
return balance, amountByExpiration, nil
|
||||
}
|
||||
|
||||
type utxo struct {
|
||||
@@ -198,11 +221,7 @@ type utxo struct {
|
||||
}
|
||||
|
||||
func getOnchainUtxos(addr string) ([]utxo, error) {
|
||||
_, net, err := getNetwork()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", baseUrl, addr))
|
||||
if err != nil {
|
||||
@@ -230,11 +249,7 @@ func getOnchainBalance(addr string) (uint64, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, net, err := getNetwork()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
balance := uint64(0)
|
||||
for _, p := range payload {
|
||||
if p.Asset != net.AssetID {
|
||||
@@ -245,36 +260,43 @@ func getOnchainBalance(addr string) (uint64, error) {
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func getTxHex(txid string) (string, error) {
|
||||
_, net, err := getNetwork()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", baseUrl, txid))
|
||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s", baseUrl, txid))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf(string(body))
|
||||
return false, 0, fmt.Errorf(string(body))
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
var tx struct {
|
||||
Status struct {
|
||||
Confirmed bool `json:"confirmed"`
|
||||
Blocktime int64 `json:"block_time"`
|
||||
} `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tx); err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
|
||||
if !tx.Status.Confirmed {
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
return true, tx.Status.Blocktime, nil
|
||||
|
||||
}
|
||||
|
||||
func broadcast(txHex string) (string, error) {
|
||||
_, net, err := getNetwork()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
body := bytes.NewBuffer([]byte(txHex))
|
||||
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
@@ -295,20 +317,20 @@ func broadcast(txHex string) (string, error) {
|
||||
return string(bodyResponse), nil
|
||||
}
|
||||
|
||||
func getNetwork() (*common.Network, *network.Network, error) {
|
||||
func getNetwork() (*common.Network, *network.Network) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return &common.TestNet, &network.Testnet
|
||||
}
|
||||
|
||||
net, ok := state["network"]
|
||||
if !ok {
|
||||
return &common.MainNet, &network.Liquid, nil
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
if net == "testnet" {
|
||||
return &common.TestNet, &network.Testnet, nil
|
||||
return &common.TestNet, &network.Testnet
|
||||
}
|
||||
return &common.MainNet, &network.Liquid, nil
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
|
||||
func getAddress() (offchainAddr, onchainAddr string, err error) {
|
||||
@@ -322,10 +344,7 @@ func getAddress() (offchainAddr, onchainAddr string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
arkNet, liquidNet, err := getNetwork()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
arkNet, liquidNet := getNetwork()
|
||||
|
||||
arkAddr, err := common.EncodeAddress(arkNet.Addr, publicKey, aspPublicKey)
|
||||
if err != nil {
|
||||
@@ -675,3 +694,41 @@ func findSweepClosure(
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getRedeemBranches(
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
client arkv1.ArkServiceClient,
|
||||
vtxos []vtxo,
|
||||
) (map[string]RedeemBranch, error) {
|
||||
congestionTrees := make(map[string]tree.CongestionTree, 0) // poolTxid -> congestionTree
|
||||
redeemBranches := make(map[string]RedeemBranch, 0) // vtxo.txid -> redeemBranch
|
||||
|
||||
for _, vtxo := range vtxos {
|
||||
if _, ok := congestionTrees[vtxo.poolTxid]; !ok {
|
||||
round, err := client.GetRound(ctx.Context, &arkv1.GetRoundRequest{
|
||||
Txid: vtxo.poolTxid,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
treeFromRound := round.GetRound().GetCongestionTree()
|
||||
congestionTree, err := toCongestionTree(treeFromRound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
congestionTrees[vtxo.poolTxid] = congestionTree
|
||||
}
|
||||
|
||||
redeemBranch, err := newRedeemBranch(ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
redeemBranches[vtxo.txid] = redeemBranch
|
||||
}
|
||||
|
||||
return redeemBranches, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vulpemventures/go-elements/transaction"
|
||||
@@ -13,11 +16,16 @@ type Explorer interface {
|
||||
|
||||
type explorer struct {
|
||||
cache map[string]string
|
||||
baseUrl string
|
||||
}
|
||||
|
||||
func NewExplorer() Explorer {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
|
||||
return &explorer{
|
||||
cache: make(map[string]string),
|
||||
baseUrl: baseUrl,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +34,7 @@ func (e *explorer) GetTxHex(txid string) (string, error) {
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
txHex, err := getTxHex(txid)
|
||||
txHex, err := e.getTxHex(txid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -55,3 +63,23 @@ func (e *explorer) Broadcast(txHex string) (string, error) {
|
||||
|
||||
return txid, nil
|
||||
}
|
||||
|
||||
func (e *explorer) getTxHex(txid string) (string, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", e.baseUrl, txid))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf(string(body))
|
||||
}
|
||||
|
||||
hex := string(body)
|
||||
e.cache[txid] = hex
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
@@ -110,10 +110,7 @@ func initWallet(ctx *cli.Context, key, password string) error {
|
||||
|
||||
cypher := NewAES128Cypher()
|
||||
|
||||
arkNetwork, _, err := getNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arkNetwork, _ := getNetwork()
|
||||
|
||||
publicKey, err := common.EncodePubKey(arkNetwork.PubKey, privateKey.PubKey())
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vulpemventures/go-elements/address"
|
||||
"github.com/vulpemventures/go-elements/psetv2"
|
||||
@@ -61,7 +60,7 @@ func redeemAction(ctx *cli.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid onchain address: unknown network")
|
||||
}
|
||||
_, liquidNet, _ := getNetwork()
|
||||
_, liquidNet := getNetwork()
|
||||
if net.Name != liquidNet.Name {
|
||||
return fmt.Errorf("invalid onchain address: must be for %s network", liquidNet.Name)
|
||||
}
|
||||
@@ -105,7 +104,9 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
}
|
||||
defer close()
|
||||
|
||||
vtxos, err := getVtxos(ctx, client, offchainAddr)
|
||||
explorer := NewExplorer()
|
||||
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -189,7 +190,8 @@ func unilateralRedeem(ctx *cli.Context, addr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
vtxos, err := getVtxos(ctx, client, offchainAddr)
|
||||
explorer := NewExplorer()
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -214,46 +216,25 @@ func unilateralRedeem(ctx *cli.Context, addr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
congestionTrees := make(map[string]tree.CongestionTree, 0)
|
||||
// transactionsMap avoid duplicates
|
||||
transactionsMap := make(map[string]struct{}, 0)
|
||||
transactions := make([]string, 0)
|
||||
|
||||
for _, vtxo := range vtxos {
|
||||
if _, ok := congestionTrees[vtxo.poolTxid]; !ok {
|
||||
round, err := client.GetRound(ctx.Context, &arkv1.GetRoundRequest{
|
||||
Txid: vtxo.poolTxid,
|
||||
})
|
||||
redeemBranches, err := getRedeemBranches(ctx, explorer, client, vtxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
treeFromRound := round.GetRound().GetCongestionTree()
|
||||
congestionTree, err := toCongestionTree(treeFromRound)
|
||||
for _, branch := range redeemBranches {
|
||||
if err := branch.AddVtxoInput(updater); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
branchTxs, err := branch.RedeemPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
congestionTrees[vtxo.poolTxid] = congestionTree
|
||||
}
|
||||
|
||||
redeemBranch, err := newRedeemBranch(ctx, congestionTrees[vtxo.poolTxid], vtxo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := redeemBranch.UpdatePath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
branchTxs, err := redeemBranch.RedeemPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := redeemBranch.AddVtxoInput(updater); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, txHex := range branchTxs {
|
||||
if _, ok := transactionsMap[txHex]; !ok {
|
||||
transactions = append(transactions, txHex)
|
||||
@@ -262,10 +243,7 @@ func unilateralRedeem(ctx *cli.Context, addr string) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, net, err := getNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, net := getNetwork()
|
||||
|
||||
outputs := []psetv2.OutputArgs{
|
||||
{
|
||||
@@ -307,8 +285,6 @@ func unilateralRedeem(ctx *cli.Context, addr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
explorer := NewExplorer()
|
||||
|
||||
for i, txHex := range transactions {
|
||||
for {
|
||||
txid, err := explorer.Broadcast(txHex)
|
||||
|
||||
@@ -102,7 +102,9 @@ func sendAction(ctx *cli.Context) error {
|
||||
}
|
||||
defer close()
|
||||
|
||||
vtxos, err := getVtxos(ctx, client, offchainAddr)
|
||||
explorer := NewExplorer()
|
||||
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -81,10 +81,7 @@ func signPset(
|
||||
return err
|
||||
}
|
||||
|
||||
_, liquidNet, err := getNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, liquidNet := getNetwork()
|
||||
|
||||
prevoutsScripts := make([][]byte, 0)
|
||||
prevoutsValues := make([][]byte, 0)
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
@@ -12,12 +13,12 @@ import (
|
||||
)
|
||||
|
||||
type RedeemBranch interface {
|
||||
// UpdatePath checks for transactions of the branch onchain and updates the branch accordingly
|
||||
UpdatePath() error
|
||||
// Redeem will sign the branch of the tree and return the associated signed pset + the vtxo input
|
||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
||||
RedeemPath() ([]string, error)
|
||||
// AddInput adds the vtxo input created by the branch
|
||||
AddVtxoInput(updater *psetv2.Updater) error
|
||||
// ExpireAt returns the expiration time of the branch
|
||||
ExpireAt() (*time.Time, error)
|
||||
}
|
||||
|
||||
type redeemBranch struct {
|
||||
@@ -25,10 +26,22 @@ type redeemBranch struct {
|
||||
branch []*psetv2.Pset
|
||||
internalKey *secp256k1.PublicKey
|
||||
sweepClosure *taproot.TapElementsLeaf
|
||||
lifetime time.Duration
|
||||
explorer Explorer
|
||||
}
|
||||
|
||||
func newRedeemBranch(ctx *cli.Context, congestionTree tree.CongestionTree, vtxo vtxo) (RedeemBranch, error) {
|
||||
sweepClosure, _, err := findSweepClosure(congestionTree)
|
||||
func newRedeemBranch(
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
congestionTree tree.CongestionTree,
|
||||
vtxo vtxo,
|
||||
) (RedeemBranch, error) {
|
||||
sweepClosure, seconds, err := findSweepClosure(congestionTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lifetime, err := time.ParseDuration(fmt.Sprintf("%ds", seconds))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,43 +71,21 @@ func newRedeemBranch(ctx *cli.Context, congestionTree tree.CongestionTree, vtxo
|
||||
branch: branch,
|
||||
internalKey: internalKey,
|
||||
sweepClosure: sweepClosure,
|
||||
lifetime: lifetime,
|
||||
explorer: explorer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdatePath checks for transactions of the branch onchain and updates the branch accordingly
|
||||
func (r *redeemBranch) UpdatePath() error {
|
||||
for i := len(r.branch) - 1; i >= 0; i-- {
|
||||
pset := r.branch[i]
|
||||
unsignedTx, err := pset.UnsignedTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txHash := unsignedTx.TxHash().String()
|
||||
|
||||
_, err = getTxHex(txHash)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if no error, the tx exists onchain, so we can remove it (+ the parents) from the branch
|
||||
if i == len(r.branch)-1 {
|
||||
r.branch = []*psetv2.Pset{}
|
||||
} else {
|
||||
r.branch = r.branch[i+1:]
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
||||
func (r *redeemBranch) RedeemPath() ([]string, error) {
|
||||
transactions := make([]string, 0, len(r.branch))
|
||||
|
||||
for _, pset := range r.branch {
|
||||
offchainPath, err := r.offchainPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pset := range offchainPath {
|
||||
for i, input := range pset.Inputs {
|
||||
if len(input.TapLeafScript) == 0 {
|
||||
return nil, fmt.Errorf("tap leaf script not found on input #%d", i)
|
||||
@@ -183,3 +174,68 @@ func (r *redeemBranch) AddVtxoInput(updater *psetv2.Updater) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *redeemBranch) ExpireAt() (*time.Time, error) {
|
||||
lastKnownBlocktime := int64(0)
|
||||
|
||||
confirmed, blocktime, _ := getTxBlocktime(r.vtxo.poolTxid)
|
||||
|
||||
if confirmed {
|
||||
lastKnownBlocktime = blocktime
|
||||
} else {
|
||||
expirationFromNow := time.Now().Add(time.Minute).Add(r.lifetime)
|
||||
return &expirationFromNow, nil
|
||||
}
|
||||
|
||||
for _, pset := range r.branch {
|
||||
utx, _ := pset.UnsignedTx()
|
||||
txid := utx.TxHash().String()
|
||||
|
||||
confirmed, blocktime, err := getTxBlocktime(txid)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if confirmed {
|
||||
lastKnownBlocktime = blocktime
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
t := time.Unix(lastKnownBlocktime, 0).Add(r.lifetime)
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// offchainPath checks for transactions of the branch onchain and returns only the offchain part
|
||||
func (r *redeemBranch) offchainPath() ([]*psetv2.Pset, error) {
|
||||
offchainPath := append([]*psetv2.Pset{}, r.branch...)
|
||||
|
||||
for i := len(r.branch) - 1; i >= 0; i-- {
|
||||
pset := r.branch[i]
|
||||
unsignedTx, err := pset.UnsignedTx()
|
||||
if err != nil {
|
||||
fmt.Println("error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txHash := unsignedTx.TxHash().String()
|
||||
|
||||
_, err = r.explorer.GetTxHex(txHash)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if no error, the tx exists onchain, so we can remove it (+ the parents) from the branch
|
||||
if i == len(r.branch)-1 {
|
||||
offchainPath = []*psetv2.Pset{}
|
||||
} else {
|
||||
offchainPath = r.branch[i+1:]
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return offchainPath, nil
|
||||
}
|
||||
|
||||
@@ -341,6 +341,31 @@ func (b *txBuilder) createPoolTx(
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if feeAmount-dust > 0 {
|
||||
newUtxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, feeAmount-dust)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if change > 0 {
|
||||
if change < dustLimit {
|
||||
feeAmount += change
|
||||
} else {
|
||||
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
||||
{
|
||||
Asset: b.net.AssetID,
|
||||
Amount: change,
|
||||
Script: aspScript,
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := addInputs(updater, newUtxos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// add fee output
|
||||
|
||||
Reference in New Issue
Block a user