iota_transaction_builder/
stake.rs1use anyhow::{Ok, anyhow, bail, ensure};
6use iota_sdk_types::{Command, Identifier, ObjectId};
7use iota_types::{
8 base_types::{IotaAddress, ObjectType},
9 governance::{ADD_STAKE_MUL_COIN_FUN_NAME, WITHDRAW_STAKE_FUN_NAME},
10 programmable_transaction_builder::ProgrammableTransactionBuilder,
11 timelock::timelocked_staking::{
12 ADD_TIMELOCKED_STAKE_FUN_NAME, WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
13 },
14 transaction::{CallArg, TransactionData, TransactionDataAPI},
15};
16
17use crate::TransactionBuilder;
18
19impl TransactionBuilder {
20 pub async fn request_add_stake(
22 &self,
23 signer: IotaAddress,
24 mut coins: Vec<ObjectId>,
25 amount: impl Into<Option<u64>>,
26 validator: IotaAddress,
27 gas: impl Into<Option<ObjectId>>,
28 gas_budget: u64,
29 ) -> anyhow::Result<TransactionData> {
30 let gas_price = self.0.get_reference_gas_price().await?;
31 let gas = self
32 .select_gas(signer, gas, gas_budget, coins.clone(), gas_price)
33 .await?;
34
35 let mut obj_vec = vec![];
36 let coin = coins
37 .pop()
38 .ok_or_else(|| anyhow!("Coins input should contain at lease one coin object."))?;
39 let (oref, coin_type) = self.get_object_ref_and_type(coin).await?;
40
41 let ObjectType::Struct(type_) = &coin_type else {
42 bail!("Provided object [{coin}] is not a move object.");
43 };
44 ensure!(
45 type_.is_coin(),
46 "Expecting either Coin<T> input coin objects. Received [{type_}]"
47 );
48
49 for coin in coins {
50 let (oref, type_) = self.get_object_ref_and_type(coin).await?;
51 ensure!(
52 type_ == coin_type,
53 "All coins should be the same type, expecting {coin_type}, got {type_}."
54 );
55 obj_vec.push(CallArg::ImmutableOrOwned(oref))
56 }
57 obj_vec.push(CallArg::ImmutableOrOwned(oref));
58
59 let pt = {
60 let mut builder = ProgrammableTransactionBuilder::new();
61 let arguments = vec![
62 builder.input(CallArg::IOTA_SYSTEM_MUTABLE).unwrap(),
63 builder.make_obj_vec(obj_vec)?,
64 builder.pure(amount.into()).unwrap(),
65 builder.pure(validator).unwrap(),
66 ];
67 builder.command(Command::new_move_call(
68 ObjectId::SYSTEM,
69 Identifier::IOTA_SYSTEM_MODULE,
70 ADD_STAKE_MUL_COIN_FUN_NAME,
71 vec![],
72 arguments,
73 ));
74 builder.finish()
75 };
76 Ok(TransactionData::new_programmable(
77 signer,
78 vec![gas],
79 pt,
80 gas_budget,
81 gas_price,
82 ))
83 }
84
85 pub async fn request_withdraw_stake(
87 &self,
88 signer: IotaAddress,
89 staked_iota: ObjectId,
90 gas: impl Into<Option<ObjectId>>,
91 gas_budget: u64,
92 ) -> anyhow::Result<TransactionData> {
93 let staked_iota = self.get_object_ref(staked_iota).await?;
94 let gas_price = self.0.get_reference_gas_price().await?;
95 let gas = self
96 .select_gas(signer, gas, gas_budget, vec![], gas_price)
97 .await?;
98 TransactionData::new_move_call(
99 signer,
100 ObjectId::SYSTEM,
101 Identifier::IOTA_SYSTEM_MODULE,
102 WITHDRAW_STAKE_FUN_NAME,
103 vec![],
104 gas,
105 vec![
106 CallArg::IOTA_SYSTEM_MUTABLE,
107 CallArg::ImmutableOrOwned(staked_iota),
108 ],
109 gas_budget,
110 gas_price,
111 )
112 }
113
114 pub async fn request_add_timelocked_stake(
116 &self,
117 signer: IotaAddress,
118 locked_balance: ObjectId,
119 validator: IotaAddress,
120 gas: ObjectId,
121 gas_budget: u64,
122 ) -> anyhow::Result<TransactionData> {
123 let gas_price = self.0.get_reference_gas_price().await?;
124 let gas = self
125 .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
126 .await?;
127
128 let (oref, locked_balance_type) = self.get_object_ref_and_type(locked_balance).await?;
129
130 let ObjectType::Struct(type_) = &locked_balance_type else {
131 bail!("Provided object [{locked_balance}] is not a move object.");
132 };
133 ensure!(
134 type_.is_timelocked_balance(),
135 "Expecting either TimeLock<Balance<T>> input objects. Received [{type_}]"
136 );
137
138 let pt = {
139 let mut builder = ProgrammableTransactionBuilder::new();
140 let arguments = vec![
141 builder.input(CallArg::IOTA_SYSTEM_MUTABLE)?,
142 builder.input(CallArg::ImmutableOrOwned(oref))?,
143 builder.pure(validator)?,
144 ];
145 builder.command(Command::new_move_call(
146 ObjectId::SYSTEM,
147 Identifier::TIMELOCKED_STAKING_MODULE,
148 ADD_TIMELOCKED_STAKE_FUN_NAME,
149 vec![],
150 arguments,
151 ));
152 builder.finish()
153 };
154 Ok(TransactionData::new_programmable(
155 signer,
156 vec![gas],
157 pt,
158 gas_budget,
159 gas_price,
160 ))
161 }
162
163 pub async fn request_withdraw_timelocked_stake(
165 &self,
166 signer: IotaAddress,
167 timelocked_staked_iota: ObjectId,
168 gas: ObjectId,
169 gas_budget: u64,
170 ) -> anyhow::Result<TransactionData> {
171 let timelocked_staked_iota = self.get_object_ref(timelocked_staked_iota).await?;
172 let gas_price = self.0.get_reference_gas_price().await?;
173 let gas = self
174 .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
175 .await?;
176 TransactionData::new_move_call(
177 signer,
178 ObjectId::SYSTEM,
179 Identifier::TIMELOCKED_STAKING_MODULE,
180 WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
181 vec![],
182 gas,
183 vec![
184 CallArg::IOTA_SYSTEM_MUTABLE,
185 CallArg::ImmutableOrOwned(timelocked_staked_iota),
186 ],
187 gas_budget,
188 gas_price,
189 )
190 }
191}