mirror of
https://github.com/aljazceru/ark.git
synced 2026-01-04 20:44:20 +01:00
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>
This commit is contained in:
296
pkg/client-sdk/common.go
Normal file
296
pkg/client-sdk/common.go
Normal file
@@ -0,0 +1,296 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user