Skip to main content

iota_json_rpc_api/
transaction_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use fastcrypto::encoding::Base64;
6use iota_json::IotaJsonValue;
7use iota_json_rpc_types::{
8    IotaTransactionBlockBuilderMode, IotaTypeTag, RPCTransactionRequestParams,
9    TransactionBlockBytes,
10    iota_primitives::{
11        Address as AddressSchema, Base64 as Base64Schema, ObjectId as ObjectIdSchema,
12        TypeTag as TypeTagSchema,
13    },
14};
15use iota_open_rpc_macros::open_rpc;
16use iota_sdk_types::{Address, ObjectId};
17use iota_types::iota_serde::BigInt;
18use jsonrpsee::{core::RpcResult, proc_macros::rpc};
19
20/// Provides methods for constructing transactions such as transferring objects,
21/// sending coins, performing Move calls, or managing stakes.
22#[open_rpc(namespace = "unsafe", tag = "Transaction Builder API")]
23#[rpc(server, client, namespace = "unsafe")]
24pub trait TransactionBuilder {
25    /// Create an unsigned transaction to transfer an object from one address to
26    /// another. The object's type must allow public transfers
27    #[rustfmt::skip]
28    #[method(name = "transferObject")]
29    async fn transfer_object(
30        &self,
31        /// the transaction signer's IOTA address
32        #[schemars(with = "AddressSchema")]
33        signer: Address,
34        /// the ID of the object to be transferred
35        #[schemars(with = "ObjectIdSchema")]
36        object_id: ObjectId,
37        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
38        #[schemars(with = "Option<ObjectIdSchema>")]
39        gas: Option<ObjectId>,
40        /// the gas budget, the transaction will fail if the gas cost exceed the budget
41        #[schemars(with = "String")]
42        gas_budget: BigInt<u64>,
43        /// the recipient's IOTA address
44        #[schemars(with = "AddressSchema")]
45        recipient: Address,
46    ) -> RpcResult<TransactionBlockBytes>;
47
48    /// Create an unsigned transaction to send IOTA coin object to an IOTA address.
49    /// The IOTA object is also used as the gas object.
50    #[rustfmt::skip]
51    #[method(name = "transferIota")]
52    async fn transfer_iota(
53        &self,
54        /// the transaction signer's IOTA address
55        #[schemars(with = "AddressSchema")]
56        signer: Address,
57        /// the IOTA coin object to be used in this transaction
58        #[schemars(with = "ObjectIdSchema")]
59        iota_object_id: ObjectId,
60        /// the gas budget, the transaction will fail if the gas cost exceed the budget
61        #[schemars(with = "String")]
62        gas_budget: BigInt<u64>,
63        /// the recipient's IOTA address
64        #[schemars(with = "AddressSchema")]
65        recipient: Address,
66        /// the amount to be split out and transferred
67        #[schemars(with = "Option<String>")]
68        amount: Option<BigInt<u64>>,
69    ) -> RpcResult<TransactionBlockBytes>;
70
71    /// Send `Coin<T>` to a list of addresses, where `T` can be any coin type,
72    /// following a list of amounts, The object specified in the `gas` field
73    /// will be used to pay the gas fee for the transaction. The gas object can
74    /// not appear in `input_coins`. If the gas object is not specified, the RPC
75    /// server will auto-select one.
76    #[rustfmt::skip]
77    #[method(name = "pay")]
78    async fn pay(
79        &self,
80        /// the transaction signer's IOTA address
81        #[schemars(with = "AddressSchema")]
82        signer: Address,
83        /// the IOTA coins to be used in this transaction
84        #[schemars(with = "Vec<ObjectIdSchema>")]
85        input_coins: Vec<ObjectId>,
86        /// the recipients' addresses, the length of this vector must be the same as amounts.
87        #[schemars(with = "Vec<AddressSchema>")]
88        recipients: Vec<Address>,
89        /// the amounts to be transferred to recipients, following the same order
90        #[schemars(with = "Vec<String>")]
91        amounts: Vec<BigInt<u64>>,
92        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
93        #[schemars(with = "Option<ObjectIdSchema>")]
94        gas: Option<ObjectId>,
95        /// the gas budget, the transaction will fail if the gas cost exceed the budget
96        #[schemars(with = "String")]
97        gas_budget: BigInt<u64>,
98    ) -> RpcResult<TransactionBlockBytes>;
99
100    /// Send IOTA coins to a list of addresses, following a list of amounts.
101    /// This is for IOTA coin only and does not require a separate gas coin object.
102    /// Specifically, what pay_iota does are:
103    /// 1. debit each input_coin to create new coin following the order of
104    /// amounts and assign it to the corresponding recipient.
105    /// 2. accumulate all residual IOTA from input coins left and deposit all IOTA to the first
106    /// input coin, then use the first input coin as the gas coin object.
107    /// 3. the balance of the first input coin after tx is sum(input_coins) - sum(amounts) -
108    ///    actual_gas_cost
109    /// 4. all other input coints other than the first one are deleted.
110    #[rustfmt::skip]
111    #[method(name = "payIota")]
112    async fn pay_iota(
113        &self,
114        /// the transaction signer's IOTA address
115        #[schemars(with = "AddressSchema")]
116        signer: Address,
117        /// the IOTA coins to be used in this transaction, including the coin for gas payment.
118        #[schemars(with = "Vec<ObjectIdSchema>")]
119        input_coins: Vec<ObjectId>,
120        /// the recipients' addresses, the length of this vector must be the same as amounts.
121        #[schemars(with = "Vec<AddressSchema>")]
122        recipients: Vec<Address>,
123        /// the amounts to be transferred to recipients, following the same order
124        #[schemars(with = "Vec<String>")]
125        amounts: Vec<BigInt<u64>>,
126        /// the gas budget, the transaction will fail if the gas cost exceed the budget
127        #[schemars(with = "String")]
128        gas_budget: BigInt<u64>,
129    ) -> RpcResult<TransactionBlockBytes>;
130
131    /// Send all IOTA coins to one recipient.
132    /// This is for IOTA coin only and does not require a separate gas coin object.
133    /// Specifically, what pay_all_iota does are:
134    /// 1. accumulate all IOTA from input coins and deposit all IOTA to the first input coin
135    /// 2. transfer the updated first coin to the recipient and also use this first coin as gas coin
136    ///    object.
137    /// 3. the balance of the first input coin after tx is sum(input_coins) - actual_gas_cost.
138    /// 4. all other input coins other than the first are deleted.
139    #[rustfmt::skip]
140    #[method(name = "payAllIota")]
141    async fn pay_all_iota(
142        &self,
143        /// the transaction signer's IOTA address
144        #[schemars(with = "AddressSchema")]
145        signer: Address,
146        /// the IOTA coins to be used in this transaction, including the coin for gas payment.
147        #[schemars(with = "Vec<ObjectIdSchema>")]
148        input_coins: Vec<ObjectId>,
149        /// the recipient address,
150        #[schemars(with = "AddressSchema")]
151        recipient: Address,
152        /// the gas budget, the transaction will fail if the gas cost exceed the budget
153        #[schemars(with = "String")]
154        gas_budget: BigInt<u64>,
155    ) -> RpcResult<TransactionBlockBytes>;
156
157    /// Create an unsigned transaction to execute a Move call on the network, by
158    /// calling the specified function in the module of a given package.
159    #[rustfmt::skip]
160    #[method(name = "moveCall")]
161    async fn move_call(
162        &self,
163        /// the transaction signer's IOTA address
164        #[schemars(with = "AddressSchema")]
165        signer: Address,
166        /// the Move package ID, e.g. `0x2`
167        #[schemars(with = "ObjectIdSchema")]
168        package_object_id: ObjectId,
169        /// the Move module name, e.g. `pay`
170        module: String,
171        /// the move function name, e.g. `split`
172        function: String,
173        /// the type arguments of the Move function
174        #[schemars(with = "Vec<TypeTagSchema>")]
175        type_arguments: Vec<IotaTypeTag>,
176        /// the arguments to be passed into the Move function, in [IotaJson](https://docs.iota.org/developer/references/iota-api) format
177        arguments: Vec<IotaJsonValue>,
178        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
179        #[schemars(with = "Option<ObjectIdSchema>")]
180        gas: Option<ObjectId>,
181        /// the gas budget, the transaction will fail if the gas cost exceed the budget
182        #[schemars(with = "String")]
183        gas_budget: BigInt<u64>,
184        /// Whether this is a Normal transaction or a Dev Inspect Transaction. Default to be `IotaTransactionBlockBuilderMode::Commit` when it's None.
185        execution_mode: Option<IotaTransactionBlockBuilderMode>,
186    ) -> RpcResult<TransactionBlockBytes>;
187
188    /// Create an unsigned transaction to publish a Move package.
189    #[rustfmt::skip]
190    #[method(name = "publish")]
191    async fn publish(
192        &self,
193        /// the transaction signer's IOTA address
194        #[schemars(with = "AddressSchema")]
195        sender: Address,
196        /// the compiled bytes of a Move package
197        #[schemars(with = "Vec<Base64Schema>")]
198        compiled_modules: Vec<Base64>,
199        /// a list of transitive dependency addresses that this set of modules depends on.
200        #[schemars(with = "Vec<ObjectIdSchema>")]
201        dependencies: Vec<ObjectId>,
202        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
203        #[schemars(with = "Option<ObjectIdSchema>")]
204        gas: Option<ObjectId>,
205        /// the gas budget, the transaction will fail if the gas cost exceed the budget
206        #[schemars(with = "String")]
207        gas_budget: BigInt<u64>,
208    ) -> RpcResult<TransactionBlockBytes>;
209
210    /// Create an unsigned transaction to split a coin object into multiple
211    /// coins.
212    #[rustfmt::skip]
213    #[method(name = "splitCoin")]
214    async fn split_coin(
215        &self,
216        /// the transaction signer's IOTA address
217        #[schemars(with = "AddressSchema")]
218        signer: Address,
219        /// the coin object to be spilt
220        #[schemars(with = "ObjectIdSchema")]
221        coin_object_id: ObjectId,
222        /// the amounts to split out from the coin
223        #[schemars(with = "Vec<String>")]
224        split_amounts: Vec<BigInt<u64>>,
225        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
226        #[schemars(with = "Option<ObjectIdSchema>")]
227        gas: Option<ObjectId>,
228        /// the gas budget, the transaction will fail if the gas cost exceed the budget
229        #[schemars(with = "String")]
230        gas_budget: BigInt<u64>,
231    ) -> RpcResult<TransactionBlockBytes>;
232
233    /// Create an unsigned transaction to split a coin object into multiple
234    /// equal-size coins.
235    #[rustfmt::skip]
236    #[method(name = "splitCoinEqual")]
237    async fn split_coin_equal(
238        &self,
239        /// the transaction signer's IOTA address
240        #[schemars(with = "AddressSchema")]
241        signer: Address,
242        /// the coin object to be spilt
243        #[schemars(with = "ObjectIdSchema")]
244        coin_object_id: ObjectId,
245        /// the number of coins to split into
246        #[schemars(with = "String")]
247        split_count: BigInt<u64>,
248        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
249        #[schemars(with = "Option<ObjectIdSchema>")]
250        gas: Option<ObjectId>,
251        /// the gas budget, the transaction will fail if the gas cost exceed the budget
252        #[schemars(with = "String")]
253        gas_budget: BigInt<u64>,
254    ) -> RpcResult<TransactionBlockBytes>;
255
256    /// Create an unsigned transaction to merge multiple coins into one coin.
257    #[rustfmt::skip]
258    #[method(name = "mergeCoins")]
259    async fn merge_coin(
260        &self,
261        /// the transaction signer's IOTA address
262        #[schemars(with = "AddressSchema")]
263        signer: Address,
264        /// the coin object to merge into, this coin will remain after the transaction
265        #[schemars(with = "ObjectIdSchema")]
266        primary_coin: ObjectId,
267        /// the coin object to be merged, this coin will be destroyed, the balance will be added to `primary_coin`
268        #[schemars(with = "ObjectIdSchema")]
269        coin_to_merge: ObjectId,
270        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
271        #[schemars(with = "Option<ObjectIdSchema>")]
272        gas: Option<ObjectId>,
273        /// the gas budget, the transaction will fail if the gas cost exceed the budget
274        #[schemars(with = "String")]
275        gas_budget: BigInt<u64>,
276    ) -> RpcResult<TransactionBlockBytes>;
277
278    /// Create an unsigned batched transaction.
279    #[rustfmt::skip]
280    #[method(name = "batchTransaction")]
281    async fn batch_transaction(
282        &self,
283        /// the transaction signer's IOTA address
284        #[schemars(with = "AddressSchema")]
285        signer: Address,
286        /// list of transaction request parameters
287        single_transaction_params: Vec<RPCTransactionRequestParams>,
288        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
289        #[schemars(with = "Option<ObjectIdSchema>")]
290        gas: Option<ObjectId>,
291        /// the gas budget, the transaction will fail if the gas cost exceed the budget
292        #[schemars(with = "String")]
293        gas_budget: BigInt<u64>,
294        /// Whether this is a regular transaction or a Dev Inspect Transaction
295        txn_builder_mode: Option<IotaTransactionBlockBuilderMode>,
296    ) -> RpcResult<TransactionBlockBytes>;
297
298    /// Add stake to a validator's staking pool using multiple coins and amount.
299    #[rustfmt::skip]
300    #[method(name = "requestAddStake")]
301    async fn request_add_stake(
302        &self,
303        /// the transaction signer's IOTA address
304        #[schemars(with = "AddressSchema")]
305        signer: Address,
306        /// Coin<IOTA> object to stake
307        #[schemars(with = "Vec<ObjectIdSchema>")]
308        coins: Vec<ObjectId>,
309        /// stake amount
310        #[schemars(with = "Option<String>")]
311        amount: Option<BigInt<u64>>,
312        /// the validator's IOTA address
313        #[schemars(with = "AddressSchema")]
314        validator: Address,
315        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
316        #[schemars(with = "Option<ObjectIdSchema>")]
317        gas: Option<ObjectId>,
318        /// the gas budget, the transaction will fail if the gas cost exceed the budget
319        #[schemars(with = "String")]
320        gas_budget: BigInt<u64>,
321    ) -> RpcResult<TransactionBlockBytes>;
322
323    /// Withdraw stake from a validator's staking pool.
324    #[rustfmt::skip]
325    #[method(name = "requestWithdrawStake")]
326    async fn request_withdraw_stake(
327        &self,
328        /// the transaction signer's IOTA address
329        #[schemars(with = "AddressSchema")]
330        signer: Address,
331        /// StakedIota object ID
332        #[schemars(with = "ObjectIdSchema")]
333        staked_iota: ObjectId,
334        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
335        #[schemars(with = "Option<ObjectIdSchema>")]
336        gas: Option<ObjectId>,
337        /// the gas budget, the transaction will fail if the gas cost exceed the budget
338        #[schemars(with = "String")]
339        gas_budget: BigInt<u64>,
340    ) -> RpcResult<TransactionBlockBytes>;
341
342    /// Add timelocked stake to a validator's staking pool using multiple balances
343    /// and amount.
344    #[rustfmt::skip]
345    #[method(name = "requestAddTimelockedStake")]
346    async fn request_add_timelocked_stake(
347        &self,
348        /// the transaction signer's IOTA address
349        #[schemars(with = "AddressSchema")]
350        signer: Address,
351        /// TimeLock<Balance<IOTA>> object to stake
352        #[schemars(with = "ObjectIdSchema")]
353        locked_balance: ObjectId,
354        /// the validator's IOTA address
355        #[schemars(with = "AddressSchema")]
356        validator: Address,
357        /// gas object to be used in this transaction
358        #[schemars(with = "ObjectIdSchema")]
359        gas: ObjectId,
360        /// the gas budget, the transaction will fail if the gas cost exceed the budget
361        #[schemars(with = "String")]
362        gas_budget: BigInt<u64>,
363    ) -> RpcResult<TransactionBlockBytes>;
364
365    /// Withdraw timelocked stake from a validator's staking pool.
366    #[rustfmt::skip]
367    #[method(name = "requestWithdrawTimelockedStake")]
368    async fn request_withdraw_timelocked_stake(
369        &self,
370        /// the transaction signer's IOTA address
371        #[schemars(with = "AddressSchema")]
372        signer: Address,
373        /// TimelockedStakedIota object ID
374        #[schemars(with = "ObjectIdSchema")]
375        timelocked_staked_iota: ObjectId,
376        /// gas object to be used in this transaction
377        #[schemars(with = "ObjectIdSchema")]
378        gas: ObjectId,
379        /// the gas budget, the transaction will fail if the gas cost exceed the budget
380        #[schemars(with = "String")]
381        gas_budget: BigInt<u64>,
382    ) -> RpcResult<TransactionBlockBytes>;
383}