decred.org/dcrwallet/v3@v3.1.0/cmd/movefunds/main.go (about) 1 /* 2 * Copyright (c) 2016-2020 The Decred developers 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/hex" 22 "encoding/json" 23 "fmt" 24 "os" 25 26 "decred.org/dcrwallet/v3/rpc/jsonrpc/types" 27 "decred.org/dcrwallet/v3/wallet/txauthor" 28 "github.com/decred/dcrd/chaincfg/chainhash" 29 "github.com/decred/dcrd/chaincfg/v3" 30 "github.com/decred/dcrd/dcrutil/v4" 31 "github.com/decred/dcrd/txscript/v4/stdaddr" 32 "github.com/decred/dcrd/wire" 33 ) 34 35 // params is the global representing the chain parameters. It is assigned 36 // in main. 37 var params *chaincfg.Params 38 39 // configJSON is a configuration file used for transaction generation. 40 type configJSON struct { 41 TxFee int64 `json:"txfee"` 42 SendToAddress string `json:"sendtoaddress"` 43 Network string `json:"network"` 44 DcrctlArgs string `json:"dcrctlargs"` 45 } 46 47 func saneOutputValue(amount dcrutil.Amount) bool { 48 return amount >= 0 && amount <= dcrutil.MaxAmount 49 } 50 51 func parseOutPoint(input *types.ListUnspentResult) (wire.OutPoint, error) { 52 txHash, err := chainhash.NewHashFromStr(input.TxID) 53 if err != nil { 54 return wire.OutPoint{}, err 55 } 56 return wire.OutPoint{Hash: *txHash, Index: input.Vout, Tree: input.Tree}, nil 57 } 58 59 // noInputValue describes an error returned by the input source when no inputs 60 // were selected because each previous output value was zero. Callers of 61 // txauthor.NewUnsignedTransaction need not report these errors to the user. 62 type noInputValue struct { 63 } 64 65 func (noInputValue) Error() string { return "no input value" } 66 67 // makeInputSource creates an InputSource that creates inputs for every unspent 68 // output with non-zero output values. The target amount is ignored since every 69 // output is consumed. The InputSource does not return any previous output 70 // scripts as they are not needed for creating the unsinged transaction and are 71 // looked up again by the wallet during the call to signrawtransaction. 72 func makeInputSource(outputs []types.ListUnspentResult) (dcrutil.Amount, txauthor.InputSource) { 73 var ( 74 totalInputValue dcrutil.Amount 75 inputs = make([]*wire.TxIn, 0, len(outputs)) 76 redeemScriptSizes = make([]int, 0, len(outputs)) 77 sourceErr error 78 ) 79 for _, output := range outputs { 80 81 outputAmount, err := dcrutil.NewAmount(output.Amount) 82 if err != nil { 83 sourceErr = fmt.Errorf("invalid amount `%v` in listunspent result", 84 output.Amount) 85 break 86 } 87 if outputAmount == 0 { 88 continue 89 } 90 if !saneOutputValue(outputAmount) { 91 sourceErr = fmt.Errorf( 92 "impossible output amount `%v` in listunspent result", 93 outputAmount) 94 break 95 } 96 totalInputValue += outputAmount 97 98 previousOutPoint, err := parseOutPoint(&output) 99 if err != nil { 100 sourceErr = fmt.Errorf( 101 "invalid data in listunspent result: %v", err) 102 break 103 } 104 105 txIn := wire.NewTxIn(&previousOutPoint, int64(outputAmount), nil) 106 inputs = append(inputs, txIn) 107 } 108 109 if sourceErr == nil && totalInputValue == 0 { 110 sourceErr = noInputValue{} 111 } 112 113 return totalInputValue, func(dcrutil.Amount) (*txauthor.InputDetail, error) { 114 inputDetail := txauthor.InputDetail{ 115 Amount: totalInputValue, 116 Inputs: inputs, 117 Scripts: nil, 118 RedeemScriptSizes: redeemScriptSizes, 119 } 120 return &inputDetail, sourceErr 121 } 122 } 123 124 func main() { 125 // Create an inputSource from the loaded utxos. 126 unspentFile, err := os.Open("unspent.json") 127 if err != nil { 128 fmt.Printf("error opening unspent file unspent.json: %v", err) 129 return 130 } 131 132 var utxos []types.ListUnspentResult 133 134 jsonParser := json.NewDecoder(unspentFile) 135 if err = jsonParser.Decode(&utxos); err != nil { 136 fmt.Printf("error parsing unspent file: %v", err) 137 return 138 } 139 140 targetAmount, inputSource := makeInputSource(utxos) 141 142 // Load and parse the movefunds config. 143 configFile, err := os.Open("config.json") 144 if err != nil { 145 fmt.Printf("error opening config file config.json: %v", err) 146 return 147 } 148 149 cfg := new(configJSON) 150 151 jsonParser = json.NewDecoder(configFile) 152 if err = jsonParser.Decode(cfg); err != nil { 153 fmt.Printf("error parsing config file: %v", err) 154 return 155 } 156 157 switch cfg.Network { 158 case "testnet": 159 params = chaincfg.TestNet3Params() 160 case "mainnet": 161 params = chaincfg.MainNetParams() 162 case "simnet": 163 params = chaincfg.SimNetParams() 164 default: 165 fmt.Printf("unknown network specified: %s", cfg.Network) 166 return 167 } 168 169 addr, err := stdaddr.DecodeAddress(cfg.SendToAddress, params) 170 if err != nil { 171 fmt.Printf("failed to parse address %s: %v", cfg.SendToAddress, err) 172 return 173 } 174 175 fee, err := dcrutil.NewAmount(float64(cfg.TxFee)) 176 if err != nil { 177 fmt.Printf("invalid fee rate: %v", err) 178 return 179 } 180 181 // Create the unsigned transaction. 182 pkScriptVer, pkScript := addr.PaymentScript() 183 184 txOuts := []*wire.TxOut{ 185 { 186 Value: int64(targetAmount - fee), 187 Version: pkScriptVer, 188 PkScript: pkScript, 189 }, 190 } 191 atx, err := txauthor.NewUnsignedTransaction(txOuts, fee, inputSource, nil, params.MaxTxSize) 192 if err != nil { 193 fmt.Printf("failed to create unsigned transaction: %s", err) 194 return 195 } 196 197 if atx.Tx.SerializeSize() > params.MaxTxSize { 198 fmt.Printf("tx too big: got %v, max %v", atx.Tx.SerializeSize(), 199 params.MaxTxSize) 200 return 201 } 202 203 // Generate the signrawtransaction command. 204 txB, err := atx.Tx.Bytes() 205 if err != nil { 206 fmt.Println("Failed to serialize tx: ", err.Error()) 207 return 208 } 209 210 // The command to sign the transaction. 211 var buf bytes.Buffer 212 buf.WriteString("dcrctl ") 213 buf.WriteString(cfg.DcrctlArgs) 214 buf.WriteString(" signrawtransaction ") 215 buf.WriteString(hex.EncodeToString(txB)) 216 buf.WriteString(" '[") 217 last := len(atx.Tx.TxIn) - 1 218 for i, in := range atx.Tx.TxIn { 219 buf.WriteString("{\"txid\":\"") 220 buf.WriteString(in.PreviousOutPoint.Hash.String()) 221 buf.WriteString("\",\"vout\":") 222 buf.WriteString(fmt.Sprintf("%v", in.PreviousOutPoint.Index)) 223 buf.WriteString(",\"tree\":") 224 buf.WriteString(fmt.Sprintf("%v", in.PreviousOutPoint.Tree)) 225 buf.WriteString(",\"scriptpubkey\":\"") 226 buf.WriteString(hex.EncodeToString(in.SignatureScript)) 227 buf.WriteString("\",\"redeemscript\":\"\"}") 228 if i != last { 229 buf.WriteString(",") 230 } 231 } 232 buf.WriteString("]' ") 233 buf.WriteString("| jq -r .hex") 234 err = os.WriteFile("sign.sh", buf.Bytes(), 0755) 235 if err != nil { 236 fmt.Println("Failed to write signing script: ", err.Error()) 237 return 238 } 239 240 fmt.Println("Successfully wrote transaction to sign script.") 241 }