Skip to content

Commit

Permalink
feat(iota-sdk): add stake examples (#2038)
Browse files Browse the repository at this point in the history
* 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
Thoralf-M and thibault-martinez authored Sep 3, 2024
1 parent b180681 commit 1c2d885
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 0 deletions.
107 changes: 107 additions & 0 deletions crates/iota-sdk/examples/stake.rs
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(())
}
183 changes: 183 additions & 0 deletions crates/iota-sdk/examples/timelocked_stake.rs
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(())
}

0 comments on commit 1c2d885

Please sign in to comment.