Skip to content

Commit

Permalink
feat(docs/examples): add self sponsor example
Browse files Browse the repository at this point in the history
  • Loading branch information
miker83z committed Aug 13, 2024
1 parent 89c5de7 commit 7626a54
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/examples/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ path = "stardust/nft-migration.rs"
[[example]]
name = "foundry-output-claim"
path = "stardust/foundry-output-claim.rs"

[[example]]
name = "self-sponsor"
path = "stardust/self-sponsor.rs"
197 changes: 197 additions & 0 deletions docs/examples/rust/stardust/self-sponsor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! Example demonstrating the self-sponsor scenario for claiming a basic output.
//! In order to work, it requires a network with test objects
//! generated from iota-genesis-builder/src/stardust/test_outputs.
use std::{fs, path::PathBuf, str::FromStr};

use anyhow::anyhow;
use bip32::DerivationPath;
use iota_keys::keystore::{AccountKeystore, FileBasedKeystore};
use iota_sdk::{
rpc_types::{IotaObjectDataOptions, IotaTransactionBlockResponseOptions},
types::{
base_types::ObjectID,
crypto::SignatureScheme::ED25519,
gas_coin::GAS,
programmable_transaction_builder::ProgrammableTransactionBuilder,
quorum_driver_types::ExecuteTransactionRequestType,
transaction::{Argument, ObjectArg, Transaction, TransactionData},
IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS,
},
IotaClientBuilder,
};
use move_core_types::ident_str;
use shared_crypto::intent::Intent;

/// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs
const MAIN_ADDRESS_MNEMONIC: &str = "crazy drum raw dirt tooth where fee base warm beach trim rule sign silk fee fee dad large creek venue coin steel hub scale";

/// Creates a temporary keystore
fn setup_keystore() -> Result<FileBasedKeystore, anyhow::Error> {
// Create a temporary keystore
let keystore_path = PathBuf::from("iotatempdb");
if !keystore_path.exists() {
let keystore = FileBasedKeystore::new(&keystore_path)?;
keystore.save()?;
}
// Read iota keystore
FileBasedKeystore::new(&keystore_path)
}

fn clean_keystore() -> Result<(), anyhow::Error> {
// Remove files
fs::remove_file("iotatempdb")?;
fs::remove_file("iotatempdb.aliases")?;
Ok(())
}

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
// Build an iota client for a local network
let iota_client = IotaClientBuilder::default().build_localnet().await?;

// Setup the temporary file based keystore
let mut keystore = setup_keystore()?;

// For this example we need to derive addresses that are not at different
// indexes, one for sponsoring and one for claiming the Basic Output.
let sponsor_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/5'")?;
let sender_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/10'")?;

// Derive the address of the sponsor
let sponsor = keystore.import_from_mnemonic(
MAIN_ADDRESS_MNEMONIC,
ED25519,
Some(sponsor_derivation_path),
)?;
println!("Sponsor address: {sponsor:?}");

// Derive the address of the sender
let sender = keystore.import_from_mnemonic(
MAIN_ADDRESS_MNEMONIC,
ED25519,
Some(sender_derivation_path),
)?;
println!("Sender address: {sender:?}");

// This object id was fetched manually. It refers to a Basic Output object that
// contains some Native Tokens.
let basic_output_object_id = ObjectID::from_hex_literal(
"0xb9c75a53a39e82bafcb454e68b3bd265a6083f32be832632df9ade976b47c37f",
)?;
// Get Basic Output object
let basic_output_object = iota_client
.read_api()
.get_object_with_options(
basic_output_object_id,
IotaObjectDataOptions::new().with_bcs(),
)
.await?
.data
.ok_or(anyhow!("Basic output not found"))?;
let basic_output_object_ref = basic_output_object.object_ref();

// Create a PTB to for claiming the assets of a basic output for the sender
let pt = {
// Init the builder
let mut builder = ProgrammableTransactionBuilder::new();

////// Command #1: extract the base token and native tokens bag.
// Type argument for a Basic Output coming from the IOTA network, i.e., the IOTA
// token or Gas type tag
let type_arguments = vec![GAS::type_tag()];
// Then pass the basic output object as input
let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(basic_output_object_ref))?];
// Finally call the basic_output::extract_assets function
if let Argument::Result(extracted_assets) = builder.programmable_move_call(
STARDUST_ADDRESS.into(),
ident_str!("basic_output").to_owned(),
ident_str!("extract_assets").to_owned(),
type_arguments,
arguments,
) {
// If the basic output can be unlocked, the command will be succesful and will
// return a `base_token` (i.e., IOTA) balance and a `Bag` of native tokens
let extracted_base_token = Argument::NestedResult(extracted_assets, 0);
let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1);

////// Command #2: delete the empty native tokens bag
let arguments = vec![extracted_native_tokens_bag];
builder.programmable_move_call(
IOTA_FRAMEWORK_ADDRESS.into(),
ident_str!("bag").to_owned(),
ident_str!("destroy_empty").to_owned(),
vec![],
arguments,
);

////// Command #3: create a coin from the extracted IOTA balance
// Type argument for the IOTA coin
let type_arguments = vec![GAS::type_tag()];
let arguments = vec![extracted_base_token];
let new_iota_coin = builder.programmable_move_call(
IOTA_FRAMEWORK_ADDRESS.into(),
ident_str!("coin").to_owned(),
ident_str!("from_balance").to_owned(),
type_arguments,
arguments,
);

////// Command #5: send back the base token coin to the user.
builder.transfer_arg(sender, new_iota_coin)
}
builder.finish()
};

// Setup gas budget and gas price
let gas_budget = 50_000_000;
let gas_price = iota_client.read_api().get_reference_gas_price().await?;

// Get a gas coin
let gas_coin = iota_client
.coin_read_api()
.get_coins(sponsor, None, None, None)
.await?
.data
.into_iter()
.next()
.ok_or(anyhow!("No coins found for sponsor"))?;

// Create the transaction data that will be sent to the network and allow
// sponsoring
let tx_data = TransactionData::new_programmable_allow_sponsor(
sender,
vec![gas_coin.object_ref()],
pt,
gas_budget,
gas_price,
sponsor,
);

// Client side, i.e., the sender POV
// Sender signs the transaction
let sender_signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?;

// Server side, i.e., the sponsor POV
// Sponsor signs the transaction
let sponsor_signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?;

// Execute transaction; the transaction data is created using the signature of
// the sender and of the sponsor.
let transaction_response = iota_client
.quorum_driver_api()
.execute_transaction_block(
Transaction::from_data(tx_data, vec![sender_signature, sponsor_signature]),
IotaTransactionBlockResponseOptions::full_content(),
Some(ExecuteTransactionRequestType::WaitForLocalExecution),
)
.await?;

println!("Transaction digest: {}", transaction_response.digest);

// Finish and clean the temporary keystore file
clean_keystore()
}

0 comments on commit 7626a54

Please sign in to comment.