mirror of
https://github.com/aljazceru/ark.git
synced 2026-01-31 09:04:46 +01:00
Cleanup (#121)
* Cleanup common * Cleanup client * Cleanup server * Renamings * Tidy up proto * Update ocean protos * Fixes * Fixes
This commit is contained in:
committed by
GitHub
parent
1650ea5935
commit
6d0d03e316
@@ -26,16 +26,20 @@ var balanceCommand = cli.Command{
|
||||
func balanceAction(ctx *cli.Context) error {
|
||||
withExpiryDetails := ctx.Bool("expiry-details")
|
||||
|
||||
client, cancel, err := getClientFromState(ctx)
|
||||
client, cancel, err := getClientFromState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
offchainAddr, onchainAddr, err := getAddress()
|
||||
offchainAddr, onchainAddr, redemptionAddr, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, network := getNetwork()
|
||||
// No need to check for error here becuase this function is called also by getAddress().
|
||||
// nolint:all
|
||||
unilateralExitDelay, _ := getUnilateralExitDelay()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
@@ -45,7 +49,7 @@ func balanceAction(ctx *cli.Context) error {
|
||||
defer wg.Done()
|
||||
explorer := NewExplorer()
|
||||
balance, amountByExpiration, err := getOffchainBalance(
|
||||
ctx, explorer, client, offchainAddr, withExpiryDetails,
|
||||
ctx.Context, explorer, client, offchainAddr, withExpiryDetails,
|
||||
)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||
@@ -57,7 +61,8 @@ func balanceAction(ctx *cli.Context) error {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
balance, err := getOnchainBalance(onchainAddr)
|
||||
explorer := NewExplorer()
|
||||
balance, err := explorer.GetBalance(onchainAddr, network.AssetID)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||
return
|
||||
@@ -67,13 +72,17 @@ func balanceAction(ctx *cli.Context) error {
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
availableBalance, futureBalance, err := getOnchainVtxosBalance()
|
||||
explorer := NewExplorer()
|
||||
|
||||
spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance(
|
||||
redemptionAddr, unilateralExitDelay,
|
||||
)
|
||||
if err != nil {
|
||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||
return
|
||||
}
|
||||
|
||||
chRes <- balanceRes{0, availableBalance, futureBalance, nil, err}
|
||||
chRes <- balanceRes{0, spendableBalance, lockedBalance, nil, err}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
@@ -90,11 +99,11 @@ func balanceAction(ctx *cli.Context) error {
|
||||
if res.offchainBalance > 0 {
|
||||
offchainBalance = res.offchainBalance
|
||||
}
|
||||
if res.onchainBalance > 0 {
|
||||
onchainBalance += res.onchainBalance
|
||||
if res.onchainSpendableBalance > 0 {
|
||||
onchainBalance += res.onchainSpendableBalance
|
||||
}
|
||||
if res.amountByExpiration != nil {
|
||||
for timestamp, amount := range res.amountByExpiration {
|
||||
if res.offchainBalanceByExpiration != nil {
|
||||
for timestamp, amount := range res.offchainBalanceByExpiration {
|
||||
if nextExpiration == 0 || timestamp < nextExpiration {
|
||||
nextExpiration = timestamp
|
||||
}
|
||||
@@ -109,8 +118,8 @@ func balanceAction(ctx *cli.Context) error {
|
||||
)
|
||||
}
|
||||
}
|
||||
if res.futureBalance != nil {
|
||||
for timestamp, amount := range res.futureBalance {
|
||||
if res.onchainLockedBalance != nil {
|
||||
for timestamp, amount := range res.onchainLockedBalance {
|
||||
fancyTime := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
|
||||
lockedOnchainBalance = append(
|
||||
lockedOnchainBalance,
|
||||
@@ -177,9 +186,9 @@ func balanceAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
type balanceRes struct {
|
||||
offchainBalance uint64
|
||||
onchainBalance uint64
|
||||
futureBalance map[int64]uint64 // availableAt -> onchain balance
|
||||
amountByExpiration map[int64]uint64 // expireAt -> offchain balance
|
||||
err error
|
||||
offchainBalance uint64
|
||||
onchainSpendableBalance uint64
|
||||
onchainLockedBalance map[int64]uint64
|
||||
offchainBalanceByExpiration map[int64]uint64
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
@@ -21,13 +21,10 @@ type vtxo struct {
|
||||
}
|
||||
|
||||
func getVtxos(
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
client arkv1.ArkServiceClient,
|
||||
addr string,
|
||||
withExpiration bool,
|
||||
ctx context.Context, explorer Explorer, client arkv1.ArkServiceClient,
|
||||
addr string, withExpiration bool,
|
||||
) ([]vtxo, error) {
|
||||
response, err := client.ListVtxos(ctx.Context, &arkv1.ListVtxosRequest{
|
||||
response, err := client.ListVtxos(ctx, &arkv1.ListVtxosRequest{
|
||||
Address: addr,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -54,7 +51,7 @@ func getVtxos(
|
||||
}
|
||||
|
||||
for vtxoTxid, branch := range redeemBranches {
|
||||
expiration, err := branch.ExpireAt()
|
||||
expiration, err := branch.expireAt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,19 +67,19 @@ func getVtxos(
|
||||
return vtxos, nil
|
||||
}
|
||||
|
||||
func getClientFromState(ctx *cli.Context) (arkv1.ArkServiceClient, func(), error) {
|
||||
func getClientFromState() (arkv1.ArkServiceClient, func(), error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addr, ok := state["ark_url"].(string)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("missing ark_url")
|
||||
addr := state[ASP_URL]
|
||||
if len(addr) <= 0 {
|
||||
return nil, nil, fmt.Errorf("missing asp url")
|
||||
}
|
||||
return getClient(ctx, addr)
|
||||
return getClient(addr)
|
||||
}
|
||||
|
||||
func getClient(ctx *cli.Context, addr string) (arkv1.ArkServiceClient, func(), error) {
|
||||
func getClient(addr string) (arkv1.ArkServiceClient, func(), error) {
|
||||
creds := insecure.NewCredentials()
|
||||
port := 80
|
||||
if strings.HasPrefix(addr, "https://") {
|
||||
|
||||
452
client/common.go
452
client/common.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -18,7 +20,6 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vulpemventures/go-elements/address"
|
||||
"github.com/vulpemventures/go-elements/elementsutil"
|
||||
"github.com/vulpemventures/go-elements/network"
|
||||
@@ -44,9 +45,9 @@ func verifyPassword(password []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordHashString, ok := state["password_hash"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("password hash not found")
|
||||
passwordHashString := state[PASSWORD_HASH]
|
||||
if len(passwordHashString) <= 0 {
|
||||
return fmt.Errorf("missing password hash")
|
||||
}
|
||||
|
||||
passwordHash, err := hex.DecodeString(passwordHashString)
|
||||
@@ -84,9 +85,9 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedPrivateKeyString, ok := state["encrypted_private_key"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("encrypted private key not found")
|
||||
encryptedPrivateKeyString := state[ENCRYPTED_PRVKEY]
|
||||
if len(encryptedPrivateKeyString) <= 0 {
|
||||
return nil, fmt.Errorf("missing encrypted private key")
|
||||
}
|
||||
|
||||
encryptedPrivateKey, err := hex.DecodeString(encryptedPrivateKeyString)
|
||||
@@ -100,8 +101,8 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
||||
}
|
||||
fmt.Println("wallet unlocked")
|
||||
|
||||
cypher := NewAES128Cypher()
|
||||
privateKeyBytes, err := cypher.Decrypt(encryptedPrivateKey, password)
|
||||
cypher := newAES128Cypher()
|
||||
privateKeyBytes, err := cypher.decrypt(encryptedPrivateKey, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,9 +117,9 @@ func getWalletPublicKey() (*secp256k1.PublicKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKeyString, ok := state["public_key"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("public key not found")
|
||||
publicKeyString := state[PUBKEY]
|
||||
if len(publicKeyString) <= 0 {
|
||||
return nil, fmt.Errorf("missing public key")
|
||||
}
|
||||
|
||||
publicKeyBytes, err := hex.DecodeString(publicKeyString)
|
||||
@@ -129,15 +130,15 @@ func getWalletPublicKey() (*secp256k1.PublicKey, error) {
|
||||
return secp256k1.ParsePubKey(publicKeyBytes)
|
||||
}
|
||||
|
||||
func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) {
|
||||
func getAspPublicKey() (*secp256k1.PublicKey, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arkPubKey, ok := state["ark_pubkey"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ark public key not found")
|
||||
arkPubKey := state[ASP_PUBKEY]
|
||||
if len(arkPubKey) <= 0 {
|
||||
return nil, fmt.Errorf("missing asp public key")
|
||||
}
|
||||
|
||||
pubKeyBytes, err := hex.DecodeString(arkPubKey)
|
||||
@@ -148,32 +149,41 @@ func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) {
|
||||
return secp256k1.ParsePubKey(pubKeyBytes)
|
||||
}
|
||||
|
||||
func getLifetime() (int64, error) {
|
||||
func getRoundLifetime() (int64, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
lifetime, ok := state["ark_lifetime"].(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("lifetime not found")
|
||||
lifetime := state[ROUND_LIFETIME]
|
||||
if len(lifetime) <= 0 {
|
||||
return -1, fmt.Errorf("missing round lifetime")
|
||||
}
|
||||
|
||||
return int64(lifetime), nil
|
||||
roundLifetime, err := strconv.Atoi(lifetime)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return int64(roundLifetime), nil
|
||||
}
|
||||
|
||||
func getExitDelay() (int64, error) {
|
||||
func getUnilateralExitDelay() (int64, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
exitDelay, ok := state["exit_delay"].(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("exit delay not found")
|
||||
delay := state[UNILATERAL_EXIT_DELAY]
|
||||
if len(delay) <= 0 {
|
||||
return -1, fmt.Errorf("missing unilateral exit delay")
|
||||
}
|
||||
|
||||
return int64(exitDelay), nil
|
||||
redeemDelay, err := strconv.Atoi(delay)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return int64(redeemDelay), nil
|
||||
}
|
||||
|
||||
func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||
@@ -201,7 +211,7 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||
}
|
||||
|
||||
if selectedAmount < amount {
|
||||
return nil, 0, fmt.Errorf("insufficient balance: %d to cover %d", selectedAmount, amount)
|
||||
return nil, 0, fmt.Errorf("not enough funds to cover amount%d", amount)
|
||||
}
|
||||
|
||||
change := selectedAmount - amount
|
||||
@@ -217,7 +227,8 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||
}
|
||||
|
||||
func getOffchainBalance(
|
||||
ctx *cli.Context, explorer Explorer, client arkv1.ArkServiceClient, addr string, withExpiration bool,
|
||||
ctx context.Context, explorer Explorer, client arkv1.ArkServiceClient,
|
||||
addr string, withExpiration bool,
|
||||
) (uint64, map[int64]uint64, error) {
|
||||
amountByExpiration := make(map[int64]uint64, 0)
|
||||
|
||||
@@ -243,120 +254,6 @@ func getOffchainBalance(
|
||||
return balance, amountByExpiration, nil
|
||||
}
|
||||
|
||||
type utxo struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Amount uint64 `json:"value"`
|
||||
Asset string `json:"asset"`
|
||||
Status struct {
|
||||
Confirmed bool `json:"confirmed"`
|
||||
Blocktime int64 `json:"block_time"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
func getOnchainUtxos(addr string) ([]utxo, error) {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", baseUrl, addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf(string(body))
|
||||
}
|
||||
payload := []utxo{}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func getOnchainBalance(addr string) (uint64, error) {
|
||||
payload, err := getOnchainUtxos(addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
balance := uint64(0)
|
||||
for _, p := range payload {
|
||||
if p.Asset != net.AssetID {
|
||||
continue
|
||||
}
|
||||
balance += p.Amount
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func getOnchainVtxosBalance() (availableBalance uint64, futureBalance map[int64]uint64, err error) {
|
||||
userPubKey, err := getWalletPublicKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exitDelay, err := getExitDelay()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
|
||||
payment, err := payment.FromTweakedKey(vtxoTapKey, net, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr, err := payment.TaprootAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utxos, err := getOnchainUtxos(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
availableBalance = uint64(0)
|
||||
futureBalance = make(map[int64]uint64, 0)
|
||||
now := time.Now()
|
||||
for _, utxo := range utxos {
|
||||
blocktime := now
|
||||
if utxo.Status.Confirmed {
|
||||
blocktime = time.Unix(utxo.Status.Blocktime, 0)
|
||||
}
|
||||
|
||||
availableAt := blocktime.Add(time.Duration(exitDelay) * time.Second)
|
||||
if availableAt.After(now) {
|
||||
if _, ok := futureBalance[availableAt.Unix()]; !ok {
|
||||
futureBalance[availableAt.Unix()] = 0
|
||||
}
|
||||
|
||||
futureBalance[availableAt.Unix()] += utxo.Amount
|
||||
} else {
|
||||
availableBalance += utxo.Amount
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
@@ -392,35 +289,13 @@ func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
||||
|
||||
}
|
||||
|
||||
func broadcast(txHex string) (string, error) {
|
||||
_, net := getNetwork()
|
||||
body := bytes.NewBuffer([]byte(txHex))
|
||||
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
resp, err := http.Post(fmt.Sprintf("%s/tx", baseUrl), "text/plain", body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bodyResponse, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf(string(bodyResponse))
|
||||
}
|
||||
|
||||
return string(bodyResponse), nil
|
||||
}
|
||||
|
||||
func getNetwork() (*common.Network, *network.Network) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return &common.TestNet, &network.Testnet
|
||||
}
|
||||
|
||||
net, ok := state["network"]
|
||||
net, ok := state[NETWORK]
|
||||
if !ok {
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
@@ -430,30 +305,54 @@ func getNetwork() (*common.Network, *network.Network) {
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
|
||||
func getAddress() (offchainAddr, onchainAddr string, err error) {
|
||||
publicKey, err := getWalletPublicKey()
|
||||
func getAddress() (offchainAddr, onchainAddr, redemptionAddr string, err error) {
|
||||
userPubkey, err := getWalletPublicKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
aspPubkey, err := getAspPublicKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
arkNet, liquidNet := getNetwork()
|
||||
|
||||
arkAddr, err := common.EncodeAddress(arkNet.Addr, publicKey, aspPublicKey)
|
||||
arkAddr, err := common.EncodeAddress(arkNet.Addr, userPubkey, aspPubkey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p2wpkh := payment.FromPublicKey(publicKey, liquidNet, nil)
|
||||
p2wpkh := payment.FromPublicKey(userPubkey, liquidNet, nil)
|
||||
liquidAddr, err := p2wpkh.WitnessPubKeyHash()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, net := getNetwork()
|
||||
|
||||
payment, err := payment.FromTweakedKey(vtxoTapKey, net, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
redemptionAddr, err = payment.TaprootAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
offchainAddr = arkAddr
|
||||
onchainAddr = liquidAddr
|
||||
|
||||
@@ -471,14 +370,10 @@ func printJSON(resp interface{}) error {
|
||||
}
|
||||
|
||||
func handleRoundStream(
|
||||
ctx *cli.Context,
|
||||
client arkv1.ArkServiceClient,
|
||||
paymentID string,
|
||||
vtxosToSign []vtxo,
|
||||
secKey *secp256k1.PrivateKey,
|
||||
receivers []*arkv1.Output,
|
||||
ctx context.Context, client arkv1.ArkServiceClient, paymentID string,
|
||||
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
||||
) (poolTxID string, err error) {
|
||||
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
||||
stream, err := client.GetEventStream(ctx, &arkv1.GetEventStreamRequest{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -502,54 +397,53 @@ func handleRoundStream(
|
||||
return "", err
|
||||
}
|
||||
|
||||
if event.GetRoundFailed() != nil {
|
||||
if e := event.GetRoundFailed(); e != nil {
|
||||
pingStop()
|
||||
return "", fmt.Errorf("round failed: %s", event.GetRoundFailed().GetReason())
|
||||
return "", fmt.Errorf("round failed: %s", e.GetReason())
|
||||
}
|
||||
|
||||
if event.GetRoundFinalization() != nil {
|
||||
if e := event.GetRoundFinalization(); e != nil {
|
||||
// stop pinging as soon as we receive some forfeit txs
|
||||
pingStop()
|
||||
fmt.Println("round finalization started")
|
||||
|
||||
poolPartialTx := event.GetRoundFinalization().GetPoolPartialTx()
|
||||
poolTransaction, err := psetv2.NewPsetFromBase64(poolPartialTx)
|
||||
poolTxStr := e.GetPoolPartialTx()
|
||||
poolTx, err := psetv2.NewPsetFromBase64(poolTxStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
congestionTree, err := toCongestionTree(event.GetRoundFinalization().GetCongestionTree())
|
||||
congestionTree, err := toCongestionTree(e.GetCongestionTree())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
aspPubkey, err := getAspPublicKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
seconds, err := getLifetime()
|
||||
roundLifetime, err := getRoundLifetime()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// validate the congestion tree
|
||||
if err := tree.ValidateCongestionTree(
|
||||
congestionTree,
|
||||
poolPartialTx,
|
||||
aspPublicKey,
|
||||
int64(seconds),
|
||||
congestionTree, poolTxStr, aspPubkey, int64(roundLifetime),
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
exitDelay, err := getExitDelay()
|
||||
exitDelay, err := getUnilateralExitDelay()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, receiver := range receivers {
|
||||
isOnChain, onchainScript, userPubKey, err := decodeReceiverAddress(receiver.Address)
|
||||
isOnChain, onchainScript, userPubkey, err := decodeReceiverAddress(
|
||||
receiver.Address,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -558,10 +452,13 @@ func handleRoundStream(
|
||||
// collaborative exit case
|
||||
// search for the output in the pool tx
|
||||
found := false
|
||||
for _, output := range poolTransaction.Outputs {
|
||||
for _, output := range poolTx.Outputs {
|
||||
if bytes.Equal(output.Script, onchainScript) {
|
||||
if output.Value != receiver.Amount {
|
||||
return "", fmt.Errorf("invalid collaborative exit output amount: got %d, want %d", output.Value, receiver.Amount)
|
||||
return "", fmt.Errorf(
|
||||
"invalid collaborative exit output amount: got %d, want %d",
|
||||
output.Value, receiver.Amount,
|
||||
)
|
||||
}
|
||||
|
||||
found = true
|
||||
@@ -570,7 +467,9 @@ func handleRoundStream(
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("collaborative exit output not found: %s", receiver.Address)
|
||||
return "", fmt.Errorf(
|
||||
"collaborative exit output not found: %s", receiver.Address,
|
||||
)
|
||||
}
|
||||
|
||||
continue
|
||||
@@ -581,7 +480,9 @@ func handleRoundStream(
|
||||
found := false
|
||||
|
||||
// compute the receiver output taproot key
|
||||
outputTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
||||
outputTapKey, _, err := computeVtxoTaprootScript(
|
||||
userPubkey, aspPubkey, uint(exitDelay),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -597,7 +498,9 @@ func handleRoundStream(
|
||||
if len(output.Script) == 0 {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(output.Script[2:], schnorr.SerializePubKey(outputTapKey)) {
|
||||
if bytes.Equal(
|
||||
output.Script[2:], schnorr.SerializePubKey(outputTapKey),
|
||||
) {
|
||||
if output.Value != receiver.Amount {
|
||||
continue
|
||||
}
|
||||
@@ -613,13 +516,15 @@ func handleRoundStream(
|
||||
}
|
||||
|
||||
if !found {
|
||||
return "", fmt.Errorf("off-chain send output not found: %s", receiver.Address)
|
||||
return "", fmt.Errorf(
|
||||
"off-chain send output not found: %s", receiver.Address,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("congestion tree validated")
|
||||
|
||||
forfeits := event.GetRoundFinalization().GetForfeitTxs()
|
||||
forfeits := e.GetForfeitTxs()
|
||||
signedForfeits := make([]string, 0)
|
||||
|
||||
fmt.Print("signing forfeit txs... ")
|
||||
@@ -665,7 +570,7 @@ func handleRoundStream(
|
||||
|
||||
fmt.Printf("%d signed\n", len(signedForfeits))
|
||||
fmt.Print("finalizing payment... ")
|
||||
_, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{
|
||||
_, err = client.FinalizePayment(ctx, &arkv1.FinalizePaymentRequest{
|
||||
SignedForfeitTxs: signedForfeits,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -687,8 +592,10 @@ func handleRoundStream(
|
||||
|
||||
// send 1 ping message every 5 seconds to signal to the ark service that we are still alive
|
||||
// returns a function that can be used to stop the pinging
|
||||
func ping(ctx *cli.Context, client arkv1.ArkServiceClient, req *arkv1.PingRequest) func() {
|
||||
_, err := client.Ping(ctx.Context, req)
|
||||
func ping(
|
||||
ctx context.Context, client arkv1.ArkServiceClient, req *arkv1.PingRequest,
|
||||
) func() {
|
||||
_, err := client.Ping(ctx, req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -698,7 +605,7 @@ func ping(ctx *cli.Context, client arkv1.ArkServiceClient, req *arkv1.PingReques
|
||||
go func(t *time.Ticker) {
|
||||
for range t.C {
|
||||
// nolint
|
||||
client.Ping(ctx.Context, req)
|
||||
client.Ping(ctx, req)
|
||||
}
|
||||
}(ticker)
|
||||
|
||||
@@ -758,18 +665,15 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
||||
}
|
||||
|
||||
func decodeReceiverAddress(addr string) (
|
||||
isOnChainAddress bool,
|
||||
onchainScript []byte,
|
||||
userPubKey *secp256k1.PublicKey,
|
||||
err error,
|
||||
bool, []byte, *secp256k1.PublicKey, error,
|
||||
) {
|
||||
outputScript, err := address.ToOutputScript(addr)
|
||||
if err != nil {
|
||||
_, userPubKey, _, err = common.DecodeAddress(addr)
|
||||
_, userPubkey, _, err := common.DecodeAddress(addr)
|
||||
if err != nil {
|
||||
return
|
||||
return false, nil, nil, err
|
||||
}
|
||||
return false, nil, userPubKey, nil
|
||||
return false, nil, userPubkey, nil
|
||||
}
|
||||
|
||||
return true, outputScript, nil, nil
|
||||
@@ -777,18 +681,20 @@ func decodeReceiverAddress(addr string) (
|
||||
|
||||
func findSweepClosure(
|
||||
congestionTree tree.CongestionTree,
|
||||
) (sweepClosure *taproot.TapElementsLeaf, seconds uint, err error) {
|
||||
) (*taproot.TapElementsLeaf, uint, error) {
|
||||
root, err := congestionTree.Root()
|
||||
if err != nil {
|
||||
return
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// find the sweep closure
|
||||
tx, err := psetv2.NewPsetFromBase64(root.Tx)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var seconds uint
|
||||
var sweepClosure *taproot.TapElementsLeaf
|
||||
for _, tapLeaf := range tx.Inputs[0].TapLeafScript {
|
||||
closure := &tree.CSVSigClosure{}
|
||||
valid, err := closure.Decode(tapLeaf.Script)
|
||||
@@ -806,21 +712,19 @@ func findSweepClosure(
|
||||
return nil, 0, fmt.Errorf("sweep closure not found")
|
||||
}
|
||||
|
||||
return
|
||||
return sweepClosure, seconds, nil
|
||||
}
|
||||
|
||||
func getRedeemBranches(
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
client arkv1.ArkServiceClient,
|
||||
ctx context.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
|
||||
) (map[string]*redeemBranch, error) {
|
||||
congestionTrees := make(map[string]tree.CongestionTree, 0)
|
||||
redeemBranches := make(map[string]*redeemBranch, 0)
|
||||
|
||||
for _, vtxo := range vtxos {
|
||||
if _, ok := congestionTrees[vtxo.poolTxid]; !ok {
|
||||
round, err := client.GetRound(ctx.Context, &arkv1.GetRoundRequest{
|
||||
round, err := client.GetRound(ctx, &arkv1.GetRoundRequest{
|
||||
Txid: vtxo.poolTxid,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -836,7 +740,9 @@ func getRedeemBranches(
|
||||
congestionTrees[vtxo.poolTxid] = congestionTree
|
||||
}
|
||||
|
||||
redeemBranch, err := newRedeemBranch(ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo)
|
||||
redeemBranch, err := newRedeemBranch(
|
||||
ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -848,18 +754,16 @@ func getRedeemBranches(
|
||||
}
|
||||
|
||||
func computeVtxoTaprootScript(
|
||||
userPubKey *secp256k1.PublicKey,
|
||||
aspPublicKey *secp256k1.PublicKey,
|
||||
exitDelay uint,
|
||||
userPubkey, aspPubkey *secp256k1.PublicKey, exitDelay uint,
|
||||
) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, error) {
|
||||
redeemClosure := &tree.CSVSigClosure{
|
||||
Pubkey: userPubKey,
|
||||
Pubkey: userPubkey,
|
||||
Seconds: exitDelay,
|
||||
}
|
||||
|
||||
forfeitClosure := &tree.ForfeitClosure{
|
||||
Pubkey: userPubKey,
|
||||
AspPubkey: aspPublicKey,
|
||||
Pubkey: userPubkey,
|
||||
AspPubkey: aspPubkey,
|
||||
}
|
||||
|
||||
redeemLeaf, err := redeemClosure.Leaf()
|
||||
@@ -872,7 +776,9 @@ func computeVtxoTaprootScript(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(*redeemLeaf, *forfeitLeaf)
|
||||
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(
|
||||
*redeemLeaf, *forfeitLeaf,
|
||||
)
|
||||
root := vtxoTaprootTree.RootNode.TapHash()
|
||||
|
||||
unspendableKey := tree.UnspendableKey()
|
||||
@@ -886,9 +792,7 @@ func computeVtxoTaprootScript(
|
||||
}
|
||||
|
||||
func addVtxoInput(
|
||||
updater *psetv2.Updater,
|
||||
inputArgs psetv2.InputArgs,
|
||||
exitDelay uint,
|
||||
updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint,
|
||||
tapLeafProof *taproot.TapscriptElementsProof,
|
||||
) error {
|
||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
||||
@@ -912,18 +816,20 @@ func addVtxoInput(
|
||||
)
|
||||
}
|
||||
|
||||
func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delayedUtxos []utxo, change uint64, err error) {
|
||||
_, onchainAddr, err := getAddress()
|
||||
func coinSelectOnchain(
|
||||
explorer Explorer, targetAmount uint64, exclude []utxo,
|
||||
) ([]utxo, []utxo, uint64, error) {
|
||||
_, onchainAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
fromExplorer, err := getOnchainUtxos(onchainAddr)
|
||||
fromExplorer, err := explorer.GetUtxos(onchainAddr)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
utxos = make([]utxo, 0)
|
||||
utxos := make([]utxo, 0)
|
||||
selectedAmount := uint64(0)
|
||||
for _, utxo := range fromExplorer {
|
||||
if selectedAmount >= targetAmount {
|
||||
@@ -944,22 +850,24 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
||||
return utxos, nil, selectedAmount - targetAmount, nil
|
||||
}
|
||||
|
||||
userPubKey, err := getWalletPublicKey()
|
||||
userPubkey, err := getWalletPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
aspPubkey, err := getAspPublicKey()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
exitDelay, err := getExitDelay()
|
||||
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
@@ -976,18 +884,20 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
fromExplorer, err = getOnchainUtxos(addr)
|
||||
fromExplorer, err = explorer.GetUtxos(addr)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
delayedUtxos = make([]utxo, 0)
|
||||
delayedUtxos := make([]utxo, 0)
|
||||
for _, utxo := range fromExplorer {
|
||||
if selectedAmount >= targetAmount {
|
||||
break
|
||||
}
|
||||
|
||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(time.Duration(exitDelay) * time.Second)
|
||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
||||
time.Duration(unilateralExitDelay) * time.Second,
|
||||
)
|
||||
if availableAt.After(time.Now()) {
|
||||
continue
|
||||
}
|
||||
@@ -1003,19 +913,18 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
||||
}
|
||||
|
||||
if selectedAmount < targetAmount {
|
||||
return nil, nil, 0, fmt.Errorf("insufficient balance: %d to cover %d", selectedAmount, targetAmount)
|
||||
return nil, nil, 0, fmt.Errorf(
|
||||
"not enough funds to cover amount %d", targetAmount,
|
||||
)
|
||||
}
|
||||
|
||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
||||
}
|
||||
|
||||
func addInputs(
|
||||
updater *psetv2.Updater,
|
||||
selected []utxo, // the utxos to add owned by the P2WPKH script
|
||||
delayedSelected []utxo, // the utxos to add owned by the VTXO script
|
||||
net *network.Network,
|
||||
updater *psetv2.Updater, utxos, delayedUtxos []utxo, net *network.Network,
|
||||
) error {
|
||||
_, onchainAddr, err := getAddress()
|
||||
_, onchainAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1025,23 +934,22 @@ func addInputs(
|
||||
return err
|
||||
}
|
||||
|
||||
for _, coin := range selected {
|
||||
fmt.Println("adding input", coin.Txid, coin.Vout)
|
||||
for _, utxo := range utxos {
|
||||
if err := updater.AddInputs([]psetv2.InputArgs{
|
||||
{
|
||||
Txid: coin.Txid,
|
||||
TxIndex: coin.Vout,
|
||||
Txid: utxo.Txid,
|
||||
TxIndex: utxo.Vout,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assetID, err := elementsutil.AssetHashToBytes(coin.Asset)
|
||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value, err := elementsutil.ValueToBytes(coin.Amount)
|
||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1053,28 +961,32 @@ func addInputs(
|
||||
Nonce: []byte{0x00},
|
||||
}
|
||||
|
||||
if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil {
|
||||
if err := updater.AddInWitnessUtxo(
|
||||
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(delayedSelected) > 0 {
|
||||
userPubKey, err := getWalletPublicKey()
|
||||
if len(delayedUtxos) > 0 {
|
||||
userPubkey, err := getWalletPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
aspPubkey, err := getAspPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exitDelay, err := getExitDelay()
|
||||
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
||||
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(
|
||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1094,25 +1006,25 @@ func addInputs(
|
||||
return err
|
||||
}
|
||||
|
||||
for _, coin := range delayedSelected {
|
||||
for _, utxo := range delayedUtxos {
|
||||
if err := addVtxoInput(
|
||||
updater,
|
||||
psetv2.InputArgs{
|
||||
Txid: coin.Txid,
|
||||
TxIndex: coin.Vout,
|
||||
Txid: utxo.Txid,
|
||||
TxIndex: utxo.Vout,
|
||||
},
|
||||
uint(exitDelay),
|
||||
uint(unilateralExitDelay),
|
||||
leafProof,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
assetID, err := elementsutil.AssetHashToBytes(coin.Asset)
|
||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value, err := elementsutil.ValueToBytes(coin.Amount)
|
||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1124,7 +1036,9 @@ func addInputs(
|
||||
Nonce: []byte{0x00},
|
||||
}
|
||||
|
||||
if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil {
|
||||
if err := updater.AddInWitnessUtxo(
|
||||
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
type Cypher struct{}
|
||||
type cypher struct{}
|
||||
|
||||
func NewAES128Cypher() *Cypher {
|
||||
return &Cypher{}
|
||||
func newAES128Cypher() *cypher {
|
||||
return &cypher{}
|
||||
}
|
||||
|
||||
func (c *Cypher) Encrypt(privateKey, password []byte) ([]byte, error) {
|
||||
func (c *cypher) encrypt(privateKey, password []byte) ([]byte, error) {
|
||||
// Due to https://github.com/golang/go/issues/7168.
|
||||
// This call makes sure that memory is freed in case the GC doesn't do that
|
||||
// right after the encryption/decryption.
|
||||
@@ -53,7 +53,7 @@ func (c *Cypher) Encrypt(privateKey, password []byte) ([]byte, error) {
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (c *Cypher) Decrypt(encrypted, password []byte) ([]byte, error) {
|
||||
func (c *cypher) decrypt(encrypted, password []byte) ([]byte, error) {
|
||||
defer debug.FreeOSMemory()
|
||||
|
||||
if len(encrypted) == 0 {
|
||||
|
||||
@@ -19,6 +19,6 @@ func dumpAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
return printJSON(map[string]interface{}{
|
||||
"privateKey": hex.EncodeToString(privateKey.Serialize()),
|
||||
"private_key": hex.EncodeToString(privateKey.Serialize()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vulpemventures/go-elements/psetv2"
|
||||
"github.com/vulpemventures/go-elements/transaction"
|
||||
)
|
||||
|
||||
type utxo struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Amount uint64 `json:"value"`
|
||||
Asset string `json:"asset"`
|
||||
Status struct {
|
||||
Confirmed bool `json:"confirmed"`
|
||||
Blocktime int64 `json:"block_time"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
type Explorer interface {
|
||||
GetTxHex(txid string) (string, error)
|
||||
Broadcast(txHex string) (string, error)
|
||||
GetUtxos(addr string) ([]utxo, error)
|
||||
GetBalance(addr, asset string) (uint64, error)
|
||||
GetRedeemedVtxosBalance(
|
||||
addr string, unilateralExitDelay int64,
|
||||
) (uint64, map[int64]uint64, error)
|
||||
}
|
||||
|
||||
type explorer struct {
|
||||
@@ -44,17 +64,28 @@ func (e *explorer) GetTxHex(txid string) (string, error) {
|
||||
return txHex, nil
|
||||
}
|
||||
|
||||
func (e *explorer) Broadcast(txHex string) (string, error) {
|
||||
tx, err := transaction.NewTxFromHex(txHex)
|
||||
func (e *explorer) Broadcast(txStr string) (string, error) {
|
||||
tx, err := transaction.NewTxFromHex(txStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
pset, err := psetv2.NewPsetFromBase64(txStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
extracted, err := psetv2.Extract(pset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
txStr, _ = extracted.ToHex()
|
||||
}
|
||||
txid := tx.TxHash().String()
|
||||
e.cache[txid] = txHex
|
||||
e.cache[txid] = txStr
|
||||
|
||||
txid, err = broadcast(txHex)
|
||||
txid, err = e.broadcast(txStr)
|
||||
if err != nil {
|
||||
if strings.Contains(strings.ToLower(err.Error()), "transaction already in block chain") {
|
||||
if strings.Contains(
|
||||
strings.ToLower(err.Error()), "transaction already in block chain",
|
||||
) {
|
||||
return txid, nil
|
||||
}
|
||||
|
||||
@@ -64,6 +95,76 @@ func (e *explorer) Broadcast(txHex string) (string, error) {
|
||||
return txid, nil
|
||||
}
|
||||
|
||||
func (e *explorer) GetUtxos(addr string) ([]utxo, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf(string(body))
|
||||
}
|
||||
payload := []utxo{}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (e *explorer) GetBalance(addr, asset string) (uint64, error) {
|
||||
payload, err := e.GetUtxos(addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
balance := uint64(0)
|
||||
for _, p := range payload {
|
||||
if p.Asset != asset {
|
||||
continue
|
||||
}
|
||||
balance += p.Amount
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func (e *explorer) GetRedeemedVtxosBalance(
|
||||
addr string, unilateralExitDelay int64,
|
||||
) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) {
|
||||
utxos, err := e.GetUtxos(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lockedBalance = make(map[int64]uint64, 0)
|
||||
now := time.Now()
|
||||
for _, utxo := range utxos {
|
||||
blocktime := now
|
||||
if utxo.Status.Confirmed {
|
||||
blocktime = time.Unix(utxo.Status.Blocktime, 0)
|
||||
}
|
||||
|
||||
delay := time.Duration(unilateralExitDelay) * time.Second
|
||||
availableAt := blocktime.Add(delay)
|
||||
if availableAt.After(now) {
|
||||
if _, ok := lockedBalance[availableAt.Unix()]; !ok {
|
||||
lockedBalance[availableAt.Unix()] = 0
|
||||
}
|
||||
|
||||
lockedBalance[availableAt.Unix()] += utxo.Amount
|
||||
} else {
|
||||
spendableBalance += utxo.Amount
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *explorer) getTxHex(txid string) (string, error) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", e.baseUrl, txid))
|
||||
if err != nil {
|
||||
@@ -83,3 +184,23 @@ func (e *explorer) getTxHex(txid string) (string, error) {
|
||||
e.cache[txid] = hex
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
func (e *explorer) broadcast(txHex string) (string, error) {
|
||||
body := bytes.NewBuffer([]byte(txHex))
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("%s/tx", e.baseUrl), "text/plain", body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bodyResponse, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf(string(bodyResponse))
|
||||
}
|
||||
|
||||
return string(bodyResponse), nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
@@ -57,7 +59,7 @@ func initAction(ctx *cli.Context) error {
|
||||
return fmt.Errorf("invalid network")
|
||||
}
|
||||
|
||||
if err := connectToAsp(ctx, net, url); err != nil {
|
||||
if err := connectToAsp(ctx.Context, net, url); err != nil {
|
||||
return err
|
||||
}
|
||||
return initWallet(ctx, key, password)
|
||||
@@ -71,24 +73,24 @@ func generateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func connectToAsp(ctx *cli.Context, net, url string) error {
|
||||
client, close, err := getClient(ctx, url)
|
||||
func connectToAsp(ctx context.Context, net, url string) error {
|
||||
client, close, err := getClient(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close()
|
||||
|
||||
resp, err := client.GetInfo(ctx.Context, &arkv1.GetInfoRequest{})
|
||||
resp, err := client.GetInfo(ctx, &arkv1.GetInfoRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setState(map[string]interface{}{
|
||||
"ark_url": url,
|
||||
"network": net,
|
||||
"ark_pubkey": resp.Pubkey,
|
||||
"ark_lifetime": resp.Lifetime,
|
||||
"exit_delay": resp.ExitDelay,
|
||||
return setState(map[string]string{
|
||||
ASP_URL: url,
|
||||
NETWORK: net,
|
||||
ASP_PUBKEY: resp.Pubkey,
|
||||
ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())),
|
||||
UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -109,17 +111,20 @@ func initWallet(ctx *cli.Context, key, password string) error {
|
||||
privateKey = secp256k1.PrivKeyFromBytes(privKeyBytes)
|
||||
}
|
||||
|
||||
encryptedPrivateKey, err := NewAES128Cypher().Encrypt(privateKey.Serialize(), []byte(password))
|
||||
cypher := newAES128Cypher()
|
||||
buf := privateKey.Serialize()
|
||||
encryptedPrivateKey, err := cypher.encrypt(buf, []byte(password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordHash := hashPassword([]byte(password))
|
||||
|
||||
state := map[string]interface{}{
|
||||
"encrypted_private_key": hex.EncodeToString(encryptedPrivateKey),
|
||||
"password_hash": hex.EncodeToString(passwordHash),
|
||||
"public_key": hex.EncodeToString(privateKey.PubKey().SerializeCompressed()),
|
||||
pubkey := privateKey.PubKey().SerializeCompressed()
|
||||
state := map[string]string{
|
||||
ENCRYPTED_PRVKEY: hex.EncodeToString(encryptedPrivateKey),
|
||||
PASSWORD_HASH: hex.EncodeToString(passwordHash),
|
||||
PUBKEY: hex.EncodeToString(pubkey),
|
||||
}
|
||||
|
||||
if err := setState(state); err != nil {
|
||||
|
||||
@@ -17,6 +17,15 @@ const (
|
||||
DATADIR_ENVVAR = "ARK_WALLET_DATADIR"
|
||||
STATE_FILE = "state.json"
|
||||
defaultNetwork = "testnet"
|
||||
|
||||
ASP_URL = "asp_url"
|
||||
ASP_PUBKEY = "asp_public_key"
|
||||
ROUND_LIFETIME = "round_lifetime"
|
||||
UNILATERAL_EXIT_DELAY = "unilateral_exit_delay "
|
||||
ENCRYPTED_PRVKEY = "encrypted_private_key "
|
||||
PASSWORD_HASH = "password_hash "
|
||||
PUBKEY = "public_key "
|
||||
NETWORK = "network "
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -29,14 +38,15 @@ var (
|
||||
network.Testnet.Name: "https://blockstream.info/liquidtestnet/api",
|
||||
}
|
||||
|
||||
initialState = map[string]interface{}{
|
||||
"ark_url": "",
|
||||
"ark_pubkey": "",
|
||||
"ark_lifetime": 0,
|
||||
"encrypted_private_key": "",
|
||||
"password_hash": "",
|
||||
"public_key": "",
|
||||
"network": defaultNetwork,
|
||||
initialState = map[string]string{
|
||||
ASP_URL: "",
|
||||
ASP_PUBKEY: "",
|
||||
ROUND_LIFETIME: "",
|
||||
UNILATERAL_EXIT_DELAY: "",
|
||||
ENCRYPTED_PRVKEY: "",
|
||||
PASSWORD_HASH: "",
|
||||
PUBKEY: "",
|
||||
NETWORK: defaultNetwork,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -110,7 +120,7 @@ func cleanAndExpandPath(path string) string {
|
||||
return filepath.Clean(os.ExpandEnv(path))
|
||||
}
|
||||
|
||||
func getState() (map[string]interface{}, error) {
|
||||
func getState() (map[string]string, error) {
|
||||
file, err := os.ReadFile(statePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
@@ -122,7 +132,7 @@ func getState() (map[string]interface{}, error) {
|
||||
return initialState, nil
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
data := map[string]string{}
|
||||
if err := json.Unmarshal(file, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -138,7 +148,7 @@ func setInitialState() error {
|
||||
return os.WriteFile(statePath, jsonString, 0755)
|
||||
}
|
||||
|
||||
func setState(data map[string]interface{}) error {
|
||||
func setState(data map[string]string) error {
|
||||
currentData, err := getState()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -158,8 +168,8 @@ func setState(data map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func merge(maps ...map[string]interface{}) map[string]interface{} {
|
||||
merge := make(map[string]interface{}, 0)
|
||||
func merge(maps ...map[string]string) map[string]string {
|
||||
merge := make(map[string]string, 0)
|
||||
for _, m := range maps {
|
||||
for k, v := range m {
|
||||
merge[k] = v
|
||||
|
||||
@@ -40,17 +40,17 @@ func onboardAction(ctx *cli.Context) error {
|
||||
|
||||
_, net := getNetwork()
|
||||
|
||||
aspPubkey, err := getServiceProviderPublicKey()
|
||||
aspPubkey, err := getAspPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lifetime, err := getLifetime()
|
||||
roundLifetime, err := getRoundLifetime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exitDelay, err := getExitDelay()
|
||||
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -66,7 +66,8 @@ func onboardAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
||||
net.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf}, minRelayFee, lifetime, exitDelay,
|
||||
net.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf},
|
||||
minRelayFee, roundLifetime, unilateralExitDelay,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -92,13 +93,14 @@ func onboardAction(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
txid, err := broadcastPset(pset)
|
||||
explorer := NewExplorer()
|
||||
txid, err := explorer.Broadcast(pset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("onboard txid:", txid)
|
||||
fmt.Println("waiting for confirmation... (this may take a while, do not cancel the process)")
|
||||
fmt.Println("onboard_txid:", txid)
|
||||
fmt.Println("waiting for confirmation... (this may take up to a minute, do not cancel the process)")
|
||||
|
||||
// wait for the transaction to be confirmed
|
||||
if err := waitForTxConfirmation(ctx, txid); err != nil {
|
||||
@@ -116,11 +118,11 @@ func onboardAction(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
client, close, err := getClientFromState(ctx)
|
||||
client, cancel, err := getClientFromState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close()
|
||||
defer cancel()
|
||||
|
||||
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
||||
BoardingTx: pset,
|
||||
|
||||
@@ -11,7 +11,7 @@ var receiveCommand = cli.Command{
|
||||
}
|
||||
|
||||
func receiveAction(ctx *cli.Context) error {
|
||||
offchainAddr, onchainAddr, err := getAddress()
|
||||
offchainAddr, onchainAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -56,18 +57,26 @@ func redeemAction(ctx *cli.Context) error {
|
||||
return fmt.Errorf("missing amount flag (--amount)")
|
||||
}
|
||||
|
||||
client, clean, err := getClientFromState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer clean()
|
||||
|
||||
if force {
|
||||
if amount > 0 {
|
||||
fmt.Printf("WARNING: unilateral exit (--force) ignores --amount flag, it will redeem all your VTXOs\n")
|
||||
}
|
||||
|
||||
return unilateralRedeem(ctx)
|
||||
return unilateralRedeem(client, ctx.Context)
|
||||
}
|
||||
|
||||
return collaborativeRedeem(ctx, addr, amount)
|
||||
return collaborativeRedeem(client, ctx.Context, addr, amount)
|
||||
}
|
||||
|
||||
func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
func collaborativeRedeem(
|
||||
client arkv1.ArkServiceClient, ctx context.Context, addr string, amount uint64,
|
||||
) error {
|
||||
if _, err := address.ToOutputScript(addr); err != nil {
|
||||
return fmt.Errorf("invalid onchain address")
|
||||
}
|
||||
@@ -86,7 +95,7 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
addr = info.Address
|
||||
}
|
||||
|
||||
offchainAddr, _, err := getAddress()
|
||||
offchainAddr, _, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -98,12 +107,6 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
},
|
||||
}
|
||||
|
||||
client, close, err := getClientFromState(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close()
|
||||
|
||||
explorer := NewExplorer()
|
||||
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
||||
@@ -137,14 +140,14 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
registerResponse, err := client.RegisterPayment(ctx.Context, &arkv1.RegisterPaymentRequest{
|
||||
registerResponse, err := client.RegisterPayment(ctx, &arkv1.RegisterPaymentRequest{
|
||||
Inputs: inputs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{
|
||||
_, err = client.ClaimPayment(ctx, &arkv1.ClaimPaymentRequest{
|
||||
Id: registerResponse.GetId(),
|
||||
Outputs: receivers,
|
||||
})
|
||||
@@ -173,14 +176,8 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unilateralRedeem(ctx *cli.Context) error {
|
||||
client, close, err := getClientFromState(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close()
|
||||
|
||||
offchainAddr, _, err := getAddress()
|
||||
func unilateralRedeem(client arkv1.ArkServiceClient, ctx context.Context) error {
|
||||
offchainAddr, _, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,7 +209,7 @@ func unilateralRedeem(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
for _, branch := range redeemBranches {
|
||||
branchTxs, err := branch.RedeemPath()
|
||||
branchTxs, err := branch.redeemPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type receiver struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
}
|
||||
|
||||
func (r *receiver) IsOnchain() bool {
|
||||
func (r *receiver) isOnchain() bool {
|
||||
_, err := address.ToOutputScript(r.To)
|
||||
return err == nil
|
||||
}
|
||||
@@ -75,20 +75,22 @@ func sendAction(ctx *cli.Context) error {
|
||||
offchainReceivers := make([]receiver, 0)
|
||||
|
||||
for _, receiver := range receiversJSON {
|
||||
if receiver.IsOnchain() {
|
||||
if receiver.isOnchain() {
|
||||
onchainReceivers = append(onchainReceivers, receiver)
|
||||
} else {
|
||||
offchainReceivers = append(offchainReceivers, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
explorer := NewExplorer()
|
||||
|
||||
if len(onchainReceivers) > 0 {
|
||||
pset, err := sendOnchain(ctx, onchainReceivers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txid, err := broadcastPset(pset)
|
||||
txid, err := explorer.Broadcast(pset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -108,7 +110,7 @@ func sendAction(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
offchainAddr, _, err := getAddress()
|
||||
offchainAddr, _, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,7 +129,9 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
return fmt.Errorf("invalid receiver address: %s", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed()) {
|
||||
if !bytes.Equal(
|
||||
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
||||
) {
|
||||
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To)
|
||||
}
|
||||
|
||||
@@ -141,7 +145,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
})
|
||||
sumOfReceivers += receiver.Amount
|
||||
}
|
||||
client, close, err := getClientFromState(ctx)
|
||||
client, close, err := getClientFromState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -149,7 +153,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
|
||||
explorer := NewExplorer()
|
||||
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
||||
vtxos, err := getVtxos(ctx.Context, explorer, client, offchainAddr, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -181,9 +185,9 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
return err
|
||||
}
|
||||
|
||||
registerResponse, err := client.RegisterPayment(ctx.Context, &arkv1.RegisterPaymentRequest{
|
||||
Inputs: inputs,
|
||||
})
|
||||
registerResponse, err := client.RegisterPayment(
|
||||
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -197,12 +201,8 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
}
|
||||
|
||||
poolTxID, err := handleRoundStream(
|
||||
ctx,
|
||||
client,
|
||||
registerResponse.GetId(),
|
||||
selectedCoins,
|
||||
secKey,
|
||||
receiversOutput,
|
||||
ctx.Context, client, registerResponse.GetId(),
|
||||
selectedCoins, secKey, receiversOutput,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -248,17 +248,21 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
selected, delayedSelected, change, err := coinSelectOnchain(targetAmount, nil)
|
||||
explorer := NewExplorer()
|
||||
|
||||
utxos, delayedUtxos, change, err := coinSelectOnchain(
|
||||
explorer, targetAmount, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
|
||||
if err := addInputs(updater, utxos, delayedUtxos, net); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if change > 0 {
|
||||
_, changeAddr, err := getAddress()
|
||||
_, changeAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -297,8 +301,7 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
}
|
||||
// reselect the difference
|
||||
selected, delayedSelected, newChange, err := coinSelectOnchain(
|
||||
feeAmount-change,
|
||||
append(selected, delayedSelected...),
|
||||
explorer, feeAmount-change, append(utxos, delayedUtxos...),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -309,7 +312,7 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
}
|
||||
|
||||
if newChange > 0 {
|
||||
_, changeAddr, err := getAddress()
|
||||
_, changeAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -345,8 +348,6 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
explorer := NewExplorer()
|
||||
|
||||
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -357,22 +358,3 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
|
||||
return updater.Pset.ToBase64()
|
||||
}
|
||||
|
||||
func broadcastPset(psetB64 string) (string, error) {
|
||||
pset, err := psetv2.NewPsetFromBase64(psetB64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
extracted, err := psetv2.Extract(pset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hex, err := extracted.ToHex()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return NewExplorer().Broadcast(hex)
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ import (
|
||||
)
|
||||
|
||||
func signPset(
|
||||
pset *psetv2.Pset,
|
||||
explorer Explorer,
|
||||
prvKey *secp256k1.PrivateKey,
|
||||
pset *psetv2.Pset, explorer Explorer, prvKey *secp256k1.PrivateKey,
|
||||
) error {
|
||||
updater, err := psetv2.NewUpdater(pset)
|
||||
if err != nil {
|
||||
@@ -66,7 +64,7 @@ func signPset(
|
||||
return err
|
||||
}
|
||||
|
||||
_, onchainAddr, err := getAddress()
|
||||
_, onchainAddr, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vulpemventures/go-elements/psetv2"
|
||||
"github.com/vulpemventures/go-elements/taproot"
|
||||
)
|
||||
|
||||
type RedeemBranch interface {
|
||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
||||
RedeemPath() ([]string, error)
|
||||
// ExpireAt returns the expiration time of the branch
|
||||
ExpireAt() (*time.Time, error)
|
||||
}
|
||||
|
||||
type redeemBranch struct {
|
||||
vtxo *vtxo
|
||||
branch []*psetv2.Pset
|
||||
@@ -29,11 +22,9 @@ type redeemBranch struct {
|
||||
}
|
||||
|
||||
func newRedeemBranch(
|
||||
ctx *cli.Context,
|
||||
explorer Explorer,
|
||||
congestionTree tree.CongestionTree,
|
||||
vtxo vtxo,
|
||||
) (RedeemBranch, error) {
|
||||
ctx context.Context, explorer Explorer,
|
||||
congestionTree tree.CongestionTree, vtxo vtxo,
|
||||
) (*redeemBranch, error) {
|
||||
sweepClosure, seconds, err := findSweepClosure(congestionTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,7 +66,7 @@ func newRedeemBranch(
|
||||
}
|
||||
|
||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
||||
func (r *redeemBranch) RedeemPath() ([]string, error) {
|
||||
func (r *redeemBranch) redeemPath() ([]string, error) {
|
||||
transactions := make([]string, 0, len(r.branch))
|
||||
|
||||
offchainPath, err := r.offchainPath()
|
||||
@@ -125,7 +116,7 @@ func (r *redeemBranch) RedeemPath() ([]string, error) {
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
func (r *redeemBranch) ExpireAt() (*time.Time, error) {
|
||||
func (r *redeemBranch) expireAt() (*time.Time, error) {
|
||||
lastKnownBlocktime := int64(0)
|
||||
|
||||
confirmed, blocktime, _ := getTxBlocktime(r.vtxo.poolTxid)
|
||||
|
||||
Reference in New Issue
Block a user