github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/cmd/bytomcli/commands/transaction.go (about) 1 package commands 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "fmt" 7 "os" 8 9 "github.com/spf13/cobra" 10 jww "github.com/spf13/jwalterweatherman" 11 12 "github.com/bytom/bytom/api" 13 "github.com/bytom/bytom/blockchain/txbuilder" 14 chainjson "github.com/bytom/bytom/encoding/json" 15 "github.com/bytom/bytom/protocol/bc/types" 16 "github.com/bytom/bytom/util" 17 ) 18 19 func init() { 20 buildTransactionCmd.PersistentFlags().StringVarP(&buildType, "type", "t", "", "transaction type, valid types: 'issue', 'spend', 'address', 'retire', 'unlock'") 21 buildTransactionCmd.PersistentFlags().StringVarP(&receiverProgram, "receiver", "r", "", "program of receiver when type is spend") 22 buildTransactionCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "address of receiver when type is address") 23 buildTransactionCmd.PersistentFlags().StringVarP(&program, "program", "p", "", "program of receiver when type is program") 24 buildTransactionCmd.PersistentFlags().StringVarP(&arbitrary, "arbitrary", "v", "", "additional arbitrary data when type is retire") 25 buildTransactionCmd.PersistentFlags().StringVarP(&btmGas, "gas", "g", "20000000", "gas of this transaction") 26 buildTransactionCmd.PersistentFlags().StringVarP(&contractName, "contract-name", "c", "", 27 "name of template contract, currently supported: 'LockWithPublicKey', 'LockWithMultiSig', 'LockWithPublicKeyHash',"+ 28 "\n\t\t\t 'RevealPreimage', 'TradeOffer', 'Escrow', 'CallOption', 'LoanCollateral'") 29 buildTransactionCmd.PersistentFlags().BoolVar(&pretty, "pretty", false, "pretty print json result") 30 buildTransactionCmd.PersistentFlags().BoolVar(&alias, "alias", false, "use alias build transaction") 31 32 signTransactionCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "password of the account which sign these transaction(s)") 33 signTransactionCmd.PersistentFlags().BoolVar(&pretty, "pretty", false, "pretty print json result") 34 35 listTransactionsCmd.PersistentFlags().StringVar(&txID, "id", "", "transaction id") 36 listTransactionsCmd.PersistentFlags().StringVar(&account, "account_id", "", "account id") 37 listTransactionsCmd.PersistentFlags().BoolVar(&detail, "detail", false, "list transactions details") 38 listTransactionsCmd.PersistentFlags().BoolVar(&unconfirmed, "unconfirmed", false, "list unconfirmed transactions") 39 } 40 41 var ( 42 buildType = "" 43 btmGas = "" 44 receiverProgram = "" 45 address = "" 46 password = "" 47 pretty = false 48 alias = false 49 txID = "" 50 account = "" 51 detail = false 52 unconfirmed = false 53 arbitrary = "" 54 program = "" 55 contractName = "" 56 ) 57 58 var buildIssueReqFmt = ` 59 {"actions": [ 60 {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"}, 61 {"type": "issue", "asset_id": "%s", "amount": %s}, 62 {"type": "control_address", "asset_id": "%s", "amount": %s, "address": "%s"} 63 ]}` 64 65 var buildIssueReqFmtByAlias = ` 66 {"actions": [ 67 {"type": "spend_account", "asset_alias": "BTM", "amount":%s, "account_alias": "%s"}, 68 {"type": "issue", "asset_alias": "%s", "amount": %s}, 69 {"type": "control_address", "asset_alias": "%s", "amount": %s, "address": "%s"} 70 ]}` 71 72 var buildSpendReqFmt = ` 73 {"actions": [ 74 {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"}, 75 {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"}, 76 {"type": "control_program", "asset_id": "%s", "amount": %s, "control_program": "%s"} 77 ]}` 78 79 var buildSpendReqFmtByAlias = ` 80 {"actions": [ 81 {"type": "spend_account", "asset_alias": "BTM", "amount":%s, "account_alias": "%s"}, 82 {"type": "spend_account", "asset_alias": "%s","amount": %s,"account_alias": "%s"}, 83 {"type": "control_program", "asset_alias": "%s", "amount": %s, "control_program": "%s"} 84 ]}` 85 86 var buildRetireReqFmt = ` 87 {"actions": [ 88 {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"}, 89 {"type": "spend_account", "asset_id": "%s", "amount": %s, "account_id": "%s"}, 90 {"type": "retire", "asset_id": "%s", "amount": %s, "arbitrary": "%s"} 91 ]}` 92 93 var buildRetireReqFmtByAlias = ` 94 {"actions": [ 95 {"type": "spend_account", "asset_alias": "BTM", "amount":%s, "account_alias": "%s"}, 96 {"type": "spend_account", "asset_alias": "%s", "amount": %s, "account_alias": "%s"}, 97 {"type": "retire", "asset_alias": "%s", "amount": %s, "arbitrary": "%s"} 98 ]}` 99 100 var buildControlAddressReqFmt = ` 101 {"actions": [ 102 {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"}, 103 {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"}, 104 {"type": "control_address", "asset_id": "%s", "amount": %s,"address": "%s"} 105 ]}` 106 107 var buildControlAddressReqFmtByAlias = ` 108 {"actions": [ 109 {"type": "spend_account", "asset_alias": "BTM", "amount":%s, "account_alias": "%s"}, 110 {"type": "spend_account", "asset_alias": "%s","amount": %s, "account_alias": "%s"}, 111 {"type": "control_address", "asset_alias": "%s", "amount": %s,"address": "%s"} 112 ]}` 113 114 var buildTransactionCmd = &cobra.Command{ 115 Use: "build-transaction <accountID|alias> <assetID|alias> <amount> [outputID]", 116 Short: "Build one transaction template,default use account id and asset id", 117 Args: cobra.RangeArgs(3, 20), 118 PreRun: func(cmd *cobra.Command, args []string) { 119 cmd.MarkFlagRequired("type") 120 if buildType == "spend" { 121 cmd.MarkFlagRequired("receiver") 122 } 123 }, 124 Run: func(cmd *cobra.Command, args []string) { 125 var buildReqStr string 126 accountInfo := args[0] 127 assetInfo := args[1] 128 amount := args[2] 129 switch buildType { 130 case "issue": 131 if alias { 132 buildReqStr = fmt.Sprintf(buildIssueReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, assetInfo, amount, address) 133 break 134 } 135 buildReqStr = fmt.Sprintf(buildIssueReqFmt, btmGas, accountInfo, assetInfo, amount, assetInfo, amount, address) 136 case "spend": 137 if alias { 138 buildReqStr = fmt.Sprintf(buildSpendReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, receiverProgram) 139 break 140 } 141 buildReqStr = fmt.Sprintf(buildSpendReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, receiverProgram) 142 case "retire": 143 if alias { 144 buildReqStr = fmt.Sprintf(buildRetireReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, arbitrary) 145 break 146 } 147 buildReqStr = fmt.Sprintf(buildRetireReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, arbitrary) 148 case "address": 149 if alias { 150 buildReqStr = fmt.Sprintf(buildControlAddressReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address) 151 break 152 } 153 buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address) 154 case "unlock": 155 var err error 156 usage := "Usage:\n bytomcli build-transaction <accountID|alias> <assetID|alias> <amount> <outputID> -c <contractName>" 157 baseCount := 4 158 if len(args) < baseCount { 159 jww.ERROR.Printf("%s <contract_argument> ... [flags]\n\n", usage) 160 os.Exit(util.ErrLocalExe) 161 } 162 163 baseArg := baseContractArg{ 164 accountInfo: accountInfo, 165 assetInfo: assetInfo, 166 amount: amount, 167 alias: alias, 168 program: program, 169 btmGas: btmGas, 170 outputID: args[baseCount-1], 171 } 172 specArgs := args[baseCount:] 173 174 if buildReqStr, err = addContractArgs(contractName, baseArg, specArgs, usage); err != nil { 175 jww.ERROR.Println(err) 176 os.Exit(util.ErrLocalExe) 177 } 178 179 default: 180 jww.ERROR.Println("Invalid transaction template type") 181 os.Exit(util.ErrLocalExe) 182 } 183 184 var buildReq api.BuildRequest 185 if err := json.Unmarshal([]byte(buildReqStr), &buildReq); err != nil { 186 jww.ERROR.Println(err) 187 os.Exit(util.ErrLocalExe) 188 } 189 190 data, exitCode := util.ClientCall("/build-transaction", &buildReq) 191 if exitCode != util.Success { 192 os.Exit(exitCode) 193 } 194 195 if pretty { 196 printJSON(data) 197 return 198 } 199 200 dataMap, ok := data.(map[string]interface{}) 201 if ok != true { 202 jww.ERROR.Println("invalid type assertion") 203 os.Exit(util.ErrLocalParse) 204 } 205 206 rawTemplate, err := json.Marshal(dataMap) 207 if err != nil { 208 jww.ERROR.Println(err) 209 os.Exit(util.ErrLocalParse) 210 } 211 212 jww.FEEDBACK.Printf("Template Type: %s\n%s\n", buildType, string(rawTemplate)) 213 }, 214 } 215 216 var signTransactionCmd = &cobra.Command{ 217 Use: "sign-transaction <json templates>", 218 Short: "Sign transaction templates with account password", 219 Args: cobra.ExactArgs(1), 220 PreRun: func(cmd *cobra.Command, args []string) { 221 cmd.MarkFlagRequired("password") 222 }, 223 Run: func(cmd *cobra.Command, args []string) { 224 template := txbuilder.Template{} 225 226 err := json.Unmarshal([]byte(args[0]), &template) 227 if err != nil { 228 jww.ERROR.Println(err) 229 os.Exit(util.ErrLocalExe) 230 } 231 232 var req = struct { 233 Password string `json:"password"` 234 Txs txbuilder.Template `json:"transaction"` 235 }{Password: password, Txs: template} 236 237 jww.FEEDBACK.Printf("\n\n") 238 data, exitCode := util.ClientCall("/sign-transaction", &req) 239 if exitCode != util.Success { 240 os.Exit(exitCode) 241 } 242 243 if pretty { 244 printJSON(data) 245 return 246 } 247 248 dataMap, ok := data.(map[string]interface{}) 249 if ok != true { 250 jww.ERROR.Println("invalid type assertion") 251 os.Exit(util.ErrLocalParse) 252 } 253 254 rawSign, err := json.Marshal(dataMap) 255 if err != nil { 256 jww.ERROR.Println(err) 257 os.Exit(util.ErrLocalParse) 258 } 259 jww.FEEDBACK.Printf("\nSign Template:\n%s\n", string(rawSign)) 260 }, 261 } 262 263 var submitTransactionCmd = &cobra.Command{ 264 Use: "submit-transaction <signed json raw_transaction>", 265 Short: "Submit signed transaction", 266 Args: cobra.ExactArgs(1), 267 Run: func(cmd *cobra.Command, args []string) { 268 var ins = struct { 269 Tx types.Tx `json:"raw_transaction"` 270 }{} 271 272 err := json.Unmarshal([]byte(args[0]), &ins) 273 if err != nil { 274 jww.ERROR.Println(err) 275 os.Exit(util.ErrLocalExe) 276 } 277 278 data, exitCode := util.ClientCall("/submit-transaction", &ins) 279 if exitCode != util.Success { 280 os.Exit(exitCode) 281 } 282 283 printJSON(data) 284 }, 285 } 286 287 var estimateTransactionGasCmd = &cobra.Command{ 288 Use: "estimate-transaction-gas <json templates>", 289 Short: "estimate gas for build transaction", 290 Args: cobra.ExactArgs(1), 291 Run: func(cmd *cobra.Command, args []string) { 292 template := txbuilder.Template{} 293 294 err := json.Unmarshal([]byte(args[0]), &template) 295 if err != nil { 296 jww.ERROR.Println(err) 297 os.Exit(util.ErrLocalExe) 298 } 299 300 var req = struct { 301 TxTemplate txbuilder.Template `json:"transaction_template"` 302 }{TxTemplate: template} 303 304 data, exitCode := util.ClientCall("/estimate-transaction-gas", &req) 305 if exitCode != util.Success { 306 os.Exit(exitCode) 307 } 308 309 printJSON(data) 310 }, 311 } 312 313 var decodeRawTransactionCmd = &cobra.Command{ 314 Use: "decode-raw-transaction <raw_transaction>", 315 Short: "decode the raw transaction", 316 Args: cobra.ExactArgs(1), 317 Run: func(cmd *cobra.Command, args []string) { 318 var ins = struct { 319 Tx types.Tx `json:"raw_transaction"` 320 }{} 321 322 err := ins.Tx.UnmarshalText([]byte(args[0])) 323 if err != nil { 324 jww.ERROR.Println(err) 325 os.Exit(util.ErrLocalExe) 326 } 327 328 data, exitCode := util.ClientCall("/decode-raw-transaction", &ins) 329 if exitCode != util.Success { 330 os.Exit(exitCode) 331 } 332 333 printJSON(data) 334 }, 335 } 336 337 var getTransactionCmd = &cobra.Command{ 338 Use: "get-transaction <hash>", 339 Short: "get the transaction by matching the given transaction hash", 340 Args: cobra.ExactArgs(1), 341 Run: func(cmd *cobra.Command, args []string) { 342 txInfo := &struct { 343 TxID string `json:"tx_id"` 344 }{TxID: args[0]} 345 346 data, exitCode := util.ClientCall("/get-transaction", txInfo) 347 if exitCode != util.Success { 348 os.Exit(exitCode) 349 } 350 351 printJSON(data) 352 }, 353 } 354 355 var listTransactionsCmd = &cobra.Command{ 356 Use: "list-transactions", 357 Short: "List the transactions", 358 Args: cobra.NoArgs, 359 Run: func(cmd *cobra.Command, args []string) { 360 filter := struct { 361 ID string `json:"id"` 362 AccountID string `json:"account_id"` 363 Detail bool `json:"detail"` 364 Unconfirmed bool `json:"unconfirmed"` 365 }{ID: txID, AccountID: account, Detail: detail, Unconfirmed: unconfirmed} 366 367 data, exitCode := util.ClientCall("/list-transactions", &filter) 368 if exitCode != util.Success { 369 os.Exit(exitCode) 370 } 371 372 printJSONList(data) 373 }, 374 } 375 376 var getUnconfirmedTransactionCmd = &cobra.Command{ 377 Use: "get-unconfirmed-transaction <hash>", 378 Short: "get unconfirmed transaction by matching the given transaction hash", 379 Args: cobra.ExactArgs(1), 380 Run: func(cmd *cobra.Command, args []string) { 381 txID, err := hex.DecodeString(args[0]) 382 if err != nil { 383 jww.ERROR.Println(err) 384 os.Exit(util.ErrLocalExe) 385 } 386 387 txInfo := &struct { 388 TxID chainjson.HexBytes `json:"tx_id"` 389 }{TxID: txID} 390 391 data, exitCode := util.ClientCall("/get-unconfirmed-transaction", txInfo) 392 if exitCode != util.Success { 393 os.Exit(exitCode) 394 } 395 396 printJSON(data) 397 }, 398 } 399 400 var listUnconfirmedTransactionsCmd = &cobra.Command{ 401 Use: "list-unconfirmed-transactions", 402 Short: "list unconfirmed transactions hashes", 403 Args: cobra.NoArgs, 404 Run: func(cmd *cobra.Command, args []string) { 405 data, exitCode := util.ClientCall("/list-unconfirmed-transactions") 406 if exitCode != util.Success { 407 os.Exit(exitCode) 408 } 409 410 printJSON(data) 411 }, 412 } 413 414 var gasRateCmd = &cobra.Command{ 415 Use: "gas-rate", 416 Short: "Print the current gas rate", 417 Args: cobra.NoArgs, 418 Run: func(cmd *cobra.Command, args []string) { 419 data, exitCode := util.ClientCall("/gas-rate") 420 if exitCode != util.Success { 421 os.Exit(exitCode) 422 } 423 printJSON(data) 424 }, 425 }