thor_devkit/
transaction_builder.rs1use 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#[derive(Clone, Debug)]
24pub struct TransactionBuilder {
25 node: ThorNode,
26 template: TransactionTemplate,
27}
28
29impl TransactionBuilder {
30 pub fn new(node: ThorNode) -> Self {
31 Self {
33 node,
34 template: TransactionTemplate::default(),
35 }
36 }
37 pub const fn delegated(mut self) -> Self {
38 self.template.delegated = true;
40 self
41 }
42 pub const fn nonce(mut self, nonce: u64) -> Self {
43 self.template.nonce = Some(nonce);
45 self
46 }
47 pub const fn depends_on(mut self, depends_on: U256) -> Self {
48 self.template.depends_on = Some(depends_on);
50 self
51 }
52 pub const fn gas(mut self, gas: u64) -> Self {
53 self.template.gas = Some(gas);
55 self
56 }
57 pub const fn gas_price_coef(mut self, gas_price_coef: u8) -> Self {
58 self.template.gas_price_coef = Some(gas_price_coef);
60 self
61 }
62 pub const fn expiration(mut self, expiration: u32) -> Self {
63 self.template.expiration = Some(expiration);
65 self
66 }
67 pub const fn block_ref(mut self, block_ref: u64) -> Self {
68 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 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 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 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 self.template.clauses.push(clause);
99 self
100 }
101
102 pub async fn build(&self) -> Result<Transaction, TransactionBuilderError> {
103 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#[derive(Clone, Debug, Eq, PartialEq)]
150pub enum TransactionBuilderError {
151 NetworkError,
153 EmptyTransaction,
155 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}