mirror of
https://github.com/aljazceru/ark.git
synced 2026-01-13 00:34:22 +01:00
* regtest support + integration tests (e2e) * add integration CI * add PR trigger on integration CI * wait for ocean to be unlocked at startup * integration tests: add tests flags and build docker images at startup * use nigiri chopsticks-liquid * fix after reviews * Update client/init.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> * do not trigger integration on PR --------- Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
209 lines
4.1 KiB
Go
209 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/vulpemventures/go-elements/psetv2"
|
|
"github.com/vulpemventures/go-elements/transaction"
|
|
)
|
|
|
|
type utxo struct {
|
|
Txid string `json:"txid"`
|
|
Vout uint32 `json:"vout"`
|
|
Amount uint64 `json:"value"`
|
|
Asset string `json:"asset"`
|
|
Status struct {
|
|
Confirmed bool `json:"confirmed"`
|
|
Blocktime int64 `json:"block_time"`
|
|
} `json:"status"`
|
|
}
|
|
|
|
type Explorer interface {
|
|
GetTxHex(txid string) (string, error)
|
|
Broadcast(txHex string) (string, error)
|
|
GetUtxos(addr string) ([]utxo, error)
|
|
GetBalance(addr, asset string) (uint64, error)
|
|
GetRedeemedVtxosBalance(
|
|
addr string, unilateralExitDelay int64,
|
|
) (uint64, map[int64]uint64, error)
|
|
}
|
|
|
|
type explorer struct {
|
|
cache map[string]string
|
|
baseUrl string
|
|
}
|
|
|
|
func NewExplorer() Explorer {
|
|
baseUrl, err := getBaseURL()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &explorer{
|
|
cache: make(map[string]string),
|
|
baseUrl: baseUrl,
|
|
}
|
|
}
|
|
|
|
func (e *explorer) GetTxHex(txid string) (string, error) {
|
|
if hex, ok := e.cache[txid]; ok {
|
|
return hex, nil
|
|
}
|
|
|
|
txHex, err := e.getTxHex(txid)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
e.cache[txid] = txHex
|
|
|
|
return txHex, nil
|
|
}
|
|
|
|
func (e *explorer) Broadcast(txStr string) (string, error) {
|
|
tx, err := transaction.NewTxFromHex(txStr)
|
|
if err != nil {
|
|
pset, err := psetv2.NewPsetFromBase64(txStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
tx, err = psetv2.Extract(pset)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
txStr, _ = tx.ToHex()
|
|
}
|
|
txid := tx.TxHash().String()
|
|
e.cache[txid] = txStr
|
|
|
|
txid, err = e.broadcast(txStr)
|
|
if err != nil {
|
|
if strings.Contains(
|
|
strings.ToLower(err.Error()), "transaction already in block chain",
|
|
) {
|
|
return txid, nil
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
return txid, nil
|
|
}
|
|
|
|
func (e *explorer) GetUtxos(addr string) ([]utxo, error) {
|
|
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf(string(body))
|
|
}
|
|
payload := []utxo{}
|
|
if err := json.Unmarshal(body, &payload); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
func (e *explorer) GetBalance(addr, asset string) (uint64, error) {
|
|
payload, err := e.GetUtxos(addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
balance := uint64(0)
|
|
for _, p := range payload {
|
|
if p.Asset != asset {
|
|
continue
|
|
}
|
|
balance += p.Amount
|
|
}
|
|
return balance, nil
|
|
}
|
|
|
|
func (e *explorer) GetRedeemedVtxosBalance(
|
|
addr string, unilateralExitDelay int64,
|
|
) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) {
|
|
utxos, err := e.GetUtxos(addr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
lockedBalance = make(map[int64]uint64, 0)
|
|
now := time.Now()
|
|
for _, utxo := range utxos {
|
|
blocktime := now
|
|
if utxo.Status.Confirmed {
|
|
blocktime = time.Unix(utxo.Status.Blocktime, 0)
|
|
}
|
|
|
|
delay := time.Duration(unilateralExitDelay) * time.Second
|
|
availableAt := blocktime.Add(delay)
|
|
if availableAt.After(now) {
|
|
if _, ok := lockedBalance[availableAt.Unix()]; !ok {
|
|
lockedBalance[availableAt.Unix()] = 0
|
|
}
|
|
|
|
lockedBalance[availableAt.Unix()] += utxo.Amount
|
|
} else {
|
|
spendableBalance += utxo.Amount
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (e *explorer) getTxHex(txid string) (string, error) {
|
|
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", e.baseUrl, txid))
|
|
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))
|
|
}
|
|
|
|
hex := string(body)
|
|
e.cache[txid] = hex
|
|
return hex, nil
|
|
}
|
|
|
|
func (e *explorer) broadcast(txHex string) (string, error) {
|
|
body := bytes.NewBuffer([]byte(txHex))
|
|
|
|
resp, err := http.Post(fmt.Sprintf("%s/tx", e.baseUrl), "text/plain", body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
bodyResponse, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf(string(bodyResponse))
|
|
}
|
|
|
|
return string(bodyResponse), nil
|
|
}
|