ergo_node_interface/
transactions.rs

1use std::convert::TryFrom;
2
3use crate::node_interface::{NodeError, NodeInterface, Result};
4use crate::JsonString;
5use ergo_lib::chain::transaction::unsigned::UnsignedTransaction;
6use ergo_lib::chain::transaction::{Transaction, TxId};
7use ergo_lib::ergo_chain_types::Digest32;
8use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox;
9use ergo_lib::ergotree_ir::serialization::{SigmaSerializable, SigmaSerializationError};
10use ergo_lib::wallet::signing::TransactionContext;
11use json::JsonValue;
12use serde_json::json;
13
14impl NodeInterface {
15    /// Submits a Signed Transaction provided as input as JSON
16    /// to the Ergo Blockchain mempool.
17    pub fn submit_json_transaction(&self, signed_tx_json: &JsonString) -> Result<TxId> {
18        let endpoint = "/transactions";
19        let res_json = self.use_json_endpoint_and_check_errors(endpoint, signed_tx_json)?;
20        let tx_id = parse_tx_id_unsafe(res_json);
21        Ok(tx_id)
22    }
23
24    /// Sign an Unsigned Transaction which is formatted in JSON
25    pub fn sign_json_transaction(&self, unsigned_tx_string: &JsonString) -> Result<JsonValue> {
26        let endpoint = "/wallet/transaction/sign";
27        let unsigned_tx_json = json::parse(unsigned_tx_string)
28            .map_err(|_| NodeError::FailedParsingNodeResponse(unsigned_tx_string.to_string()))?;
29
30        let prepared_body = object! {
31            tx: unsigned_tx_json
32        };
33
34        let res_json = self.use_json_endpoint_and_check_errors(endpoint, &prepared_body.dump())?;
35
36        Ok(res_json)
37    }
38
39    /// Sign an Unsigned Transaction which is formatted in JSON
40    /// and then submit it to the mempool.
41    pub fn sign_and_submit_json_transaction(
42        &self,
43        unsigned_tx_string: &JsonString,
44    ) -> Result<TxId> {
45        let signed_tx = self.sign_json_transaction(unsigned_tx_string)?;
46        let signed_tx_json = json::stringify(signed_tx);
47
48        self.submit_json_transaction(&signed_tx_json)
49    }
50
51    /// Submits a Signed `Transaction` provided as input
52    /// to the Ergo Blockchain mempool.
53    pub fn submit_transaction(&self, signed_tx: &Transaction) -> Result<TxId> {
54        let signed_tx_json = &serde_json::to_string(&signed_tx)
55            .map_err(|_| NodeError::Other("Failed Converting `Transaction` to json".to_string()))?;
56        let tx_id = self.submit_json_transaction(signed_tx_json)?;
57        assert_eq!(tx_id, signed_tx.id());
58        Ok(tx_id)
59    }
60
61    /// Sign an `UnsignedTransaction`
62    /// unsigned_tx - The unsigned transaction to sign.
63    /// boxes_to_spend - optional list of input boxes. If not provided, the node will search for the boxes in UTXO
64    /// data_input_boxes - optional list of data boxes. If not provided, the node will search for the data boxes in UTXO
65    pub fn sign_transaction(
66        &self,
67        unsigned_tx: &UnsignedTransaction,
68        boxes_to_spend: Option<Vec<ErgoBox>>,
69        data_input_boxes: Option<Vec<ErgoBox>>,
70    ) -> Result<Transaction> {
71        if let Some(ref boxes_to_spend) = boxes_to_spend {
72            // check input boxes against tx's inputs (for every input should be a box)
73            if let Err(e) = TransactionContext::new(
74                unsigned_tx.clone(),
75                boxes_to_spend.clone(),
76                data_input_boxes.clone().unwrap_or_default(),
77            ) {
78                return Err(NodeError::Other(e.to_string()));
79            };
80        }
81
82        let endpoint = "/wallet/transaction/sign";
83
84        fn encode_boxes(
85            maybe_boxes: Option<Vec<ErgoBox>>,
86        ) -> std::result::Result<Option<Vec<String>>, NodeError> {
87            match maybe_boxes.map(|boxes| {
88                boxes
89                    .iter()
90                    .map(|b| {
91                        b.sigma_serialize_bytes()
92                            .map(|bytes| base16::encode_lower(&bytes))
93                    })
94                    .collect::<std::result::Result<Vec<String>, SigmaSerializationError>>()
95            }) {
96                Some(Ok(base16_boxes)) => Ok(Some(base16_boxes)),
97                Some(Err(e)) => Err(NodeError::Other(e.to_string())),
98                None => Ok(None),
99            }
100        }
101
102        let input_boxes_base16 = encode_boxes(boxes_to_spend)?;
103        let data_input_boxes_base16 = encode_boxes(data_input_boxes)?;
104
105        let prepared_body = json!({
106            "tx": unsigned_tx,
107            "inputsRaw": input_boxes_base16,
108            "dataInputsRaw": data_input_boxes_base16,
109        });
110
111        let json_signed_tx =
112            self.use_json_endpoint_and_check_errors(endpoint, &prepared_body.to_string())?;
113
114        serde_json::from_str(&json_signed_tx.dump())
115            .map_err(|_| NodeError::Other("Failed Converting `Transaction` to json".to_string()))
116    }
117
118    /// Sign an `UnsignedTransaction` and then submit it to the mempool.
119    pub fn sign_and_submit_transaction(&self, unsigned_tx: &UnsignedTransaction) -> Result<TxId> {
120        let signed_tx = self.sign_transaction(unsigned_tx, None, None)?;
121        self.submit_transaction(&signed_tx)
122    }
123
124    /// Generates and submits a tx using the node endpoints. Input is
125    /// a json formatted request with rawInputs (and rawDataInputs)
126    /// manually selected or inputs will be automatically selected by wallet.
127    /// Returns the resulting `TxId`.
128    pub fn generate_and_submit_transaction(&self, tx_request_json: &JsonString) -> Result<TxId> {
129        let endpoint = "/wallet/transaction/send";
130        let res_json = self.use_json_endpoint_and_check_errors(endpoint, tx_request_json)?;
131        let tx_id = parse_tx_id_unsafe(res_json);
132        Ok(tx_id)
133    }
134
135    /// Generates Json of an Unsigned Transaction.
136    /// Input must be a json formatted request with rawInputs (and rawDataInputs)
137    /// manually selected or will be automatically selected by wallet.
138    pub fn generate_json_transaction(&self, tx_request_json: &JsonString) -> Result<JsonValue> {
139        let endpoint = "/wallet/transaction/generate";
140        let res_json = self.use_json_endpoint_and_check_errors(endpoint, tx_request_json)?;
141
142        Ok(res_json)
143    }
144
145    /// Gets the recommended fee for a transaction.
146    /// bytes - size of the transaction in bytes
147    /// wait_time - minutes to wait for the transaction to be included in the blockchain
148    pub fn get_recommended_fee(&self, bytes: u64, wait_time: u64) -> Result<u64> {
149        let endpoint = format!(
150            "/transactions/getFee?bytes={}&waitTime={}",
151            bytes, wait_time
152        );
153        let res = self.send_get_req(&endpoint);
154        let res_json = self.parse_response_to_json(res);
155        let fee = res_json?.as_u64().unwrap();
156        Ok(fee)
157    }
158}
159
160fn parse_tx_id_unsafe(mut res_json: JsonValue) -> TxId {
161    // If tx is valid and is posted, return just the tx id
162    let tx_id_str = res_json.take_string().unwrap();
163    TxId(Digest32::try_from(tx_id_str).unwrap())
164}