mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
* 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
227 lines
5.6 KiB
Go
227 lines
5.6 KiB
Go
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
|
|
}
|
|
}
|