thor_devkit/
transaction_builder.rs

1use rand::Rng;
2
3use crate::address::Address;
4use crate::network::ThorNode;
5use crate::rlp::Bytes;
6use crate::transactions::{Clause, Reserved, Transaction};
7use crate::U256;
8
9#[derive(Clone, Debug, Eq, PartialEq, Default)]
10struct TransactionTemplate {
11    block_ref: Option<u64>,
12    expiration: Option<u32>,
13    clauses: Vec<Clause>,
14    gas_price_coef: Option<u8>,
15    gas: Option<u64>,
16    depends_on: Option<U256>,
17    nonce: Option<u64>,
18    delegated: bool,
19}
20
21/// Transaction builder allows to create and prepare transactions
22/// with minimal developers efforts.
23#[derive(Clone, Debug)]
24pub struct TransactionBuilder {
25    node: ThorNode,
26    template: TransactionTemplate,
27}
28
29impl TransactionBuilder {
30    pub fn new(node: ThorNode) -> Self {
31        //! Create a new builder.
32        Self {
33            node,
34            template: TransactionTemplate::default(),
35        }
36    }
37    pub const fn delegated(mut self) -> Self {
38        //! Make a transaction delegated.
39        self.template.delegated = true;
40        self
41    }
42    pub const fn nonce(mut self, nonce: u64) -> Self {
43        //! Set a nonce for transaction.
44        self.template.nonce = Some(nonce);
45        self
46    }
47    pub const fn depends_on(mut self, depends_on: U256) -> Self {
48        //! Mark a transaction as dependent on another one.
49        self.template.depends_on = Some(depends_on);
50        self
51    }
52    pub const fn gas(mut self, gas: u64) -> Self {
53        //! Set maximal gas amount for transaction.
54        self.template.gas = Some(gas);
55        self
56    }
57    pub const fn gas_price_coef(mut self, gas_price_coef: u8) -> Self {
58        //! Set gas price coefficient for transaction.
59        self.template.gas_price_coef = Some(gas_price_coef);
60        self
61    }
62    pub const fn expiration(mut self, expiration: u32) -> Self {
63        //! Set expiration for transaction in blocks, starting from `block_ref`.
64        self.template.expiration = Some(expiration);
65        self
66    }
67    pub const fn block_ref(mut self, block_ref: u64) -> Self {
68        //! Set block_ref for transaction to count `expiration` from.
69        self.template.block_ref = Some(block_ref);
70        self
71    }
72    pub fn add_transfer<T: Into<U256>>(self, recipient: Address, value: T) -> Self {
73        //! Add a simple transfer to clauses.
74        self.add_clause(Clause {
75            to: Some(recipient),
76            value: value.into(),
77            data: Bytes::new(),
78        })
79    }
80    pub fn add_contract_create(self, contract_bytes: Bytes) -> Self {
81        //! Add a contract creation clause.
82        self.add_clause(Clause {
83            to: None,
84            value: U256::zero(),
85            data: contract_bytes,
86        })
87    }
88    pub fn add_contract_call(self, contract_address: Address, call_bytes: Bytes) -> Self {
89        //! Add a contract method call clause.
90        self.add_clause(Clause {
91            to: Some(contract_address),
92            value: U256::zero(),
93            data: call_bytes,
94        })
95    }
96    pub fn add_clause(mut self, clause: Clause) -> Self {
97        //! Add an arbitrary, user-provided clause.
98        self.template.clauses.push(clause);
99        self
100    }
101
102    pub async fn build(&self) -> Result<Transaction, TransactionBuilderError> {
103        //! Prepare a `Transaction`. This may perform a network request
104        //! to identify appropriate parameters.
105        if self.template.clauses.is_empty() {
106            return Err(TransactionBuilderError::EmptyTransaction);
107        }
108        let block_ref = match self.template.block_ref {
109            Some(r) => r,
110            None => self
111                .node
112                .fetch_best_block()
113                .await
114                .map_err(|_| TransactionBuilderError::NetworkError)?
115                .0
116                .block_ref(),
117        };
118        let mut tx = Transaction {
119            chain_tag: self.node.chain_tag,
120            block_ref,
121            expiration: self.template.expiration.unwrap_or(128),
122            clauses: self.template.clauses.clone(),
123            gas_price_coef: self.template.gas_price_coef.unwrap_or(0),
124            gas: self.template.gas.unwrap_or(0),
125            depends_on: self.template.depends_on,
126            nonce: self.template.nonce.unwrap_or_else(|| {
127                let mut rng = rand::rng();
128                rng.random::<u64>()
129            }),
130            reserved: if self.template.delegated {
131                Some(Reserved::new_delegated())
132            } else {
133                None
134            },
135            signature: None,
136        };
137        if self.template.gas.is_some() {
138            Ok(tx)
139        } else if tx.clauses.iter().all(|clause| clause.data.is_empty()) {
140            tx.gas = tx.intrinsic_gas();
141            Ok(tx)
142        } else {
143            Err(TransactionBuilderError::CannotEstimateGas)
144        }
145    }
146}
147
148/// Transaction creation errors
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub enum TransactionBuilderError {
151    /// Network error (failed to fetch data from node)
152    NetworkError,
153    /// No clauses provided
154    EmptyTransaction,
155    /// Transaction clauses involve contract interaction, and gas was not provided.
156    CannotEstimateGas,
157}
158
159impl std::error::Error for TransactionBuilderError {}
160impl std::fmt::Display for TransactionBuilderError {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        match self {
163            Self::NetworkError => f.write_str("Failed to retrieve data from network"),
164            Self::EmptyTransaction => f.write_str("Cannot build an empty transaction - make sure to add at least one clause first."),
165            Self::CannotEstimateGas => f.write_str("Transaction clauses involve contract interaction, please provide gas amount explicitly."),
166        }
167    }
168}