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