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 }