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, TransactionData, TransactionDataAPI},
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(CallArg::ImmutableOrOwned(oref))
55 }
56 obj_vec.push(CallArg::ImmutableOrOwned(oref));
57
58 let pt = {
59 let mut builder = ProgrammableTransactionBuilder::new();
60 let arguments = vec![
61 builder.input(CallArg::IOTA_SYSTEM_MUTABLE).unwrap(),
62 builder.make_obj_vec(obj_vec)?,
63 builder.pure(amount.into()).unwrap(),
64 builder.pure(validator).unwrap(),
65 ];
66 builder.command(Command::new_move_call(
67 ObjectID::SYSTEM,
68 Identifier::IOTA_SYSTEM_MODULE,
69 ADD_STAKE_MUL_COIN_FUN_NAME,
70 vec![],
71 arguments,
72 ));
73 builder.finish()
74 };
75 Ok(TransactionData::new_programmable(
76 signer,
77 vec![gas],
78 pt,
79 gas_budget,
80 gas_price,
81 ))
82 }
83
84 pub async fn request_withdraw_stake(
86 &self,
87 signer: IotaAddress,
88 staked_iota: ObjectID,
89 gas: impl Into<Option<ObjectID>>,
90 gas_budget: u64,
91 ) -> anyhow::Result<TransactionData> {
92 let staked_iota = self.get_object_ref(staked_iota).await?;
93 let gas_price = self.0.get_reference_gas_price().await?;
94 let gas = self
95 .select_gas(signer, gas, gas_budget, vec![], gas_price)
96 .await?;
97 TransactionData::new_move_call(
98 signer,
99 ObjectID::SYSTEM,
100 Identifier::IOTA_SYSTEM_MODULE,
101 WITHDRAW_STAKE_FUN_NAME,
102 vec![],
103 gas,
104 vec![
105 CallArg::IOTA_SYSTEM_MUTABLE,
106 CallArg::ImmutableOrOwned(staked_iota),
107 ],
108 gas_budget,
109 gas_price,
110 )
111 }
112
113 pub async fn request_add_timelocked_stake(
115 &self,
116 signer: IotaAddress,
117 locked_balance: ObjectID,
118 validator: IotaAddress,
119 gas: ObjectID,
120 gas_budget: u64,
121 ) -> anyhow::Result<TransactionData> {
122 let gas_price = self.0.get_reference_gas_price().await?;
123 let gas = self
124 .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
125 .await?;
126
127 let (oref, locked_balance_type) = self.get_object_ref_and_type(locked_balance).await?;
128
129 let ObjectType::Struct(type_) = &locked_balance_type else {
130 bail!("Provided object [{locked_balance}] is not a move object.");
131 };
132 ensure!(
133 type_.is_timelocked_balance(),
134 "Expecting either TimeLock<Balance<T>> input objects. Received [{type_}]"
135 );
136
137 let pt = {
138 let mut builder = ProgrammableTransactionBuilder::new();
139 let arguments = vec![
140 builder.input(CallArg::IOTA_SYSTEM_MUTABLE)?,
141 builder.input(CallArg::ImmutableOrOwned(oref))?,
142 builder.pure(validator)?,
143 ];
144 builder.command(Command::new_move_call(
145 ObjectID::SYSTEM,
146 Identifier::TIMELOCKED_STAKING_MODULE,
147 ADD_TIMELOCKED_STAKE_FUN_NAME,
148 vec![],
149 arguments,
150 ));
151 builder.finish()
152 };
153 Ok(TransactionData::new_programmable(
154 signer,
155 vec![gas],
156 pt,
157 gas_budget,
158 gas_price,
159 ))
160 }
161
162 pub async fn request_withdraw_timelocked_stake(
164 &self,
165 signer: IotaAddress,
166 timelocked_staked_iota: ObjectID,
167 gas: ObjectID,
168 gas_budget: u64,
169 ) -> anyhow::Result<TransactionData> {
170 let timelocked_staked_iota = self.get_object_ref(timelocked_staked_iota).await?;
171 let gas_price = self.0.get_reference_gas_price().await?;
172 let gas = self
173 .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
174 .await?;
175 TransactionData::new_move_call(
176 signer,
177 ObjectID::SYSTEM,
178 Identifier::TIMELOCKED_STAKING_MODULE,
179 WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
180 vec![],
181 gas,
182 vec![
183 CallArg::IOTA_SYSTEM_MUTABLE,
184 CallArg::ImmutableOrOwned(timelocked_staked_iota),
185 ],
186 gas_budget,
187 gas_price,
188 )
189 }
190}