Files
ark/pkg/client-sdk/common.go
Dusan Sekulic 1f42843a69 Add first version of client SDK (#193)
* Add gRPC, REST, and gRPC-Web clients for server access

This commit introduces clients for gRPC, REST, and gRPC-Web to access the server.

- gRPC client: Includes additional argument opts ...grpc.CallOption in the interface for future extensibility.
- REST client: Factory function accepts http.Client as an argument to allow user customization.
- gRPC-Web client: Added a Log method for fast debugging in JavaScript.

The decision to use different interfaces for each client type is to accommodate specific features and extensibility requirements for each protocol.

* remove grpc web

* generate rest

* use grpc sdk in CLI

* temp wasm

* ark sdk

* renaming

* pr review refactor

* pr review refactor

* walletStore & configStore

* ark sdk wasm wrapper

* handle event stream with rest

* wip on supporting rest

* store init

* simulate event stream with rest

* fix rest sdk wip

* Fix returning forfeit txs in round event

* wasm first working e2e example

* pr review refactor

* pr review refactor

* pr review refactor

* Fixes

---------

Co-authored-by: altafan <18440657+altafan@users.noreply.github.com>
2024-07-18 16:27:14 +02:00

297 lines
6.4 KiB
Go

package arksdk
import (
"fmt"
"io"
"net/http"
"sort"
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"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
)
func getAddress(
walletPubKey []byte,
aspPubKey []byte,
unilateralExitDelay int64,
net string,
) (offchainAddr, onchainAddr, redemptionAddr string, err error) {
userPubkey, err := secp256k1.ParsePubKey(walletPubKey)
if err != nil {
return
}
aspPubkey, err := secp256k1.ParsePubKey(aspPubKey)
if err != nil {
return
}
arkNet, liquidNet := networkFromString(net)
arkAddr, err := common.EncodeAddress(arkNet.Addr, userPubkey, aspPubkey)
if err != nil {
return
}
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
}
_, n := networkFromString(net)
pay, err := payment.FromTweakedKey(vtxoTapKey, n, nil)
if err != nil {
return
}
redemptionAddr, err = pay.TaprootAddress()
if err != nil {
return
}
offchainAddr = arkAddr
onchainAddr = liquidAddr
return
}
func computeVtxoTaprootScript(
userPubkey, aspPubkey *secp256k1.PublicKey, exitDelay uint,
) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, error) {
redeemClosure := &tree.CSVSigClosure{
Pubkey: userPubkey,
Seconds: exitDelay,
}
forfeitClosure := &tree.ForfeitClosure{
Pubkey: userPubkey,
AspPubkey: aspPubkey,
}
redeemLeaf, err := redeemClosure.Leaf()
if err != nil {
return nil, nil, err
}
forfeitLeaf, err := forfeitClosure.Leaf()
if err != nil {
return nil, nil, err
}
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(
*redeemLeaf, *forfeitLeaf,
)
root := vtxoTaprootTree.RootNode.TapHash()
unspendableKey := tree.UnspendableKey()
vtxoTaprootKey := taproot.ComputeTaprootOutputKey(unspendableKey, root[:])
redeemLeafHash := redeemLeaf.TapHash()
proofIndex := vtxoTaprootTree.LeafProofIndex[redeemLeafHash]
proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex]
return vtxoTaprootKey, &proof, nil
}
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 networkFromString(net string) (*common.Network, *network.Network) {
if net == "testnet" {
return &common.TestNet, &network.Testnet
}
if net == "regtest" {
return &common.RegTest, &network.Regtest
}
return &common.Liquid, &network.Liquid
}
func testEsploraEndpoint(net *network.Network, url string) error {
resp, err := http.Get(fmt.Sprintf("%s/asset/%s", url, net.AssetID))
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))
}
return 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 []vtxo, amount uint64, sortByExpirationTime bool) ([]vtxo, uint64, error) {
selected := make([]vtxo, 0)
notSelected := make([]vtxo, 0)
selectedAmount := uint64(0)
if sortByExpirationTime {
// 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)
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 findSweepClosure(
congestionTree tree.CongestionTree,
) (*taproot.TapElementsLeaf, uint, error) {
root, err := congestionTree.Root()
if err != nil {
return nil, 0, err
}
// find the sweep closure
tx, err := psetv2.NewPsetFromBase64(root.Tx)
if err != nil {
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)
if err != nil {
continue
}
if valid && closure.Seconds > seconds {
seconds = closure.Seconds
sweepClosure = &tapLeaf.TapElementsLeaf
}
}
if sweepClosure == nil {
return nil, 0, fmt.Errorf("sweep closure not found")
}
return sweepClosure, seconds, 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
}