1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
gettxout "txid" n ( include_mempool )

Returns details about an unspent transaction output.

Arguments:
1. txid               (string, required) The transaction id
2. n                  (numeric, required) vout number
3. include_mempool    (boolean, optional, default=true) Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear.

Result:
{                             (json object)
  "bestblock" : "hex",        (string) The hash of the block at the tip of the chain
  "confirmations" : n,        (numeric) The number of confirmations
  "value" : n,                (numeric) The transaction value in BTC
  "scriptPubKey" : {          (json object)
    "asm" : "hex",            (string)
    "hex" : "hex",            (string)
    "reqSigs" : n,            (numeric) Number of required signatures
    "type" : "hex",           (string) The type, eg pubkeyhash
    "addresses" : [           (json array) array of bitcoin addresses
      "str",                  (string) bitcoin address
      ...
    ]
  },
  "coinbase" : true|false     (boolean) Coinbase or not
}

Examples:

Get unspent transactions
> bitcoin-cli listunspent

View the details
> bitcoin-cli gettxout "txid" 1

As a JSON-RPC call
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "gettxout", "params": ["txid", 1]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/
 */

use crate::{
    client::Client,
    command::{request::request, CallableCommand},
};
use serde::{Deserialize, Serialize};
use serde_json::value::to_raw_value;

const GET_TX_OUT_COMMAND: &str = "gettxout";

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ScriptPubKey {
    pub asm: String,           // "hex"
    pub hex: String,           // "hex"
    pub req_sigs: Option<u64>, // Number of required signatures
    #[serde(alias = "type")]
    pub type_: String, // The type, eg pubkeyhash
    // TODO: Why are there both of these. The docs say there is an "addresses" field
    // (https://bitcoincore.org/en/doc/0.21.0/rpc/blockchain/gettxout/) but the transaction I'm
    // testing only has an "address" field. Why? Will it return either/or?
    pub addresses: Option<Vec<String>>, // array of bitcoin addresses
    pub address: Option<String>,        // bitcoin addresses
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GetTxOutCommandResponse {
    bestblock: String,  // "hex" The hash of the block at the tip of the chain
    confirmations: u64, // The number of confirmations
    value: f64,         // The transaction value in BTC
    script_pub_key: ScriptPubKey,
    coinbase: bool, // Coinbase or not
}

pub struct GetTxOutCommand {
    tx_id: String,                 // (string, required) The transaction id
    n: u64,                        // (numeric, required) vout number
    include_mempool: Option<bool>, // (boolean, optional, default=true) Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear.
}
impl GetTxOutCommand {
    pub fn new(tx_id: String, n: u64) -> Self {
        GetTxOutCommand {
            tx_id,
            n,
            include_mempool: None,
        }
    }
    // TODO: Currently errors out if you don't enable mempool inclusion but search for a mempool
    // transaction
    pub fn include_mempool(&mut self, include_mempool: bool) -> &Self {
        self.include_mempool = Some(include_mempool);
        self
    }
}
impl CallableCommand for GetTxOutCommand {
    type Response = GetTxOutCommandResponse;
    fn call(&self, client: &Client) -> Result<Self::Response, jsonrpc::Error> {
        let tx_id_arg = &self.tx_id;
        let n_arg = &self.n;
        let include_mempool = &self.include_mempool;

        let tx_id_arg_raw_value = to_raw_value(&tx_id_arg).unwrap();
        let n_arg_raw_value = to_raw_value(&n_arg).unwrap();
        let mut params = vec![tx_id_arg_raw_value, n_arg_raw_value];
        if let Some(include_mempool_arg) = include_mempool {
            let include_mempool_arg_raw_value = to_raw_value(&include_mempool_arg).unwrap();
            params.push(include_mempool_arg_raw_value)
        }
        let r = request(client, GET_TX_OUT_COMMAND, params);
        let response: GetTxOutCommandResponse = r.result()?;
        Ok(response)
    }
}