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