Skip to main content

iota_json_rpc/
transaction_builder_api.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::sync::Arc;
6
7use async_trait::async_trait;
8use fastcrypto::encoding::Base64;
9use iota_core::authority::AuthorityState;
10use iota_json::IotaJsonValue;
11use iota_json_rpc_api::{
12    TransactionBuilderOpenRpc, TransactionBuilderServer, cap_page_limit, internal_error,
13};
14use iota_json_rpc_types::{
15    IotaObjectDataFilter, IotaObjectDataOptions, IotaObjectResponse,
16    IotaTransactionBlockBuilderMode, IotaTypeTag, RPCTransactionRequestParams,
17    TransactionBlockBytes,
18};
19use iota_open_rpc::Module;
20use iota_sdk_types::{Address, ObjectId, StructTag};
21use iota_transaction_builder::{DataReader, TransactionBuilder};
22use iota_types::iota_serde::BigInt;
23use jsonrpsee::{RpcModule, core::RpcResult};
24
25use crate::{IotaRpcModule, authority_state::StateRead};
26
27pub struct TransactionBuilderApi(TransactionBuilder);
28
29impl TransactionBuilderApi {
30    pub fn new(state: Arc<AuthorityState>) -> Self {
31        let reader = Arc::new(AuthorityStateDataReader::new(state));
32        Self(TransactionBuilder::new(reader))
33    }
34
35    pub fn new_with_data_reader(data_reader: Arc<dyn DataReader + Sync + Send>) -> Self {
36        Self(TransactionBuilder::new(data_reader))
37    }
38}
39
40pub struct AuthorityStateDataReader(Arc<dyn StateRead>);
41
42impl AuthorityStateDataReader {
43    pub fn new(state: Arc<AuthorityState>) -> Self {
44        Self(state)
45    }
46}
47
48#[async_trait]
49impl DataReader for AuthorityStateDataReader {
50    async fn get_owned_objects(
51        &self,
52        address: Address,
53        object_type: StructTag,
54        cursor: Option<ObjectId>,
55        limit: Option<usize>,
56        options: IotaObjectDataOptions,
57    ) -> Result<iota_json_rpc_types::ObjectsPage, anyhow::Error> {
58        let limit = cap_page_limit(limit);
59        let mut rows = self.0.get_owner_objects_with_limit(
60            address,
61            cursor,
62            limit + 1,
63            Some(IotaObjectDataFilter::StructType(object_type)),
64        )?;
65        let has_next_page = rows.len() > limit && limit > 0; // limit == 0 only when RPC_QUERY_MAX_RESULT_LIMIT set to 0
66        rows.truncate(limit);
67        let next_cursor = if has_next_page {
68            rows.last().map(|row| row.object_id)
69        } else {
70            None
71        };
72
73        let data = rows
74            .into_iter()
75            .map(|info| {
76                let read = self.0.get_object_read(&info.object_id)?;
77                IotaObjectResponse::try_from_object_read_and_options(read, &options)
78            })
79            .collect::<Result<Vec<_>, _>>()?;
80
81        Ok(iota_json_rpc_types::ObjectsPage {
82            data,
83            next_cursor,
84            has_next_page,
85        })
86    }
87
88    async fn get_object_with_options(
89        &self,
90        object_id: ObjectId,
91        options: IotaObjectDataOptions,
92    ) -> Result<IotaObjectResponse, anyhow::Error> {
93        let result = self.0.get_object_read(&object_id)?;
94        IotaObjectResponse::try_from_object_read_and_options(result, &options)
95    }
96
97    async fn get_reference_gas_price(&self) -> Result<u64, anyhow::Error> {
98        let epoch_store = self.0.load_epoch_store_one_call_per_task();
99        Ok(epoch_store.reference_gas_price())
100    }
101}
102
103#[async_trait]
104impl TransactionBuilderServer for TransactionBuilderApi {
105    async fn transfer_object(
106        &self,
107        signer: Address,
108        object_id: ObjectId,
109        gas: Option<ObjectId>,
110        gas_budget: BigInt<u64>,
111        recipient: Address,
112    ) -> RpcResult<TransactionBlockBytes> {
113        let data = self
114            .0
115            .transfer_object(signer, object_id, gas, *gas_budget, recipient)
116            .await
117            .map_err(internal_error)?;
118        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
119    }
120
121    async fn transfer_iota(
122        &self,
123        signer: Address,
124        iota_object_id: ObjectId,
125        gas_budget: BigInt<u64>,
126        recipient: Address,
127        amount: Option<BigInt<u64>>,
128    ) -> RpcResult<TransactionBlockBytes> {
129        let data = self
130            .0
131            .transfer_iota(
132                signer,
133                iota_object_id,
134                *gas_budget,
135                recipient,
136                amount.map(|a| *a),
137            )
138            .await
139            .map_err(internal_error)?;
140        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
141    }
142
143    async fn pay(
144        &self,
145        signer: Address,
146        input_coins: Vec<ObjectId>,
147        recipients: Vec<Address>,
148        amounts: Vec<BigInt<u64>>,
149        gas: Option<ObjectId>,
150        gas_budget: BigInt<u64>,
151    ) -> RpcResult<TransactionBlockBytes> {
152        let data = self
153            .0
154            .pay(
155                signer,
156                input_coins,
157                recipients,
158                amounts.into_iter().map(|a| *a).collect(),
159                gas,
160                *gas_budget,
161            )
162            .await
163            .map_err(internal_error)?;
164        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
165    }
166
167    async fn pay_iota(
168        &self,
169        signer: Address,
170        input_coins: Vec<ObjectId>,
171        recipients: Vec<Address>,
172        amounts: Vec<BigInt<u64>>,
173        gas_budget: BigInt<u64>,
174    ) -> RpcResult<TransactionBlockBytes> {
175        let data = self
176            .0
177            .pay_iota(
178                signer,
179                input_coins,
180                recipients,
181                amounts.into_iter().map(|a| *a).collect(),
182                *gas_budget,
183            )
184            .await
185            .map_err(internal_error)?;
186        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
187    }
188
189    async fn pay_all_iota(
190        &self,
191        signer: Address,
192        input_coins: Vec<ObjectId>,
193        recipient: Address,
194        gas_budget: BigInt<u64>,
195    ) -> RpcResult<TransactionBlockBytes> {
196        let data = self
197            .0
198            .pay_all_iota(signer, input_coins, recipient, *gas_budget)
199            .await
200            .map_err(internal_error)?;
201        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
202    }
203
204    async fn publish(
205        &self,
206        sender: Address,
207        compiled_modules: Vec<Base64>,
208        dependencies: Vec<ObjectId>,
209        gas: Option<ObjectId>,
210        gas_budget: BigInt<u64>,
211    ) -> RpcResult<TransactionBlockBytes> {
212        let compiled_modules = compiled_modules
213            .into_iter()
214            .map(|data| data.to_vec().map_err(|e| anyhow::anyhow!(e)))
215            .collect::<Result<Vec<_>, _>>()
216            .map_err(internal_error)?;
217        let data = self
218            .0
219            .publish(sender, compiled_modules, dependencies, gas, *gas_budget)
220            .await
221            .map_err(internal_error)?;
222        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
223    }
224
225    async fn split_coin(
226        &self,
227        signer: Address,
228        coin_object_id: ObjectId,
229        split_amounts: Vec<BigInt<u64>>,
230        gas: Option<ObjectId>,
231        gas_budget: BigInt<u64>,
232    ) -> RpcResult<TransactionBlockBytes> {
233        let split_amounts = split_amounts.into_iter().map(|a| *a).collect();
234        let data = self
235            .0
236            .split_coin(signer, coin_object_id, split_amounts, gas, *gas_budget)
237            .await
238            .map_err(internal_error)?;
239        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
240    }
241
242    async fn split_coin_equal(
243        &self,
244        signer: Address,
245        coin_object_id: ObjectId,
246        split_count: BigInt<u64>,
247        gas: Option<ObjectId>,
248        gas_budget: BigInt<u64>,
249    ) -> RpcResult<TransactionBlockBytes> {
250        let data = self
251            .0
252            .split_coin_equal(signer, coin_object_id, *split_count, gas, *gas_budget)
253            .await
254            .map_err(internal_error)?;
255        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
256    }
257
258    async fn merge_coin(
259        &self,
260        signer: Address,
261        primary_coin: ObjectId,
262        coin_to_merge: ObjectId,
263        gas: Option<ObjectId>,
264        gas_budget: BigInt<u64>,
265    ) -> RpcResult<TransactionBlockBytes> {
266        let data = self
267            .0
268            .merge_coins(signer, primary_coin, coin_to_merge, gas, *gas_budget)
269            .await
270            .map_err(internal_error)?;
271        Ok(TransactionBlockBytes::from_data(data).map_err(internal_error)?)
272    }
273
274    async fn move_call(
275        &self,
276        signer: Address,
277        package_object_id: ObjectId,
278        module: String,
279        function: String,
280        type_arguments: Vec<IotaTypeTag>,
281        rpc_arguments: Vec<IotaJsonValue>,
282        gas: Option<ObjectId>,
283        gas_budget: BigInt<u64>,
284        _txn_builder_mode: Option<IotaTransactionBlockBuilderMode>,
285    ) -> RpcResult<TransactionBlockBytes> {
286        Ok(TransactionBlockBytes::from_data(
287            self.0
288                .move_call(
289                    signer,
290                    package_object_id,
291                    &module,
292                    &function,
293                    type_arguments,
294                    rpc_arguments,
295                    gas,
296                    *gas_budget,
297                    None,
298                )
299                .await
300                .map_err(internal_error)?,
301        )
302        .map_err(internal_error)?)
303    }
304
305    async fn batch_transaction(
306        &self,
307        signer: Address,
308        params: Vec<RPCTransactionRequestParams>,
309        gas: Option<ObjectId>,
310        gas_budget: BigInt<u64>,
311        _txn_builder_mode: Option<IotaTransactionBlockBuilderMode>,
312    ) -> RpcResult<TransactionBlockBytes> {
313        Ok(TransactionBlockBytes::from_data(
314            self.0
315                .batch_transaction(signer, params, gas, *gas_budget)
316                .await
317                .map_err(internal_error)?,
318        )
319        .map_err(internal_error)?)
320    }
321
322    async fn request_add_stake(
323        &self,
324        signer: Address,
325        coins: Vec<ObjectId>,
326        amount: Option<BigInt<u64>>,
327        validator: Address,
328        gas: Option<ObjectId>,
329        gas_budget: BigInt<u64>,
330    ) -> RpcResult<TransactionBlockBytes> {
331        let amount = amount.map(|a| *a);
332        Ok(TransactionBlockBytes::from_data(
333            self.0
334                .request_add_stake(signer, coins, amount, validator, gas, *gas_budget)
335                .await
336                .map_err(internal_error)?,
337        )
338        .map_err(internal_error)?)
339    }
340
341    async fn request_withdraw_stake(
342        &self,
343        signer: Address,
344        staked_iota: ObjectId,
345        gas: Option<ObjectId>,
346        gas_budget: BigInt<u64>,
347    ) -> RpcResult<TransactionBlockBytes> {
348        Ok(TransactionBlockBytes::from_data(
349            self.0
350                .request_withdraw_stake(signer, staked_iota, gas, *gas_budget)
351                .await
352                .map_err(internal_error)?,
353        )
354        .map_err(internal_error)?)
355    }
356
357    async fn request_add_timelocked_stake(
358        &self,
359        signer: Address,
360        locked_balance: ObjectId,
361        validator: Address,
362        gas: ObjectId,
363        gas_budget: BigInt<u64>,
364    ) -> RpcResult<TransactionBlockBytes> {
365        Ok(TransactionBlockBytes::from_data(
366            self.0
367                .request_add_timelocked_stake(signer, locked_balance, validator, gas, *gas_budget)
368                .await
369                .map_err(internal_error)?,
370        )
371        .map_err(internal_error)?)
372    }
373
374    async fn request_withdraw_timelocked_stake(
375        &self,
376        signer: Address,
377        timelocked_staked_iota: ObjectId,
378        gas: ObjectId,
379        gas_budget: BigInt<u64>,
380    ) -> RpcResult<TransactionBlockBytes> {
381        Ok(TransactionBlockBytes::from_data(
382            self.0
383                .request_withdraw_timelocked_stake(signer, timelocked_staked_iota, gas, *gas_budget)
384                .await
385                .map_err(internal_error)?,
386        )
387        .map_err(internal_error)?)
388    }
389}
390
391impl IotaRpcModule for TransactionBuilderApi {
392    fn rpc(self) -> RpcModule<Self> {
393        self.into_rpc()
394    }
395
396    fn rpc_doc_module() -> Module {
397        TransactionBuilderOpenRpc::module_doc()
398    }
399}