-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iota-sdk): add stake examples (#2038)
* feat(iota-sdk): add stake example * feat(iota-sdk): add timelocked_stake example * Use short version * Clippy * Address review comments * Apply suggestions from code review Co-authored-by: Thibault Martinez <[email protected]> * Print object changes --------- Co-authored-by: Thibault Martinez <[email protected]>
- Loading branch information
1 parent
b180681
commit 1c2d885
Showing
2 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! This example shows how to stake/withdraw staked coins and get events for the | ||
//! staking address. | ||
//! | ||
//! cargo run --example stake | ||
mod utils; | ||
|
||
use futures::StreamExt; | ||
use iota_json_rpc_types::EventFilter; | ||
use utils::{setup_for_write, sign_and_execute_transaction}; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), anyhow::Error> { | ||
// Get the Iota client, the sender and recipient that we will use | ||
// for the transaction | ||
let (client, sender, _) = setup_for_write().await?; | ||
|
||
// Get the coin we will use for the amount | ||
let coins = client | ||
.coin_read_api() | ||
.get_coins(sender, None, None, None) | ||
.await?; | ||
let coin = coins.data.into_iter().next().unwrap(); | ||
|
||
let gas_budget = 50_000_000; | ||
|
||
// Get a validator | ||
let validator = client | ||
.governance_api() | ||
.get_latest_iota_system_state() | ||
.await? | ||
.active_validators[0] | ||
.iota_address; | ||
|
||
// Build the transaction data, to stake 1 IOTA | ||
let tx_data = client | ||
.transaction_builder() | ||
.request_add_stake( | ||
sender, | ||
vec![coin.coin_object_id], | ||
// Min delegation amount is 1 IOTA | ||
Some(1_000_000_000), | ||
validator, | ||
None, | ||
gas_budget, | ||
) | ||
.await?; | ||
|
||
let transaction_response = sign_and_execute_transaction(&client, &sender, tx_data).await?; | ||
|
||
println!("Transaction sent {}", transaction_response.digest); | ||
println!("Object changes:"); | ||
for object_change in transaction_response.object_changes.unwrap() { | ||
println!("{:?}", object_change); | ||
} | ||
|
||
// Unstake IOTA, if staking for longer than 1 epoch already | ||
|
||
let current_epoch = client | ||
.read_api() | ||
.get_checkpoints(None, Some(1), true) | ||
.await? | ||
.data[0] | ||
.epoch; | ||
let staked_iota = client.governance_api().get_stakes(sender).await?; | ||
|
||
if let Some(staked_iota_id) = staked_iota.into_iter().find_map(|d| { | ||
d.stakes.into_iter().find_map(|s| { | ||
if s.stake_request_epoch < current_epoch { | ||
Some(s.staked_iota_id) | ||
} else { | ||
None | ||
} | ||
}) | ||
}) { | ||
let tx_data = client | ||
.transaction_builder() | ||
.request_withdraw_stake(sender, staked_iota_id, None, gas_budget) | ||
.await?; | ||
|
||
let transaction_response = sign_and_execute_transaction(&client, &sender, tx_data).await?; | ||
|
||
println!("Transaction sent {}", transaction_response.digest); | ||
println!("Object changes:"); | ||
for object_change in transaction_response.object_changes.unwrap() { | ||
println!("{:?}", object_change); | ||
} | ||
} else { | ||
println!("No stake found that can be unlocked (must be staked >= 1 epoch)") | ||
}; | ||
|
||
// Wait some time to let the indexer process the tx, before requesting the | ||
// events | ||
tokio::time::sleep(std::time::Duration::from_secs(3)).await; | ||
|
||
let events = client | ||
.event_api() | ||
.get_events_stream(EventFilter::Sender(sender), None, true) | ||
.collect::<Vec<_>>() | ||
.await; | ||
println!("{events:?}"); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! This example shows how to stake timelocked coins. | ||
//! | ||
//! cargo run --example timelocked_stake | ||
mod utils; | ||
|
||
use iota_json_rpc_api::GovernanceReadApiClient; | ||
use iota_json_rpc_types::{IotaObjectDataFilter, IotaObjectDataOptions, IotaObjectResponseQuery}; | ||
use iota_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore}; | ||
use iota_sdk::{ | ||
rpc_types::IotaTransactionBlockResponseOptions, | ||
types::{quorum_driver_types::ExecuteTransactionRequestType, transaction::Transaction}, | ||
IotaClientBuilder, | ||
}; | ||
use iota_types::crypto::SignatureScheme; | ||
use shared_crypto::intent::Intent; | ||
use utils::request_tokens_from_faucet; | ||
|
||
const MNEMONIC_WITH_TIMELOCKED_IOTA: &str = "mesh dose off wage gas tent key light help girl faint catch sock trouble guard moon talk pill enemy hawk gain mix sad mimic"; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), anyhow::Error> { | ||
let client = IotaClientBuilder::default().build_testnet().await?; | ||
|
||
let mut keystore = Keystore::from( | ||
FileBasedKeystore::new(&"staking-example.keystore".to_string().into()).unwrap(), | ||
); | ||
let address = keystore.import_from_mnemonic( | ||
MNEMONIC_WITH_TIMELOCKED_IOTA, | ||
SignatureScheme::ED25519, | ||
None, | ||
None, | ||
)?; | ||
println!("Sender address: {address:?}"); | ||
|
||
request_tokens_from_faucet(address, &client).await?; | ||
let gas_coin = client | ||
.coin_read_api() | ||
.get_coins(address, None, None, None) | ||
.await? | ||
.data | ||
.into_iter() | ||
.next() | ||
.expect("missing gas coin"); | ||
|
||
let timelocked_objects = client | ||
.read_api() | ||
.get_owned_objects( | ||
address, | ||
Some(IotaObjectResponseQuery::new( | ||
Some(IotaObjectDataFilter::StructType( | ||
"0x2::timelock::TimeLock<0x2::balance::Balance<0x2::iota::IOTA>>".parse()?, | ||
)), | ||
Some( | ||
IotaObjectDataOptions::new() | ||
.with_type() | ||
.with_owner() | ||
.with_previous_transaction(), | ||
), | ||
)), | ||
None, | ||
None, | ||
) | ||
.await?; | ||
|
||
let timelocked_object = timelocked_objects.data[1].object()?.object_id; | ||
println!("Timelocked object: {timelocked_object}"); | ||
|
||
// Delegate some timelocked IOTAs | ||
let validator = client | ||
.governance_api() | ||
.get_latest_iota_system_state() | ||
.await? | ||
.active_validators[0] | ||
.iota_address; | ||
|
||
let tx_data = client | ||
.transaction_builder() | ||
.request_add_timelocked_stake( | ||
address, | ||
timelocked_object, | ||
validator, | ||
gas_coin.coin_object_id, | ||
100_000_000, | ||
) | ||
.await?; | ||
|
||
let signature = keystore.sign_secure(&address, &tx_data, Intent::iota_transaction())?; | ||
|
||
let transaction_response = client | ||
.quorum_driver_api() | ||
.execute_transaction_block( | ||
Transaction::from_data(tx_data, vec![signature]), | ||
IotaTransactionBlockResponseOptions::new().with_object_changes(), | ||
Some(ExecuteTransactionRequestType::WaitForLocalExecution), | ||
) | ||
.await?; | ||
println!("Transaction sent {}", transaction_response.digest); | ||
println!("Object changes:"); | ||
for object_change in transaction_response.object_changes.unwrap() { | ||
println!("{:?}", object_change); | ||
} | ||
|
||
// Wait for indexer to process the tx | ||
tokio::time::sleep(std::time::Duration::from_secs(3)).await; | ||
|
||
// Check DelegatedTimelockedStake object | ||
let staked_iota = client.http().get_timelocked_stakes(address).await?; | ||
println!("Staked: {staked_iota:?}\n\n"); | ||
|
||
// Unstake timelocked IOTA, if staking for longer than 1 epoch already | ||
|
||
let current_epoch = client | ||
.read_api() | ||
.get_checkpoints(None, Some(1), true) | ||
.await? | ||
.data[0] | ||
.epoch; | ||
|
||
if let Some(timelocked_staked_iota_id) = staked_iota.into_iter().find_map(|d| { | ||
d.stakes.into_iter().find_map(|s| { | ||
if s.stake_request_epoch < current_epoch { | ||
Some(s.timelocked_staked_iota_id) | ||
} else { | ||
None | ||
} | ||
}) | ||
}) { | ||
let gas_coin = client | ||
.coin_read_api() | ||
.get_coins(address, None, None, None) | ||
.await? | ||
.data | ||
.into_iter() | ||
.next() | ||
.expect("missing gas coin"); | ||
|
||
let tx_data = client | ||
.transaction_builder() | ||
.request_withdraw_timelocked_stake( | ||
address, | ||
timelocked_staked_iota_id, | ||
gas_coin.coin_object_id, | ||
100_000_000, | ||
) | ||
.await?; | ||
|
||
let signature = keystore.sign_secure(&address, &tx_data, Intent::iota_transaction())?; | ||
|
||
let transaction_response = client | ||
.quorum_driver_api() | ||
.execute_transaction_block( | ||
Transaction::from_data(tx_data, vec![signature]), | ||
IotaTransactionBlockResponseOptions::full_content(), | ||
Some(ExecuteTransactionRequestType::WaitForLocalExecution), | ||
) | ||
.await?; | ||
|
||
println!("Transaction sent {}", transaction_response.digest); | ||
println!("Object changes:"); | ||
for object_change in transaction_response.object_changes.unwrap() { | ||
println!("{:?}", object_change); | ||
} | ||
|
||
// Wait for indexer to process the tx | ||
tokio::time::sleep(std::time::Duration::from_secs(3)).await; | ||
|
||
// Check DelegatedTimelockedStake object | ||
let staked_iota = client.http().get_timelocked_stakes(address).await?; | ||
println!("Staked: {staked_iota:?}"); | ||
} else { | ||
println!("No stake found that can be unlocked (must be staked >= 1 epoch)") | ||
}; | ||
|
||
// Cleanup | ||
std::fs::remove_file("staking-example.aliases")?; | ||
std::fs::remove_file("staking-example.keystore")?; | ||
|
||
Ok(()) | ||
} |