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