decred.org/dcrdex@v1.0.5/client/cmd/testbinance/wallets.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/big"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/ethclient"
    17  	ethrpc "github.com/ethereum/go-ethereum/rpc"
    18  )
    19  
    20  var (
    21  	dextestDir = filepath.Join(os.Getenv("HOME"), "dextest")
    22  )
    23  
    24  type Wallet interface {
    25  	DepositAddress() string
    26  	Confirmations(ctx context.Context, txID string) (uint32, error)
    27  	Send(ctx context.Context, addr, coin string, amt float64) (string, error)
    28  }
    29  
    30  type utxoWallet struct {
    31  	symbol string
    32  	dir    string
    33  	addr   string
    34  }
    35  
    36  func newUtxoWallet(ctx context.Context, symbol string) (*utxoWallet, error) {
    37  	symbol = strings.ToLower(symbol)
    38  	dir := filepath.Join(dextestDir, symbol, "harness-ctl")
    39  	var addr string
    40  	switch symbol {
    41  	case "zec":
    42  		addr = "tmEgW8c44RQQfft9FHXnqGp8XEcQQSRcUXD"
    43  	default:
    44  		ctx, cancel := context.WithTimeout(ctx, time.Second*5)
    45  		defer cancel()
    46  		cmd := exec.CommandContext(ctx, "./alpha", "getnewaddress")
    47  		cmd.Dir = dir
    48  		addrB, err := cmd.CombinedOutput()
    49  		if err != nil {
    50  			return nil, fmt.Errorf("getnewaddress error with output = %q, err = %v", string(addrB), err)
    51  		}
    52  		addr = string(addrB)
    53  	}
    54  
    55  	return &utxoWallet{
    56  		symbol: symbol,
    57  		dir:    dir,
    58  		addr:   strings.TrimSpace(addr),
    59  	}, nil
    60  }
    61  
    62  func (w *utxoWallet) DepositAddress() string {
    63  	return w.addr
    64  }
    65  
    66  func (w *utxoWallet) Confirmations(ctx context.Context, txID string) (uint32, error) {
    67  	cmd := exec.CommandContext(ctx, "./alpha", "gettransaction", txID)
    68  	cmd.Dir = w.dir
    69  	log.Tracef("Running utxoWallet.Confirmations command %q from directory %q", cmd, w.dir)
    70  	b, err := cmd.CombinedOutput()
    71  	if err != nil {
    72  		return 0, fmt.Errorf("gettransaction error with output = %q, err = %v", string(b), err)
    73  	}
    74  	var resp struct {
    75  		Confs uint32 `json:"confirmations"`
    76  	}
    77  	if err = json.Unmarshal(b, &resp); err != nil {
    78  		return 0, fmt.Errorf("error unmarshaling gettransaction response = %q, err = %s", string(b), err)
    79  	}
    80  	return resp.Confs, nil
    81  }
    82  
    83  func (w *utxoWallet) unlock(ctx context.Context) {
    84  	switch w.symbol {
    85  	case "zec": // TODO: Others?
    86  		return
    87  	}
    88  	cmd := exec.CommandContext(ctx, "./alpha", "walletpassphrase", "abc", "100000000")
    89  	cmd.Dir = w.dir
    90  	errText, err := cmd.CombinedOutput()
    91  	if err != nil {
    92  		log.Errorf("walletpassphrase error with output = %q, err = %v", string(errText), err)
    93  	}
    94  }
    95  
    96  func (w *utxoWallet) Send(ctx context.Context, addr, _ string, amt float64) (string, error) {
    97  	w.unlock(ctx)
    98  	cmd := exec.CommandContext(ctx, "./alpha", "sendtoaddress", addr, strconv.FormatFloat(amt, 'f', 8, 64))
    99  	cmd.Dir = w.dir
   100  	log.Tracef("Running utxoWallet.Send command %q from directory %q", cmd, w.dir)
   101  	txID, err := cmd.CombinedOutput()
   102  	if err != nil {
   103  		return "", fmt.Errorf("sendtoaddress error with output = %q, err = %v", string(txID), err)
   104  	}
   105  	return strings.TrimSpace(string(txID)), err
   106  }
   107  
   108  type evmWallet struct {
   109  	dir  string
   110  	addr string
   111  	ec   *ethclient.Client
   112  }
   113  
   114  func newEvmWallet(ctx context.Context, symbol string) (*evmWallet, error) {
   115  	symbol = strings.ToLower(symbol)
   116  	rpcAddr := "http://localhost:38556"
   117  	switch symbol {
   118  	case "matic", "polygon":
   119  		symbol = "polygon"
   120  		rpcAddr = "http://localhost:48296"
   121  	}
   122  
   123  	ctx, cancel := context.WithTimeout(ctx, time.Second*5)
   124  	defer cancel()
   125  	rpcClient, err := ethrpc.DialContext(ctx, rpcAddr)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	ec := ethclient.NewClient(rpcClient)
   131  	return &evmWallet{
   132  		dir:  filepath.Join(dextestDir, symbol, "harness-ctl"),
   133  		addr: "0x18d65fb8d60c1199bb1ad381be47aa692b482605",
   134  		ec:   ec,
   135  	}, nil
   136  }
   137  
   138  func (w *evmWallet) DepositAddress() string {
   139  	return w.addr
   140  }
   141  
   142  func (w *evmWallet) Confirmations(ctx context.Context, txID string) (uint32, error) {
   143  	r, err := w.ec.TransactionReceipt(ctx, common.HexToHash(txID))
   144  	if err != nil {
   145  		return 0, fmt.Errorf("TransactionReceipt error: %v", err)
   146  	}
   147  	tip, err := w.ec.HeaderByNumber(ctx, nil /* latest */)
   148  	if err != nil {
   149  		return 0, fmt.Errorf("HeaderByNumber error: %w", err)
   150  	}
   151  	if r.BlockNumber != nil && tip.Number != nil {
   152  		bigConfs := new(big.Int).Sub(tip.Number, r.BlockNumber)
   153  		if bigConfs.Sign() < 0 { // avoid potential overflow
   154  			return 0, nil
   155  		}
   156  		bigConfs.Add(bigConfs, big.NewInt(1))
   157  		if bigConfs.IsInt64() {
   158  			return uint32(bigConfs.Int64()), nil
   159  		}
   160  	}
   161  	return 0, nil
   162  }
   163  
   164  func (w *evmWallet) Send(ctx context.Context, addr, coin string, amt float64) (string, error) {
   165  	script := "./sendtoaddress"
   166  	if strings.ToLower(coin) == "usdc" {
   167  		script = "./sendUSDC"
   168  	}
   169  	cmd := exec.CommandContext(ctx, script, addr, strconv.FormatFloat(amt, 'f', 9, 64))
   170  	cmd.Dir = w.dir
   171  	log.Tracef("Running evmWallet.Send command %q from directory %q", cmd, w.dir)
   172  	b, err := cmd.CombinedOutput()
   173  	if err != nil {
   174  		return "", fmt.Errorf("sendtoaddress error with output = %q, err = %v", string(b), err)
   175  	}
   176  	// There's probably a deprecation warning ending in a newline before the txid.
   177  	lines := strings.Split(strings.TrimSpace(string(b)), "\n")
   178  	jsonTxID := lines[len(lines)-1]
   179  	var txID string
   180  	if err = json.Unmarshal([]byte(jsonTxID), &txID); err != nil {
   181  		return "", fmt.Errorf("error decoding address from %q: %v", jsonTxID, err)
   182  	}
   183  	if common.HexToHash(txID) == (common.Hash{}) {
   184  		return "", fmt.Errorf("output %q did not parse to a tx hash", txID)
   185  	}
   186  	return txID, nil
   187  }
   188  
   189  func newWallet(ctx context.Context, symbol string) (w Wallet, err error) {
   190  	switch strings.ToLower(symbol) {
   191  	case "btc", "dcr", "zec":
   192  		w, err = newUtxoWallet(ctx, symbol)
   193  	case "eth", "matic", "polygon":
   194  		w, err = newEvmWallet(ctx, symbol)
   195  	}
   196  	return w, err
   197  }