Update client sdk (#207)

* Add bitcoin networks

* Refactor client

* Refactor explorer

* Refactor store

* Refactor wallet

* Refactor sdk client

* Refactor wasm & Update examples

* Move common util funcs to internal/utils

* Move to constants for service types

* Add unit tests

* Parallelize tests

* Lint

* Add job to gh action

* go mod tidy

* Fixes

* Fixes

* Fix compose file

* Fixes

* Fixes after review:
* Drop factory pattern
* Drop password from ark client methods
* Make singlekey wallet manage store and wallet store instead of defining WalletStore as extension of Store
* Move constants to arksdk module
* Drop config and expect directory store and wallet as ark client factory args

* Fix

* Add constants for bitcoin/liquid explorer

* Fix test

* Fix wasm

* Rename client.Client to client.ASPClient

* Rename store.Store to store.ConfigStore

* Rename wallet.Wallet to wallet.WalletService

* Renamings

* Lint

* Fixes

* Move everything to internal/utils & move ComputeVtxoTaprootScript to common

* Go mod tidy
This commit is contained in:
Pietralberto Mazza
2024-07-30 16:08:23 +02:00
committed by GitHub
parent e45bff3c70
commit 89df461623
148 changed files with 8497 additions and 6466 deletions

View File

@@ -0,0 +1,226 @@
package utils
import (
"fmt"
"sort"
"strings"
"github.com/ark-network/ark-sdk/client"
"github.com/ark-network/ark-sdk/explorer"
liquidexplorer "github.com/ark-network/ark-sdk/explorer/liquid"
"github.com/ark-network/ark-sdk/store"
"github.com/ark-network/ark-sdk/wallet"
liquidwallet "github.com/ark-network/ark-sdk/wallet/singlekey/liquid"
walletstore "github.com/ark-network/ark-sdk/wallet/singlekey/store"
filestore "github.com/ark-network/ark-sdk/wallet/singlekey/store/file"
inmemorystore "github.com/ark-network/ark-sdk/wallet/singlekey/store/inmemory"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
)
func ToCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
levels := make(tree.CongestionTree, 0, len(treeFromProto.Levels))
for _, level := range treeFromProto.Levels {
nodes := make([]tree.Node, 0, len(level.Nodes))
for _, node := range level.Nodes {
nodes = append(nodes, tree.Node{
Txid: node.Txid,
Tx: node.Tx,
ParentTxid: node.ParentTxid,
Leaf: false,
})
}
levels = append(levels, nodes)
}
for j, treeLvl := range levels {
for i, node := range treeLvl {
if len(levels.Children(node.Txid)) == 0 {
levels[j][i].Leaf = true
}
}
}
return levels, nil
}
func CastCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
for _, level := range congestionTree {
levelProto := &arkv1.TreeLevel{
Nodes: make([]*arkv1.Node, 0, len(level)),
}
for _, node := range level {
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
Txid: node.Txid,
Tx: node.Tx,
ParentTxid: node.ParentTxid,
})
}
levels = append(levels, levelProto)
}
return &arkv1.Tree{
Levels: levels,
}
}
func CoinSelect(
vtxos []*client.Vtxo, amount, dust uint64, sortByExpirationTime bool,
) ([]*client.Vtxo, uint64, error) {
selected := make([]*client.Vtxo, 0)
notSelected := make([]*client.Vtxo, 0)
selectedAmount := uint64(0)
if sortByExpirationTime {
// sort vtxos by expiration (older first)
sort.SliceStable(vtxos, func(i, j int) bool {
if vtxos[i].ExpiresAt == nil || vtxos[j].ExpiresAt == nil {
return false
}
return vtxos[i].ExpiresAt.Before(*vtxos[j].ExpiresAt)
})
}
for _, vtxo := range vtxos {
if selectedAmount >= amount {
notSelected = append(notSelected, vtxo)
break
}
selected = append(selected, vtxo)
selectedAmount += vtxo.Amount
}
if selectedAmount < amount {
return nil, 0, fmt.Errorf("not enough funds to cover amount%d", amount)
}
change := selectedAmount - amount
if change < dust {
if len(notSelected) > 0 {
selected = append(selected, notSelected[0])
change += notSelected[0].Amount
}
}
return selected, change, nil
}
func DecodeReceiverAddress(addr string) (
bool, []byte, *secp256k1.PublicKey, error,
) {
outputScript, err := address.ToOutputScript(addr)
if err != nil {
_, userPubkey, _, err := common.DecodeAddress(addr)
if err != nil {
return false, nil, nil, err
}
return false, nil, userPubkey, nil
}
return true, outputScript, nil, nil
}
func IsOnchainOnly(receivers []*arkv1.Output) bool {
for _, receiver := range receivers {
isOnChain, _, _, err := DecodeReceiverAddress(receiver.Address)
if err != nil {
continue
}
if !isOnChain {
return false
}
}
return true
}
func GetClient(
supportedClients SupportedType[ClientFactory], clientType, aspUrl string,
) (client.ASPClient, error) {
factory := supportedClients[clientType]
return factory(aspUrl)
}
func GetExplorer(
supportedNetworks SupportedType[string], network string,
) (explorer.Explorer, error) {
url, ok := supportedNetworks[network]
if !ok {
return nil, fmt.Errorf("invalid network")
}
if strings.Contains(network, "liquid") {
return liquidexplorer.NewExplorer(url, network), nil
}
// TODO: support bitcoin explorer
return nil, fmt.Errorf("network not supported yet")
}
func GetWallet(
storeSvc store.ConfigStore, data *store.StoreData, supportedWallets SupportedType[struct{}],
) (wallet.WalletService, error) {
switch data.WalletType {
case wallet.SingleKeyWallet:
return getSingleKeyWallet(storeSvc, data.Network.Name)
default:
return nil, fmt.Errorf(
"unsuported wallet type '%s', please select one of: %s",
data.WalletType, supportedWallets,
)
}
}
func getSingleKeyWallet(
configStore store.ConfigStore, network string,
) (wallet.WalletService, error) {
walletStore, err := getWalletStore(configStore.GetType(), configStore.GetDatadir())
if err != nil {
return nil, err
}
if strings.Contains(network, "liquid") {
return liquidwallet.NewWalletService(configStore, walletStore)
}
// TODO: Support bitcoin wallet
return nil, fmt.Errorf("network %s not supported yet", network)
}
func getWalletStore(storeType, datadir string) (walletstore.WalletStore, error) {
switch storeType {
case store.InMemoryStore:
return inmemorystore.NewWalletStore()
case store.FileStore:
return filestore.NewWalletStore(datadir)
default:
return nil, fmt.Errorf("unknown wallet store type")
}
}
func NetworkFromString(net string) common.Network {
switch net {
case common.Liquid.Name:
return common.Liquid
case common.LiquidTestNet.Name:
return common.LiquidTestNet
case common.LiquidRegTest.Name:
return common.LiquidRegTest
case common.BitcoinTestNet.Name:
return common.BitcoinTestNet
case common.BitcoinRegTest.Name:
return common.BitcoinRegTest
case common.Bitcoin.Name:
fallthrough
default:
return common.Bitcoin
}
}