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
106 /// to the first
107 /// input coin, then use the first input coin as the gas coin object.
108 /// 3. the balance of the first input coin after tx is sum(input_coins) -
109 /// sum(amounts) - actual_gas_cost
110 /// 4. all other input coints other than the first one are deleted.
111 #[rustfmt::skip]
112 #[method(name = "payIota")]
113 async fn pay_iota(
114 &self,
115 /// the transaction signer's IOTA address
116 #[schemars(with = "AddressSchema")]
117 signer: Address,
118 /// the IOTA coins to be used in this transaction, including the coin for gas payment.
119 #[schemars(with = "Vec<ObjectIdSchema>")]
120 input_coins: Vec<ObjectId>,
121 /// the recipients' addresses, the length of this vector must be the same as amounts.
122 #[schemars(with = "Vec<AddressSchema>")]
123 recipients: Vec<Address>,
124 /// the amounts to be transferred to recipients, following the same order
125 #[schemars(with = "Vec<String>")]
126 amounts: Vec<BigInt<u64>>,
127 /// the gas budget, the transaction will fail if the gas cost exceed the budget
128 #[schemars(with = "String")]
129 gas_budget: BigInt<u64>,
130 ) -> RpcResult<TransactionBlockBytes>;
131
132 /// Send all IOTA coins to one recipient.
133 /// This is for IOTA coin only and does not require a separate gas coin object.
134 /// Specifically, what pay_all_iota does are:
135 /// 1. accumulate all IOTA from input coins and deposit all IOTA to the first
136 /// input coin
137 /// 2. transfer the updated first coin to the recipient and also use this first
138 /// coin as gas coin object.
139 /// 3. the balance of the first input coin after tx is sum(input_coins) -
140 /// actual_gas_cost.
141 /// 4. all other input coins other than the first are deleted.
142 #[rustfmt::skip]
143 #[method(name = "payAllIota")]
144 async fn pay_all_iota(
145 &self,
146 /// the transaction signer's IOTA address
147 #[schemars(with = "AddressSchema")]
148 signer: Address,
149 /// the IOTA coins to be used in this transaction, including the coin for gas payment.
150 #[schemars(with = "Vec<ObjectIdSchema>")]
151 input_coins: Vec<ObjectId>,
152 /// the recipient address,
153 #[schemars(with = "AddressSchema")]
154 recipient: Address,
155 /// the gas budget, the transaction will fail if the gas cost exceed the budget
156 #[schemars(with = "String")]
157 gas_budget: BigInt<u64>,
158 ) -> RpcResult<TransactionBlockBytes>;
159
160 /// Create an unsigned transaction to execute a Move call on the network, by
161 /// calling the specified function in the module of a given package.
162 #[rustfmt::skip]
163 #[method(name = "moveCall")]
164 async fn move_call(
165 &self,
166 /// the transaction signer's IOTA address
167 #[schemars(with = "AddressSchema")]
168 signer: Address,
169 /// the Move package ID, e.g. `0x2`
170 #[schemars(with = "ObjectIdSchema")]
171 package_object_id: ObjectId,
172 /// the Move module name, e.g. `pay`
173 module: String,
174 /// the move function name, e.g. `split`
175 function: String,
176 /// the type arguments of the Move function
177 #[schemars(with = "Vec<TypeTagSchema>")]
178 type_arguments: Vec<IotaTypeTag>,
179 /// the arguments to be passed into the Move function, in [IotaJson](https://docs.iota.org/developer/references/iota-api) format
180 arguments: Vec<IotaJsonValue>,
181 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
182 #[schemars(with = "Option<ObjectIdSchema>")]
183 gas: Option<ObjectId>,
184 /// the gas budget, the transaction will fail if the gas cost exceed the budget
185 #[schemars(with = "String")]
186 gas_budget: BigInt<u64>,
187 /// Whether this is a Normal transaction or a Dev Inspect Transaction. Default to be `IotaTransactionBlockBuilderMode::Commit` when it's None.
188 execution_mode: Option<IotaTransactionBlockBuilderMode>,
189 ) -> RpcResult<TransactionBlockBytes>;
190
191 /// Create an unsigned transaction to publish a Move package.
192 #[rustfmt::skip]
193 #[method(name = "publish")]
194 async fn publish(
195 &self,
196 /// the transaction signer's IOTA address
197 #[schemars(with = "AddressSchema")]
198 sender: Address,
199 /// the compiled bytes of a Move package
200 #[schemars(with = "Vec<Base64Schema>")]
201 compiled_modules: Vec<Base64>,
202 /// a list of transitive dependency addresses that this set of modules depends on.
203 #[schemars(with = "Vec<ObjectIdSchema>")]
204 dependencies: Vec<ObjectId>,
205 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
206 #[schemars(with = "Option<ObjectIdSchema>")]
207 gas: Option<ObjectId>,
208 /// the gas budget, the transaction will fail if the gas cost exceed the budget
209 #[schemars(with = "String")]
210 gas_budget: BigInt<u64>,
211 ) -> RpcResult<TransactionBlockBytes>;
212
213 /// Create an unsigned transaction to split a coin object into multiple
214 /// coins.
215 #[rustfmt::skip]
216 #[method(name = "splitCoin")]
217 async fn split_coin(
218 &self,
219 /// the transaction signer's IOTA address
220 #[schemars(with = "AddressSchema")]
221 signer: Address,
222 /// the coin object to be spilt
223 #[schemars(with = "ObjectIdSchema")]
224 coin_object_id: ObjectId,
225 /// the amounts to split out from the coin
226 #[schemars(with = "Vec<String>")]
227 split_amounts: Vec<BigInt<u64>>,
228 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
229 #[schemars(with = "Option<ObjectIdSchema>")]
230 gas: Option<ObjectId>,
231 /// the gas budget, the transaction will fail if the gas cost exceed the budget
232 #[schemars(with = "String")]
233 gas_budget: BigInt<u64>,
234 ) -> RpcResult<TransactionBlockBytes>;
235
236 /// Create an unsigned transaction to split a coin object into multiple
237 /// equal-size coins.
238 #[rustfmt::skip]
239 #[method(name = "splitCoinEqual")]
240 async fn split_coin_equal(
241 &self,
242 /// the transaction signer's IOTA address
243 #[schemars(with = "AddressSchema")]
244 signer: Address,
245 /// the coin object to be spilt
246 #[schemars(with = "ObjectIdSchema")]
247 coin_object_id: ObjectId,
248 /// the number of coins to split into
249 #[schemars(with = "String")]
250 split_count: BigInt<u64>,
251 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
252 #[schemars(with = "Option<ObjectIdSchema>")]
253 gas: Option<ObjectId>,
254 /// the gas budget, the transaction will fail if the gas cost exceed the budget
255 #[schemars(with = "String")]
256 gas_budget: BigInt<u64>,
257 ) -> RpcResult<TransactionBlockBytes>;
258
259 /// Create an unsigned transaction to merge multiple coins into one coin.
260 #[rustfmt::skip]
261 #[method(name = "mergeCoins")]
262 async fn merge_coin(
263 &self,
264 /// the transaction signer's IOTA address
265 #[schemars(with = "AddressSchema")]
266 signer: Address,
267 /// the coin object to merge into, this coin will remain after the transaction
268 #[schemars(with = "ObjectIdSchema")]
269 primary_coin: ObjectId,
270 /// the coin object to be merged, this coin will be destroyed, the balance will be added to `primary_coin`
271 #[schemars(with = "ObjectIdSchema")]
272 coin_to_merge: ObjectId,
273 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
274 #[schemars(with = "Option<ObjectIdSchema>")]
275 gas: Option<ObjectId>,
276 /// the gas budget, the transaction will fail if the gas cost exceed the budget
277 #[schemars(with = "String")]
278 gas_budget: BigInt<u64>,
279 ) -> RpcResult<TransactionBlockBytes>;
280
281 /// Create an unsigned batched transaction.
282 #[rustfmt::skip]
283 #[method(name = "batchTransaction")]
284 async fn batch_transaction(
285 &self,
286 /// the transaction signer's IOTA address
287 #[schemars(with = "AddressSchema")]
288 signer: Address,
289 /// list of transaction request parameters
290 single_transaction_params: Vec<RPCTransactionRequestParams>,
291 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
292 #[schemars(with = "Option<ObjectIdSchema>")]
293 gas: Option<ObjectId>,
294 /// the gas budget, the transaction will fail if the gas cost exceed the budget
295 #[schemars(with = "String")]
296 gas_budget: BigInt<u64>,
297 /// Whether this is a regular transaction or a Dev Inspect Transaction
298 txn_builder_mode: Option<IotaTransactionBlockBuilderMode>,
299 ) -> RpcResult<TransactionBlockBytes>;
300
301 /// Add stake to a validator's staking pool using multiple coins and amount.
302 #[rustfmt::skip]
303 #[method(name = "requestAddStake")]
304 async fn request_add_stake(
305 &self,
306 /// the transaction signer's IOTA address
307 #[schemars(with = "AddressSchema")]
308 signer: Address,
309 /// Coin<IOTA> object to stake
310 #[schemars(with = "Vec<ObjectIdSchema>")]
311 coins: Vec<ObjectId>,
312 /// stake amount
313 #[schemars(with = "Option<String>")]
314 amount: Option<BigInt<u64>>,
315 /// the validator's IOTA address
316 #[schemars(with = "AddressSchema")]
317 validator: Address,
318 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
319 #[schemars(with = "Option<ObjectIdSchema>")]
320 gas: Option<ObjectId>,
321 /// the gas budget, the transaction will fail if the gas cost exceed the budget
322 #[schemars(with = "String")]
323 gas_budget: BigInt<u64>,
324 ) -> RpcResult<TransactionBlockBytes>;
325
326 /// Withdraw stake from a validator's staking pool.
327 #[rustfmt::skip]
328 #[method(name = "requestWithdrawStake")]
329 async fn request_withdraw_stake(
330 &self,
331 /// the transaction signer's IOTA address
332 #[schemars(with = "AddressSchema")]
333 signer: Address,
334 /// StakedIota object ID
335 #[schemars(with = "ObjectIdSchema")]
336 staked_iota: ObjectId,
337 /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
338 #[schemars(with = "Option<ObjectIdSchema>")]
339 gas: Option<ObjectId>,
340 /// the gas budget, the transaction will fail if the gas cost exceed the budget
341 #[schemars(with = "String")]
342 gas_budget: BigInt<u64>,
343 ) -> RpcResult<TransactionBlockBytes>;
344
345 /// Add timelocked stake to a validator's staking pool using multiple balances
346 /// and amount.
347 #[rustfmt::skip]
348 #[method(name = "requestAddTimelockedStake")]
349 async fn request_add_timelocked_stake(
350 &self,
351 /// the transaction signer's IOTA address
352 #[schemars(with = "AddressSchema")]
353 signer: Address,
354 /// TimeLock<Balance<IOTA>> object to stake
355 #[schemars(with = "ObjectIdSchema")]
356 locked_balance: ObjectId,
357 /// the validator's IOTA address
358 #[schemars(with = "AddressSchema")]
359 validator: Address,
360 /// gas object to be used in this transaction
361 #[schemars(with = "ObjectIdSchema")]
362 gas: ObjectId,
363 /// the gas budget, the transaction will fail if the gas cost exceed the budget
364 #[schemars(with = "String")]
365 gas_budget: BigInt<u64>,
366 ) -> RpcResult<TransactionBlockBytes>;
367
368 /// Withdraw timelocked stake from a validator's staking pool.
369 #[rustfmt::skip]
370 #[method(name = "requestWithdrawTimelockedStake")]
371 async fn request_withdraw_timelocked_stake(
372 &self,
373 /// the transaction signer's IOTA address
374 #[schemars(with = "AddressSchema")]
375 signer: Address,
376 /// TimelockedStakedIota object ID
377 #[schemars(with = "ObjectIdSchema")]
378 timelocked_staked_iota: ObjectId,
379 /// gas object to be used in this transaction
380 #[schemars(with = "ObjectIdSchema")]
381 gas: ObjectId,
382 /// the gas budget, the transaction will fail if the gas cost exceed the budget
383 #[schemars(with = "String")]
384 gas_budget: BigInt<u64>,
385 ) -> RpcResult<TransactionBlockBytes>;
386}