From e953fba92ab6ee0abb6454601bfd2396c9554abd Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 15:17:06 +0200 Subject: [PATCH 01/39] feat(docs): Create an examples folder for Rust code (#1503) * feat(docs): create an example folder for Rust code * Update docs/examples/rust/Cargo.toml --------- Co-authored-by: Dr-Electron --- Cargo.lock | 12 +++++++ Cargo.toml | 1 + docs/examples/rust/Cargo.toml | 13 ++++++++ docs/examples/rust/examples/base_keystore.rs | 35 ++++++++++++++++++++ docs/examples/rust/src/lib.rs | 1 + 5 files changed, 62 insertions(+) create mode 100644 docs/examples/rust/Cargo.toml create mode 100644 docs/examples/rust/examples/base_keystore.rs create mode 100644 docs/examples/rust/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8b1ae477ccb..460eb4d9e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3457,6 +3457,18 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docs-examples" +version = "1.22.0" +dependencies = [ + "anyhow", + "iota-keys", + "iota-sdk 1.22.0", + "move-core-types", + "shared-crypto", + "tokio", +] + [[package]] name = "dotenvy" version = "0.15.7" diff --git a/Cargo.toml b/Cargo.toml index 02b4a51f2d9..8070a88eb02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,7 @@ members = [ "crates/typed-store", "crates/typed-store-derive", "crates/typed-store-error", + "docs/examples/rust", "iota-execution", "iota-execution/cut", "iota-execution/latest/iota-adapter", diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml new file mode 100644 index 00000000000..fc9e2f16039 --- /dev/null +++ b/docs/examples/rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "docs-examples" +edition = "2021" +version.workspace = true + +[dependencies] +anyhow.workspace = true +tokio.workspace = true + +iota-keys.workspace = true +iota-sdk.workspace = true +move-core-types.workspace = true +shared-crypto.workspace = true diff --git a/docs/examples/rust/examples/base_keystore.rs b/docs/examples/rust/examples/base_keystore.rs new file mode 100644 index 00000000000..99c34f6090a --- /dev/null +++ b/docs/examples/rust/examples/base_keystore.rs @@ -0,0 +1,35 @@ +use std::{fs, path::PathBuf}; + +use iota_keys::keystore::FileBasedKeystore; +use iota_sdk::IotaClientBuilder; + +/// Creates a temporary keystore +fn setup_keystore() -> Result { + // Create a temporary keystore + let keystore_path = PathBuf::from("iotatempdb"); + if !keystore_path.exists() { + let keystore = FileBasedKeystore::new(&keystore_path)?; + keystore.save()?; + } + // Read the iota keystore + Ok(FileBasedKeystore::new(&keystore_path)?) +} + +fn clean_keystore() -> Result<(), anyhow::Error> { + // Remove the keystore 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 a temporary file based keystore + let _keystore = setup_keystore()?; + + // Finish and clean the temporary keystore file + clean_keystore() +} diff --git a/docs/examples/rust/src/lib.rs b/docs/examples/rust/src/lib.rs new file mode 100644 index 00000000000..7837b42ecf8 --- /dev/null +++ b/docs/examples/rust/src/lib.rs @@ -0,0 +1 @@ +// Empty lib From f7f0c5d3f9b5c77c8ecbd2f50999d9dd1c653b71 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 31 Jul 2024 09:33:31 +0200 Subject: [PATCH 02/39] fix(docs/examples): remove base_keystore example --- Cargo.lock | 4 +-- docs/examples/rust/examples/base_keystore.rs | 35 -------------------- 2 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 docs/examples/rust/examples/base_keystore.rs diff --git a/Cargo.lock b/Cargo.lock index 460eb4d9e96..fe57834ccce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3459,11 +3459,11 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docs-examples" -version = "1.22.0" +version = "0.1.1" dependencies = [ "anyhow", "iota-keys", - "iota-sdk 1.22.0", + "iota-sdk 0.1.1", "move-core-types", "shared-crypto", "tokio", diff --git a/docs/examples/rust/examples/base_keystore.rs b/docs/examples/rust/examples/base_keystore.rs deleted file mode 100644 index 99c34f6090a..00000000000 --- a/docs/examples/rust/examples/base_keystore.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::{fs, path::PathBuf}; - -use iota_keys::keystore::FileBasedKeystore; -use iota_sdk::IotaClientBuilder; - -/// Creates a temporary keystore -fn setup_keystore() -> Result { - // Create a temporary keystore - let keystore_path = PathBuf::from("iotatempdb"); - if !keystore_path.exists() { - let keystore = FileBasedKeystore::new(&keystore_path)?; - keystore.save()?; - } - // Read the iota keystore - Ok(FileBasedKeystore::new(&keystore_path)?) -} - -fn clean_keystore() -> Result<(), anyhow::Error> { - // Remove the keystore 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 a temporary file based keystore - let _keystore = setup_keystore()?; - - // Finish and clean the temporary keystore file - clean_keystore() -} From d2761c50a5a413a22ec61b0f9a3a25db0f299254 Mon Sep 17 00:00:00 2001 From: Valerii Reutov Date: Wed, 31 Jul 2024 16:11:55 +0300 Subject: [PATCH 03/39] feat(iota-sdk): added an alias output claim example (#1493) * feat(iota-sdk): added an alias output claim example * feat(docs): moved the alias output claim example * refactor(docs): used the extract_and_send_to function to simplify the example * fix(docs): added a license header and an example description * fix(docs): alias-output-claim comments * fix(docs): clippy * fix(docs): move the alias-output-claim example into the stardust subfolder * fix(docs): fixed the example after rebasing * fix(docs): move the alias-output-claim example * fix(docs): alias-output-claim logs improved --- Cargo.lock | 1 + docs/examples/rust/Cargo.toml | 7 +- .../rust/stardust/alias-output-claim.rs | 230 ++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 docs/examples/rust/stardust/alias-output-claim.rs diff --git a/Cargo.lock b/Cargo.lock index fe57834ccce..bd6e0df4f76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3462,6 +3462,7 @@ name = "docs-examples" version = "0.1.1" dependencies = [ "anyhow", + "bcs", "iota-keys", "iota-sdk 0.1.1", "move-core-types", diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index fc9e2f16039..b130046ec92 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -1,13 +1,18 @@ [package] name = "docs-examples" -edition = "2021" version.workspace = true +edition = "2021" [dependencies] anyhow.workspace = true +bcs.workspace = true tokio.workspace = true iota-keys.workspace = true iota-sdk.workspace = true move-core-types.workspace = true shared-crypto.workspace = true + +[[example]] +name = "alias-output-claim" +path = "stardust/alias-output-claim.rs" diff --git a/docs/examples/rust/stardust/alias-output-claim.rs b/docs/examples/rust/stardust/alias-output-claim.rs new file mode 100644 index 00000000000..f2a710e797e --- /dev/null +++ b/docs/examples/rust/stardust/alias-output-claim.rs @@ -0,0 +1,230 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the claim of an alias 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 iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_sdk::{ + rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, + types::{ + base_types::ObjectID, + crypto::SignatureScheme::ED25519, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + stardust::output::AliasOutput, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + TypeTag, 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +/// Creates a temporary keystore. +fn setup_keystore() -> Result { + // Create a temporary keystore. + let keystore_path = PathBuf::from("iotatempdb"); + if !keystore_path.exists() { + let keystore = FileBasedKeystore::new(&keystore_path)?; + keystore.save()?; + } + // Read the iota keystore. + FileBasedKeystore::new(&keystore_path) +} + +fn clean_keystore() -> Result<(), anyhow::Error> { + // Remove the keystore 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 a temporary file based keystore. + let mut keystore = setup_keystore()?; + + // Derive the address of the first account and set it as default. + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("Sender address: {sender:?}"); + + // Get a gas coin. + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found for sponsor"))?; + + // Get an AliasOutput object. + let alias_output_object_id = ObjectID::from_hex_literal( + "0x354a1864c8af23fde393f7603bc133f755a9405353b30878e41b929eb7e37554", + )?; + let alias_output_object = iota_client + .read_api() + .get_object_with_options( + alias_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("alias not found"))?; + let alias_output_object_ref = alias_output_object.object_ref(); + + // Convert the AliasOutput object into its Rust representation. + let alias_output = bcs::from_bytes::( + &alias_output_object + .bcs + .expect("should contain bcs") + .try_as_move() + .expect("should convert it to a move object") + .bcs_bytes, + )?; + + // Extract the keys of the native_tokens bag if it is not empty; the keys + // are the type_arg of each native token, so they can be used later in the PTB. + let mut df_type_keys = vec![]; + let native_token_bag = alias_output.native_tokens; + if native_token_bag.size > 0 { + // Get the dynamic fields owned by the native tokens bag. + let dynamic_field_page = iota_client + .read_api() + .get_dynamic_fields(*native_token_bag.id.object_id(), None, None) + .await?; + // Only one page should exist. + assert!(!dynamic_field_page.has_next_page); + + // Extract the dynamic fields keys, i.e., the native token type. + df_type_keys.extend( + dynamic_field_page + .data + .into_iter() + .map(|dyi| { + dyi.name + .value + .as_str() + .expect("should be a string") + .to_string() + }) + .collect::>(), + ); + } + + // Create a PTB to claim the assets related to the alias output. + let pt = { + // Init a programmable transaction builder. + let mut builder = ProgrammableTransactionBuilder::new(); + + // Type argument for an AliasOutput coming from the IOTA network, i.e., the + // IOTA token or the Gas type tag. + let type_arguments = vec![GAS::type_tag()]; + // Then pass the AliasOutput object as an input. + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?]; + // Finally call the alias_output::extract_assets function. + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("alias_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // The alias output can always be unlocked by the governor address. So the + // command will be successful and will return a `base_token` (i.e., IOTA) + // balance, a `Bag` of the related native tokens and the related Alias object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let mut extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let extracted_alias = Argument::NestedResult(extracted_assets, 2); + + // Extract the IOTA balance. + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![extracted_base_token]; + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer the IOTA balance to the sender. + builder.transfer_arg(sender, iota_coin); + + // Extract the native tokens from the bag. + for type_key in df_type_keys { + let type_arguments = vec![TypeTag::from_str(&format!("0x{type_key}"))?]; + let arguments = vec![extracted_native_tokens_bag, builder.pure(sender)?]; + + // Extract a native token balance. + extracted_native_tokens_bag = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("utilities").to_owned(), + ident_str!("extract_and_send_to").to_owned(), + type_arguments, + arguments, + ); + } + + // Cleanup the 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, + ); + + // Transfer the alias asset. + builder.transfer_arg(sender, extracted_alias); + } + builder.finish() + }; + + // Setup a gas budget and a gas price. + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create a transaction data that will be sent to the network. + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction. + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute the transaction. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file. + clean_keystore() +} From 050678c59f2fe9ede9dc4eecb948fdddb5cf1a19 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 25 Jul 2024 18:33:38 +0200 Subject: [PATCH 04/39] feat(iota-sdk): add basic output claim example --- crates/iota-sdk/Cargo.toml | 4 + .../examples/stardust/basic-output-claim.rs | 218 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 crates/iota-sdk/examples/stardust/basic-output-claim.rs diff --git a/crates/iota-sdk/Cargo.toml b/crates/iota-sdk/Cargo.toml index e5cef6c5e97..fe1ab760bb7 100644 --- a/crates/iota-sdk/Cargo.toml +++ b/crates/iota-sdk/Cargo.toml @@ -85,3 +85,7 @@ path = "examples/sign_tx_guide.rs" name = "utils" path = "examples/utils.rs" crate-type = ["staticlib"] + +[[example]] +name = "basic-output-claim" +path = "examples/stardust/basic-output-claim.rs" \ No newline at end of file diff --git a/crates/iota-sdk/examples/stardust/basic-output-claim.rs b/crates/iota-sdk/examples/stardust/basic-output-claim.rs new file mode 100644 index 00000000000..1d2409df099 --- /dev/null +++ b/crates/iota-sdk/examples/stardust/basic-output-claim.rs @@ -0,0 +1,218 @@ +use std::{fs, path::PathBuf, str::FromStr}; + +use anyhow::anyhow; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_sdk::{ + rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, + types::{ + base_types::ObjectID, + crypto::SignatureScheme::ED25519, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + stardust::output::BasicOutput, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + TypeTag, 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +/// Creates a temporary keystore +fn setup_keystore() -> Result { + // 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 + Ok(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 a 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()?; + + // Derive the address of the first account and set it as default + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("{:?}", sender); + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found for sponsor"))?; + + // Get Basic Output object + let basic_output_object_id = ObjectID::from_hex_literal( + "0xe0624f0a78a02dd8070ffe74c6b7fcaefe7514fdcbf4deb962419fb3b14b23dc", + )?; + let basic_output_object = iota_client + .read_api() + .get_object_with_options( + basic_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found for sponsor"))?; + let basic_output_object_ref = basic_output_object.object_ref(); + + // Convert the basic output object into its Rust representation + let basic_output = bcs::from_bytes::( + &basic_output_object + .bcs + .expect("should contain bcs") + .try_as_move() + .expect("should convert it to a move object") + .bcs_bytes, + )?; + + // Extract the keys of the native_tokens bag if this is not empty; here the keys + // are the type_arg of each native token, sothey can be used later in the PTB. + let mut df_type_keys = vec![]; + let native_token_bag = basic_output.native_tokens; + if native_token_bag.size > 0 { + // Get the dynamic fieldss of the native tokens bag + let dynamic_field_page = iota_client + .read_api() + .get_dynamic_fields(native_token_bag.id.object_id().clone(), None, None) + .await?; + // should have only one page + assert!(!dynamic_field_page.has_next_page); + + // Extract the dynamic fields keys, i.e., the native token type + df_type_keys.extend( + dynamic_field_page + .data + .into_iter() + .map(|dyi| { + dyi.name + .value + .as_str() + .expect("should be a string") + .to_string() + }) + .collect::>(), + ); + } + + // Create a PTB to for claiming the assets of a basic output + 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 mut extracted_native_tokens_bag = Box::new(Argument::NestedResult(extracted_assets, 1)); + + ////// Command #2: extract the netive tokens from the Bag and send them to sender. + for type_key in df_type_keys { + // Type argument for a Native Token contained in the basic output bag + let type_arguments = vec![TypeTag::from_str(&type_key)?]; + // Then pass the the bag and the receiver address as input + let arguments = vec![*extracted_native_tokens_bag, builder.pure(sender)?]; + *extracted_native_tokens_bag = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("utilities").to_owned(), + ident_str!("extract_and_send_to").to_owned(), + type_arguments, + arguments, + ); + } + + ////// Command #3: delete the 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 #4: create a coin from the extracted IOTA balance + 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(), + vec![], + 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?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + println!("Transaction digest: {:?}", transaction_response); + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} From 64f81c8e1dac8e7fe01f7d900715367491416322 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Fri, 26 Jul 2024 16:36:40 +0200 Subject: [PATCH 05/39] fix(iota-sdk): use the proper native token bag key --- .../examples/stardust/basic-output-claim.rs | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/crates/iota-sdk/examples/stardust/basic-output-claim.rs b/crates/iota-sdk/examples/stardust/basic-output-claim.rs index 1d2409df099..c8f2f65dece 100644 --- a/crates/iota-sdk/examples/stardust/basic-output-claim.rs +++ b/crates/iota-sdk/examples/stardust/basic-output-claim.rs @@ -142,15 +142,15 @@ async fn main() -> Result<(), anyhow::Error> { // 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 mut extracted_native_tokens_bag = Box::new(Argument::NestedResult(extracted_assets, 1)); + let mut extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); ////// Command #2: extract the netive tokens from the Bag and send them to sender. for type_key in df_type_keys { // Type argument for a Native Token contained in the basic output bag - let type_arguments = vec![TypeTag::from_str(&type_key)?]; + let type_arguments = vec![TypeTag::from_str(&format!("0x{type_key}"))?]; // Then pass the the bag and the receiver address as input - let arguments = vec![*extracted_native_tokens_bag, builder.pure(sender)?]; - *extracted_native_tokens_bag = builder.programmable_move_call( + let arguments = vec![extracted_native_tokens_bag, builder.pure(sender)?]; + extracted_native_tokens_bag = builder.programmable_move_call( STARDUST_ADDRESS.into(), ident_str!("utilities").to_owned(), ident_str!("extract_and_send_to").to_owned(), @@ -160,27 +160,29 @@ async fn main() -> Result<(), anyhow::Error> { } ////// Command #3: delete the 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 #4: create a coin from the extracted IOTA balance - 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(), - vec![], - arguments, - ); - - ////// Command #5: send back the base token coin to the user. - builder.transfer_arg(sender, new_iota_coin) + 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 #4: 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() }; From 0c9a2b3dbac30def1bcb5e46a3d2b6a85b9f1771 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 15:30:38 +0200 Subject: [PATCH 06/39] fix(iota-sdk): move basic output example in docs --- crates/iota-sdk/Cargo.toml | 6 +----- docs/examples/rust/Cargo.toml | 5 +++++ .../examples/rust}/examples/stardust/basic-output-claim.rs | 0 3 files changed, 6 insertions(+), 5 deletions(-) rename {crates/iota-sdk => docs/examples/rust}/examples/stardust/basic-output-claim.rs (100%) diff --git a/crates/iota-sdk/Cargo.toml b/crates/iota-sdk/Cargo.toml index fe1ab760bb7..664e60c1cfd 100644 --- a/crates/iota-sdk/Cargo.toml +++ b/crates/iota-sdk/Cargo.toml @@ -84,8 +84,4 @@ path = "examples/sign_tx_guide.rs" [[example]] name = "utils" path = "examples/utils.rs" -crate-type = ["staticlib"] - -[[example]] -name = "basic-output-claim" -path = "examples/stardust/basic-output-claim.rs" \ No newline at end of file +crate-type = ["staticlib"] \ No newline at end of file diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index b130046ec92..6e9ce1a3c9c 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -8,6 +8,7 @@ anyhow.workspace = true bcs.workspace = true tokio.workspace = true +bcs.workspace = true iota-keys.workspace = true iota-sdk.workspace = true move-core-types.workspace = true @@ -16,3 +17,7 @@ shared-crypto.workspace = true [[example]] name = "alias-output-claim" path = "stardust/alias-output-claim.rs" + +[[example]] +name = "basic-output-claim" +path = "stardust/basic-output-claim.rs" \ No newline at end of file diff --git a/crates/iota-sdk/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs similarity index 100% rename from crates/iota-sdk/examples/stardust/basic-output-claim.rs rename to docs/examples/rust/examples/stardust/basic-output-claim.rs From a59d43c8271fad7ed774cbbfa04800f967b454e9 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 15:41:35 +0200 Subject: [PATCH 07/39] fix(docs/examples): comments --- .../rust/examples/stardust/basic-output-claim.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs index c8f2f65dece..97771715e12 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/examples/stardust/basic-output-claim.rs @@ -43,7 +43,7 @@ fn clean_keystore() -> Result<(), anyhow::Error> { #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - // Build a iota client for a local network + // Build an iota client for a local network let iota_client = IotaClientBuilder::default().build_localnet().await?; // Setup the temporary file based keystore @@ -52,8 +52,6 @@ async fn main() -> Result<(), anyhow::Error> { // Derive the address of the first account and set it as default let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; - println!("{:?}", sender); - // Get a gas coin let gas_coin = iota_client .coin_read_api() @@ -92,11 +90,11 @@ async fn main() -> Result<(), anyhow::Error> { )?; // Extract the keys of the native_tokens bag if this is not empty; here the keys - // are the type_arg of each native token, sothey can be used later in the PTB. + // are the type_arg of each native token, so they can be used later in the PTB. let mut df_type_keys = vec![]; let native_token_bag = basic_output.native_tokens; if native_token_bag.size > 0 { - // Get the dynamic fieldss of the native tokens bag + // Get the dynamic fields owned by the native tokens bag let dynamic_field_page = iota_client .read_api() .get_dynamic_fields(native_token_bag.id.object_id().clone(), None, None) @@ -114,7 +112,7 @@ async fn main() -> Result<(), anyhow::Error> { .value .as_str() .expect("should be a string") - .to_string() + .to_owned() }) .collect::>(), ); @@ -173,7 +171,7 @@ async fn main() -> Result<(), anyhow::Error> { // 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( + let new_iota_coin = builder.programmable_move_call( IOTA_FRAMEWORK_ADDRESS.into(), ident_str!("coin").to_owned(), ident_str!("from_balance").to_owned(), @@ -212,7 +210,7 @@ async fn main() -> Result<(), anyhow::Error> { Some(ExecuteTransactionRequestType::WaitForLocalExecution), ) .await?; - println!("Transaction digest: {:?}", transaction_response); + println!("Transaction digest: {}", transaction_response.digest); // Finish and clean the temporary keystore file From 9e8ac44776afe933c70deb349b9c0dab7d12db2e Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 15:44:50 +0200 Subject: [PATCH 08/39] fix(docs/examples): license header --- docs/examples/rust/examples/stardust/basic-output-claim.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs index 97771715e12..16416a10d7d 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/examples/stardust/basic-output-claim.rs @@ -1,3 +1,8 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the claim of a basic output. + use std::{fs, path::PathBuf, str::FromStr}; use anyhow::anyhow; From 3e7216c46f3587fbf3b81b5adea71e5b6a2e6ce3 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 16:27:12 +0200 Subject: [PATCH 09/39] fix(docs/examples): basic output address --- crates/iota-sdk/Cargo.toml | 2 +- .../rust/examples/stardust/basic-output-claim.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/iota-sdk/Cargo.toml b/crates/iota-sdk/Cargo.toml index 664e60c1cfd..e5cef6c5e97 100644 --- a/crates/iota-sdk/Cargo.toml +++ b/crates/iota-sdk/Cargo.toml @@ -84,4 +84,4 @@ path = "examples/sign_tx_guide.rs" [[example]] name = "utils" path = "examples/utils.rs" -crate-type = ["staticlib"] \ No newline at end of file +crate-type = ["staticlib"] diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs index 16416a10d7d..17565338be7 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/examples/stardust/basic-output-claim.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 //! Example demonstrating the claim of 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}; @@ -25,7 +27,7 @@ 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; +const MAIN_ADDRESS_MNEMONIC: &str = "rain flip mad lamp owner siren tower buddy wolf shy tray exit glad come dry tent they pond wrist web cliff mixed seek drum"; /// Creates a temporary keystore fn setup_keystore() -> Result { @@ -65,11 +67,11 @@ async fn main() -> Result<(), anyhow::Error> { .data .into_iter() .next() - .ok_or(anyhow!("No coins found for sponsor"))?; + .ok_or(anyhow!("No coins found for sender"))?; // Get Basic Output object let basic_output_object_id = ObjectID::from_hex_literal( - "0xe0624f0a78a02dd8070ffe74c6b7fcaefe7514fdcbf4deb962419fb3b14b23dc", + "0xde09139ed46b9f5f876671e4403f312fad867c5ae5d300a252e4b6a6f1fa1fbd", )?; let basic_output_object = iota_client .read_api() @@ -102,7 +104,7 @@ async fn main() -> Result<(), anyhow::Error> { // Get the dynamic fields owned by the native tokens bag let dynamic_field_page = iota_client .read_api() - .get_dynamic_fields(native_token_bag.id.object_id().clone(), None, None) + .get_dynamic_fields(*native_token_bag.id.object_id(), None, None) .await?; // should have only one page assert!(!dynamic_field_page.has_next_page); From fe2f4e4d5153d524cd52e0bd2ba43ca71201182b Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 16:33:46 +0200 Subject: [PATCH 10/39] fix(docs/examples): fix clippy --- docs/examples/rust/examples/stardust/basic-output-claim.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs index 17565338be7..7ff905f5407 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/examples/stardust/basic-output-claim.rs @@ -38,7 +38,7 @@ fn setup_keystore() -> Result { keystore.save()?; } // Read iota keystore - Ok(FileBasedKeystore::new(&keystore_path)?) + FileBasedKeystore::new(&keystore_path) } fn clean_keystore() -> Result<(), anyhow::Error> { From 5dc2e3b043651fa9806f4740f785de46cde425fa Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 30 Jul 2024 17:19:18 +0200 Subject: [PATCH 11/39] fix(docs/examples): fix from comments --- docs/examples/rust/examples/stardust/basic-output-claim.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/examples/stardust/basic-output-claim.rs index 7ff905f5407..d768b09cec3 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/examples/stardust/basic-output-claim.rs @@ -81,9 +81,7 @@ async fn main() -> Result<(), anyhow::Error> { ) .await? .data - .into_iter() - .next() - .ok_or(anyhow!("No coins found for sponsor"))?; + .ok_or(anyhow!("Basic output not found"))?; let basic_output_object_ref = basic_output_object.object_ref(); // Convert the basic output object into its Rust representation From 09e09b28e36220030f058ae976a2c6bb3ec18847 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 31 Jul 2024 16:08:16 +0200 Subject: [PATCH 12/39] fix(docs/examples): move basic output example --- docs/examples/rust/Cargo.toml | 3 +-- .../rust/{examples => }/stardust/basic-output-claim.rs | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) rename docs/examples/rust/{examples => }/stardust/basic-output-claim.rs (99%) diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 6e9ce1a3c9c..ea16acbb9ac 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] anyhow.workspace = true -bcs.workspace = true tokio.workspace = true bcs.workspace = true @@ -20,4 +19,4 @@ path = "stardust/alias-output-claim.rs" [[example]] name = "basic-output-claim" -path = "stardust/basic-output-claim.rs" \ No newline at end of file +path = "stardust/basic-output-claim.rs" diff --git a/docs/examples/rust/examples/stardust/basic-output-claim.rs b/docs/examples/rust/stardust/basic-output-claim.rs similarity index 99% rename from docs/examples/rust/examples/stardust/basic-output-claim.rs rename to docs/examples/rust/stardust/basic-output-claim.rs index d768b09cec3..83b47a6499a 100644 --- a/docs/examples/rust/examples/stardust/basic-output-claim.rs +++ b/docs/examples/rust/stardust/basic-output-claim.rs @@ -59,6 +59,8 @@ async fn main() -> Result<(), anyhow::Error> { // Derive the address of the first account and set it as default let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + println!("Sender address: {sender:?}"); + // Get a gas coin let gas_coin = iota_client .coin_read_api() From ad781ef36cfac7240ac94ed92818ef4a01ff5c7c Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 31 Jul 2024 16:26:35 +0200 Subject: [PATCH 13/39] fix(docs/examples): remove src folder --- docs/examples/rust/Cargo.toml | 2 +- docs/examples/rust/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 docs/examples/rust/src/lib.rs diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index b130046ec92..0c9d11d6bbb 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -5,9 +5,9 @@ edition = "2021" [dependencies] anyhow.workspace = true -bcs.workspace = true tokio.workspace = true +bcs.workspace = true iota-keys.workspace = true iota-sdk.workspace = true move-core-types.workspace = true diff --git a/docs/examples/rust/src/lib.rs b/docs/examples/rust/src/lib.rs deleted file mode 100644 index 7837b42ecf8..00000000000 --- a/docs/examples/rust/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -// Empty lib From 31b1421a3df1bc869d48954938bb5f01a4c6d498 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Wed, 31 Jul 2024 17:59:42 +0300 Subject: [PATCH 14/39] feat(docs/examples): nft output extraction example based on sdk (#1478) * feat(docs): add nft output claiming test example based on sdk usage --- docs/examples/rust/Cargo.toml | 4 + .../rust/stardust/nft-output-claim.rs | 223 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 docs/examples/rust/stardust/nft-output-claim.rs diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 0c9d11d6bbb..715672d8e70 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -13,6 +13,10 @@ iota-sdk.workspace = true move-core-types.workspace = true shared-crypto.workspace = true +[[example]] +name = "nft-output-claim" +path = "stardust/nft-output-claim.rs" + [[example]] name = "alias-output-claim" path = "stardust/alias-output-claim.rs" diff --git a/docs/examples/rust/stardust/nft-output-claim.rs b/docs/examples/rust/stardust/nft-output-claim.rs new file mode 100644 index 00000000000..78e66a31123 --- /dev/null +++ b/docs/examples/rust/stardust/nft-output-claim.rs @@ -0,0 +1,223 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the claim of an NFT 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 iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_sdk::{ + rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, + types::{ + base_types::ObjectID, + crypto::SignatureScheme::ED25519, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + stardust::output::NftOutput, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + TypeTag, 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +#[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()?; + + // Derive the address of the first account and set it as default + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("{sender:?}"); + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Get an NftOutput object + let nft_output_object_id = ObjectID::from_hex_literal( + "0xad87a60921c62f84d57301ea127d1706b406cde5ec6fa4d3af2a80f424fab93a", + )?; + + let nft_output_object = iota_client + .read_api() + .get_object_with_options( + nft_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .ok_or(anyhow!("Nft not found"))?; + + let nft_output_object_ref = nft_output_object.object_ref(); + + let nft_output = bcs::from_bytes::( + &nft_output_object + .bcs + .expect("should contain bcs") + .try_as_move() + .expect("should convert it to a move object") + .bcs_bytes, + )?; + + let mut df_type_keys = vec![]; + let native_token_bag = nft_output.native_tokens; + if native_token_bag.size > 0 { + // Get the dynamic fieldss of the native tokens bag + let dynamic_field_page = iota_client + .read_api() + .get_dynamic_fields(*native_token_bag.id.object_id(), None, None) + .await?; + // should have only one page + assert!(!dynamic_field_page.has_next_page); + + // Extract the dynamic fields keys, i.e., the native token type + df_type_keys.extend( + dynamic_field_page + .data + .into_iter() + .map(|dyi| { + dyi.name + .value + .as_str() + .expect("should be a string") + .to_string() + }) + .collect::>(), + ); + } + + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + + // Extract nft assets(base token, native tokens bag, nft asset itself). + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(nft_output_object_ref))?]; + // Finally call the nft_output::extract_assets function + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("nft_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // If the nft 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 and + // related nft object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let mut extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let nft_asset = Argument::NestedResult(extracted_assets, 2); + + // Extract IOTA balance + let arguments = vec![extracted_base_token]; + let type_arguments = vec![GAS::type_tag()]; + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer IOTA balance + builder.transfer_arg(sender, iota_coin); + + for type_key in df_type_keys { + let type_arguments = vec![TypeTag::from_str(&format!("0x{type_key}"))?]; + // Then pass the the bag and the receiver address as input + let arguments = vec![extracted_native_tokens_bag, builder.pure(sender)?]; + + // Extract native tokens from the bag. + // Extract native token balance + // Transfer native token balance + extracted_native_tokens_bag = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("utilities").to_owned(), + ident_str!("extract_and_send_to").to_owned(), + type_arguments, + arguments, + ); + } + + // Transferring nft asset + builder.transfer_arg(sender, nft_asset); + + // Cleanup 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, + ); + } + builder.finish() + }; + + // Setup gas budget and gas price + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} + +fn setup_keystore() -> Result { + // 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(()) +} From 84ad95e60812acef2983a1a9cd950f18eb5e3570 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 1 Aug 2024 09:47:21 +0200 Subject: [PATCH 15/39] feat(ci): add docs examples in rust (#1539) --- .github/actions/diffs/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/diffs/action.yml b/.github/actions/diffs/action.yml index ed03daeb86e..eda1aced422 100644 --- a/.github/actions/diffs/action.yml +++ b/.github/actions/diffs/action.yml @@ -32,6 +32,7 @@ runs: - "external-crates/**" - "narwhal/**" - "iota-execution/**" + - "docs/examples/rust/**" - ".github/workflows/codecov.yml" - ".github/workflows/rust.yml" - ".github/workflows/external.yml" From a6c62b279f9a9e8adb4a39d46641e01fb41e64cf Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 1 Aug 2024 10:00:02 +0200 Subject: [PATCH 16/39] fix(docs/examples): add license --- docs/examples/rust/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 715672d8e70..b1365f60dae 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "docs-examples" version.workspace = true +authors = ["IOTA Stiftung"] edition = "2021" +license = "Apache-2.0" +publish = false [dependencies] anyhow.workspace = true From 73361576c4063ef8e751da200fc96ab0656d269f Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 1 Aug 2024 10:46:05 +0200 Subject: [PATCH 17/39] fix(docs/examples): cargo lock --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06726b2cf86..bdf8903b691 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3491,12 +3491,12 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "docs-examples" -version = "0.1.1" +version = "0.1.2" dependencies = [ "anyhow", "bcs", "iota-keys", - "iota-sdk 0.1.1", + "iota-sdk 0.1.2", "move-core-types", "shared-crypto", "tokio", From 5e3f99b6ab0bbe2b71543e77172e8b58c9ad0b7f Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 1 Aug 2024 13:04:41 +0200 Subject: [PATCH 18/39] chore(docs/examples): add clarification comment --- docs/examples/rust/stardust/basic-output-claim.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/examples/rust/stardust/basic-output-claim.rs b/docs/examples/rust/stardust/basic-output-claim.rs index 83b47a6499a..38dd8766965 100644 --- a/docs/examples/rust/stardust/basic-output-claim.rs +++ b/docs/examples/rust/stardust/basic-output-claim.rs @@ -71,10 +71,12 @@ async fn main() -> Result<(), anyhow::Error> { .next() .ok_or(anyhow!("No coins found for sender"))?; - // Get Basic Output object + // 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( "0xde09139ed46b9f5f876671e4403f312fad867c5ae5d300a252e4b6a6f1fa1fbd", )?; + // Get Basic Output object let basic_output_object = iota_client .read_api() .get_object_with_options( From ac12a57ba1953e8bcd1a8f0962529b5e0106e312 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Mon, 5 Aug 2024 16:58:14 +0300 Subject: [PATCH 19/39] feat(docs/examples): add unlock condition example based on rust sdk (#1559) * feat(docs): add unlock condition example based on rust sdk Add fund_address function for sponsoring the main transaction --- Cargo.lock | 2 + docs/examples/rust/Cargo.toml | 10 +- .../rust/stardust/address_unlock_condition.rs | 353 ++++++++++++++++++ 3 files changed, 363 insertions(+), 2 deletions(-) create mode 100644 docs/examples/rust/stardust/address_unlock_condition.rs diff --git a/Cargo.lock b/Cargo.lock index bdf8903b691..2bb9f6561bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3495,9 +3495,11 @@ version = "0.1.2" dependencies = [ "anyhow", "bcs", + "bip32", "iota-keys", "iota-sdk 0.1.2", "move-core-types", + "serde_json", "shared-crypto", "tokio", ] diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 96bb93ff2a7..f7eaebdb5df 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -6,16 +6,22 @@ edition = "2021" license = "Apache-2.0" publish = false -[dependencies] +[dev-dependencies] anyhow.workspace = true +bcs.workspace = true +bip32.workspace = true tokio.workspace = true -bcs.workspace = true iota-keys.workspace = true iota-sdk.workspace = true move-core-types.workspace = true +serde_json.workspace = true shared-crypto.workspace = true +[[example]] +name = "address_unlock_condition" +path = "stardust/address_unlock_condition.rs" + [[example]] name = "nft-output-claim" path = "stardust/nft-output-claim.rs" diff --git a/docs/examples/rust/stardust/address_unlock_condition.rs b/docs/examples/rust/stardust/address_unlock_condition.rs new file mode 100644 index 00000000000..914ecf057a5 --- /dev/null +++ b/docs/examples/rust/stardust/address_unlock_condition.rs @@ -0,0 +1,353 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating how to unlock an output owned by an alias 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::{ + IotaObjectDataFilter, IotaObjectDataOptions, IotaObjectResponseQuery, + IotaTransactionBlockResponseOptions, + }, + types::{ + base_types::{IotaAddress, ObjectID}, + crypto::SignatureScheme::ED25519, + dynamic_field::DynamicFieldName, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + stardust::output::NftOutput, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + TypeTag, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, + }, + IotaClient, 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 SPONSOR_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +/// Got from iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs +const MAIN_ADDRESS_MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car"; + +#[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 an address that is not at index 0. This + // because we need an alias output that owns an Nft Output. In this case, we can + // derive the address index "/2'" of the "/0'" account. + let derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/2'")?; + println!("{derivation_path:?}"); + + // Derive the address of the first account and set it as default + let sender = + keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, Some(derivation_path))?; + + println!("Sender address - {sender:?}"); + + fund_address(&iota_client, &mut keystore, sender).await?; + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // This object id was fetched manually. It refers to an Alias Output object that + // owns a NftOutput. + let alias_output_object_id = ObjectID::from_hex_literal( + "0x3b35e67750b8e4ccb45b2fc4a6a26a6d97e74c37a532f17177e6324ab93eaca6", + )?; + + let alias_output_object = iota_client + .read_api() + .get_object_with_options( + alias_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("Alias output not found"))?; + + let alias_output_object_ref = alias_output_object.object_ref(); + + // Get the dynamic field owned by the Alias Output, i.e., only the Alias + // object. + // The dynamic field name for the Alias object is "alias", of type vector + let df_name = DynamicFieldName { + type_: TypeTag::Vector(Box::new(TypeTag::U8)), + value: serde_json::Value::String("alias".to_string()), + }; + let alias_object = iota_client + .read_api() + .get_dynamic_field_object(alias_output_object_id, df_name) + .await? + .data + .ok_or(anyhow!("alias not found"))?; + let alias_object_address = alias_object.object_ref().0; + + // Some objects are owned by the Alias object. In this case we filter them by + // type using the NftOutput type. + let owned_objects_query_filter = + IotaObjectDataFilter::StructType(NftOutput::tag(GAS::type_tag())); + let owned_objects_query = IotaObjectResponseQuery::new(Some(owned_objects_query_filter), None); + + // Get the first NftOutput found + let nft_output_object_owned_by_alias = iota_client + .read_api() + .get_owned_objects( + alias_object_address.into(), + Some(owned_objects_query), + None, + None, + ) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("Owned nft outputs not found"))? + .data + .ok_or(anyhow!("Nft output data not found"))?; + + let nft_output_object_ref = nft_output_object_owned_by_alias.object_ref(); + + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + + // Extract alias output assets + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?]; + if let Argument::Result(extracted_alias_output_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("alias_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + let extracted_base_token = Argument::NestedResult(extracted_alias_output_assets, 0); + let extracted_native_tokens_bag = + Argument::NestedResult(extracted_alias_output_assets, 1); + let alias = Argument::NestedResult(extracted_alias_output_assets, 2); + + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![extracted_base_token]; + + // Extract the IOTA balance. + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer the IOTA balance to the sender. + builder.transfer_arg(sender, iota_coin); + + // Cleanup the 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, + ); + + // Unlock the nft output. + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![ + alias, + builder.obj(ObjectArg::Receiving(nft_output_object_ref))?, + ]; + + let nft_output = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("address_unlock_condition").to_owned(), + ident_str!("unlock_alias_address_owned_nft").to_owned(), + type_arguments, + arguments, + ); + + // Transferring alias asset + builder.transfer_arg(sender, alias); + + // Extract nft assets(base token, native tokens bag, nft asset itself). + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![nft_output]; + // Finally call the nft_output::extract_assets function + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("nft_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // If the nft 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 and + // related nft object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let nft_asset = Argument::NestedResult(extracted_assets, 2); + + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![extracted_base_token]; + + // Extract the IOTA balance. + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer the IOTA balance to the sender. + builder.transfer_arg(sender, iota_coin); + + // Cleanup the bag because it is empty. + 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, + ); + + // Transferring nft asset + builder.transfer_arg(sender, nft_asset); + } + } + builder.finish() + }; + + // Setup gas budget and gas price + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} + +fn setup_keystore() -> Result { + // 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(()) +} + +async fn fund_address( + iota_client: &IotaClient, + keystore: &mut FileBasedKeystore, + recipient: IotaAddress, +) -> Result<(), anyhow::Error> { + // Derive the address of the sponsor. + let sponsor = keystore.import_from_mnemonic(SPONSOR_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("Sponsor address: {sponsor:?}"); + + // 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"))?; + + let pt = { + // Init a programmable transaction builder. + let mut builder = ProgrammableTransactionBuilder::new(); + // Pay all iotas from the gas object + builder.pay_all_iota(recipient); + builder.finish() + }; + + // Setup a gas budget and a gas price. + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create a transaction data that will be sent to the network. + let tx_data = TransactionData::new_programmable( + sponsor, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction. + let signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; + + // Execute the transaction. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Funding transaction digest: {}", + transaction_response.digest + ); + + Ok(()) +} From 7a2eb46a0e685e18b43d77539f8b1a35aeb4e02d Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 6 Aug 2024 17:00:59 +0200 Subject: [PATCH 20/39] feat(docs/examples): Add foundry output claim example (#1575) * feat(docs/examples): ad foundry output claim * fix(docs/examples): cargo toml and clones --- docs/examples/rust/Cargo.toml | 6 +- .../rust/stardust/foundry-output-claim.rs | 332 ++++++++++++++++++ 2 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 docs/examples/rust/stardust/foundry-output-claim.rs diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index f7eaebdb5df..793d3ec4098 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -10,12 +10,12 @@ publish = false anyhow.workspace = true bcs.workspace = true bip32.workspace = true +serde_json.workspace = true tokio.workspace = true iota-keys.workspace = true iota-sdk.workspace = true move-core-types.workspace = true -serde_json.workspace = true shared-crypto.workspace = true [[example]] @@ -33,3 +33,7 @@ path = "stardust/alias-output-claim.rs" [[example]] name = "basic-output-claim" path = "stardust/basic-output-claim.rs" + +[[example]] +name = "foundry-output-claim" +path = "stardust/foundry-output-claim.rs" diff --git a/docs/examples/rust/stardust/foundry-output-claim.rs b/docs/examples/rust/stardust/foundry-output-claim.rs new file mode 100644 index 00000000000..3c9fc0880c8 --- /dev/null +++ b/docs/examples/rust/stardust/foundry-output-claim.rs @@ -0,0 +1,332 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the claim of a CoinManagerTreasuryCap related to a +//! foundry 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}; + +use anyhow::anyhow; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_sdk::{ + rpc_types::{ + IotaObjectDataOptions, IotaObjectResponseQuery, IotaTransactionBlockResponseOptions, + }, + types::{ + base_types::{IotaAddress, ObjectID}, + coin_manager::CoinManagerTreasuryCap, + crypto::SignatureScheme::ED25519, + dynamic_field::DynamicFieldName, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + TypeTag, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, + }, + IotaClient, IotaClientBuilder, +}; +use move_core_types::{ident_str, language_storage::StructTag}; +use shared_crypto::intent::Intent; + +/// Got from iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs +const MAIN_ADDRESS_MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car"; + +/// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs +const SPONSOR_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +/// Creates a temporary keystore. +fn setup_keystore() -> Result { + // Create a temporary keystore. + let keystore_path = PathBuf::from("iotatempdb"); + if !keystore_path.exists() { + let keystore = FileBasedKeystore::new(&keystore_path)?; + keystore.save()?; + } + // Read the iota keystore. + FileBasedKeystore::new(&keystore_path) +} + +fn clean_keystore() -> Result<(), anyhow::Error> { + // Remove the keystore files. + fs::remove_file("iotatempdb")?; + fs::remove_file("iotatempdb.aliases")?; + Ok(()) +} + +async fn fund_address( + iota_client: &IotaClient, + keystore: &mut FileBasedKeystore, + recipient: IotaAddress, +) -> Result<(), anyhow::Error> { + // Derive the address of the sponsor. + let sponsor = keystore.import_from_mnemonic(SPONSOR_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("Sponsor address: {sponsor:?}"); + + // 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"))?; + + let pt = { + // Init a programmable transaction builder. + let mut builder = ProgrammableTransactionBuilder::new(); + // Pay all iotas from the gas object + builder.pay_all_iota(recipient); + builder.finish() + }; + + // Setup a gas budget and a gas price. + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create a transaction data that will be sent to the network. + let tx_data = TransactionData::new_programmable( + sponsor, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction. + let signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; + + // Execute the transaction. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Funding transaction digest: {}", + transaction_response.digest + ); + + 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 a temporary file based keystore. + let mut keystore = setup_keystore()?; + + // Derive the address of the first account and set it as default. + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("Sender address: {sender:?}"); + + // Fund the sender address + fund_address(&iota_client, &mut keystore, sender).await?; + + // Get a gas coin. + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found for sponsor"))?; + + // This object id was fetched manually. It refers to an Alias Output object that + // contains a CoinManagerTreasuryCap (i.e., a Foundry representation). + let alias_output_object_id = ObjectID::from_hex_literal( + "0xa58e9b6b85863e2fa50710c4594f701b2f5e2c6ff5e3c2b10cf09e6b18d740da", + )?; + let alias_output_object = iota_client + .read_api() + .get_object_with_options( + alias_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .ok_or(anyhow!("alias output not found"))?; + let alias_output_object_ref = alias_output_object.object_ref(); + + // Get the dynamic field owned by the Alias Output, i.e., only the Alias + // object. + // The dynamic field name for the Alias object is "alias", of type vector + let df_name = DynamicFieldName { + type_: TypeTag::Vector(Box::new(TypeTag::U8)), + value: serde_json::Value::String("alias".to_string()), + }; + let alias_object = iota_client + .read_api() + .get_dynamic_field_object(alias_output_object_id, df_name) + .await? + .data + .ok_or(anyhow!("alias not found"))?; + let alias_object_ref = alias_object.object_ref(); + + // Get the objects owned by the alias object and filter in the ones with + // CoinManagerTreasuryCap as type. + let alias_owned_objects_page = iota_client + .read_api() + .get_owned_objects( + alias_object_ref.0.into(), + Some(IotaObjectResponseQuery::new_with_options( + IotaObjectDataOptions::new().with_bcs().with_type(), + )), + None, + None, + ) + .await?; + // Only one page should exist. + assert!(!alias_owned_objects_page.has_next_page); + // Get the CoinManagerTreasuryCaps from the query + let owned_coin_manager_treasury_caps = alias_owned_objects_page + .data + .into_iter() + .filter(|object| { + CoinManagerTreasuryCap::is_coin_manager_treasury_cap( + &object + .data + .as_ref() + .expect("the query should request the data") + .object_type() + .expect("should contain the type") + .try_into() + .expect("should convert into a struct tag"), + ) + }) + .collect::>(); + + // Get only the first coin manager treasury cap + let coin_manager_treasury_cap_object = owned_coin_manager_treasury_caps + .into_iter() + .next() + .ok_or(anyhow!("no coin manager treasury caps found"))? + .data + .ok_or(anyhow!("coin manager treasury cap data not found"))?; + let coin_manager_treasury_cap_object_ref = coin_manager_treasury_cap_object.object_ref(); + + // Extract the foundry token type from the type parameters of the coin manager + // treasury cap object + let foundry_token_type_struct_tag: StructTag = coin_manager_treasury_cap_object + .object_type() + .expect("should contain the type") + .try_into()?; + let foundry_token_type = foundry_token_type_struct_tag + .type_params + .first() + .expect("should contain the type param"); + + // Create a PTB to claim the CoinManagerTreasuryCap related to the foundry + // output from the alias output. + let pt = { + // Init a programmable transaction builder. + let mut builder = ProgrammableTransactionBuilder::new(); + + // Type argument for an AliasOutput coming from the IOTA network, i.e., the + // IOTA token or the Gas type tag. + let type_arguments = vec![GAS::type_tag()]; + // Then pass the AliasOutput object as an input. + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?]; + // Finally call the alias_output::extract_assets function. + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("alias_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // The alias output can always be unlocked by the governor address. So the + // command will be successful and will return a `base_token` (i.e., IOTA) + // balance, a `Bag` of the related native tokens and the related Alias object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let extracted_alias = Argument::NestedResult(extracted_assets, 2); + + // Extract the IOTA balance. + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![extracted_base_token]; + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer the IOTA balance to the sender. + builder.transfer_arg(sender, iota_coin); + + // In this example the native tokens bag is empty, so it can be destroyed. + 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, + ); + + // Extract the CoinManagerTreasuryCap + let type_arguments = vec![foundry_token_type.clone()]; + let arguments = vec![ + extracted_alias, + builder.obj(ObjectArg::Receiving(coin_manager_treasury_cap_object_ref))?, + ]; + let coin_manager_treasury_cap = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("address_unlock_condition").to_owned(), + ident_str!("unlock_alias_address_owned_coinmanager_treasury").to_owned(), + type_arguments, + arguments, + ); + + // Transfer the coin manager treasury cap. + builder.transfer_arg(sender, coin_manager_treasury_cap); + + // Transfer the alias asset. + builder.transfer_arg(sender, extracted_alias); + } + builder.finish() + }; + + // Setup a gas budget and a gas price. + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create a transaction data that will be sent to the network. + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction. + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute the transaction. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file. + clean_keystore() +} From a292b110e701760cc7cf3270313b535577a4fbfa Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Wed, 7 Aug 2024 17:38:05 +0300 Subject: [PATCH 21/39] feat(docs/examples): Test the positive scenarios for using an NFT object (#1615) * Add example of third party simple nft package, PTB that creates custom nft from stardust::nft Add a conversion function for custom NFTs to allow migrating custom NFTs from Stardust NFTs --- Cargo.lock | 1 + docs/examples/move/custom_nft/Move.toml | 10 + .../move/custom_nft/sources/custom_nft.move | 95 +++++++ docs/examples/rust/Cargo.toml | 5 + docs/examples/rust/stardust/nft-migration.rs | 266 ++++++++++++++++++ 5 files changed, 377 insertions(+) create mode 100644 docs/examples/move/custom_nft/Move.toml create mode 100644 docs/examples/move/custom_nft/sources/custom_nft.move create mode 100644 docs/examples/rust/stardust/nft-migration.rs diff --git a/Cargo.lock b/Cargo.lock index 2bb9f6561bb..baa04b575a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3497,6 +3497,7 @@ dependencies = [ "bcs", "bip32", "iota-keys", + "iota-move-build", "iota-sdk 0.1.2", "move-core-types", "serde_json", diff --git a/docs/examples/move/custom_nft/Move.toml b/docs/examples/move/custom_nft/Move.toml new file mode 100644 index 00000000000..d7b360635d4 --- /dev/null +++ b/docs/examples/move/custom_nft/Move.toml @@ -0,0 +1,10 @@ +[package] +name = "custom_nft" +edition = "2024.beta" + +[dependencies] +Iota = { local = "../../../../crates/iota-framework/packages/iota-framework" } +Stardust = { local = "../../../../crates/iota-framework/packages/stardust" } + +[addresses] +custom_nft = "0x0" diff --git a/docs/examples/move/custom_nft/sources/custom_nft.move b/docs/examples/move/custom_nft/sources/custom_nft.move new file mode 100644 index 00000000000..d2f225d4178 --- /dev/null +++ b/docs/examples/move/custom_nft/sources/custom_nft.move @@ -0,0 +1,95 @@ +module custom_nft::custom_nft { + use iota::url::Url; + use stardust::nft::Nft; + use std::string::String; + use iota::event; + + /// An example NFT that can be minted by anybody + public struct CustomNFT has key, store { + id: UID, + /// Name for the token + name: String, + /// Description of the token + description: Option, + /// URL for the token + url: Url, + + collection_name: Option + // Allow custom attributes + } + + // ===== Events ===== + + public struct CustomNFTMinted has copy, drop { + // The Object ID of the NFT + object_id: ID, + // The creator of the NFT + creator: address, + // The name of the NFT + name: String, + } + + // ===== Public view functions ===== + + /// Get the NFT's `name` + public fun name(nft: &CustomNFT): &String { + &nft.name + } + + /// Get the NFT's `description` + public fun description(nft: &CustomNFT): &Option { + &nft.description + } + + /// Get the NFT's `url` + public fun url(nft: &CustomNFT): &Url { + &nft.url + } + + // ===== Entrypoints ===== + + /// The developer of CustomNft package could tie minting to several conditions, + /// for example only accept Stardust nfts from a certain issuer, with a certain name/collection name, NftId even. + /// Only the `immutable_issuer` and `id` fields count as proof for an Nft belonging to the original collection. + /// The developer could technically mint the same NFT on the running stardust network before the mainnet switch and fake the name and metadata. + public fun convert(stardust_nft: Nft, ctx: &mut TxContext) { + let nft_metadata = stardust_nft.immutable_metadata(); + + mint(*nft_metadata.name(), *nft_metadata.description(), *nft_metadata.uri(), *nft_metadata.collection_name(), ctx); + + stardust::nft::destroy(stardust_nft) + } + + #[allow(lint(self_transfer))] + /// Create a new CustomNFT + fun mint( + name: String, + description: Option, + url: Url, + collection_name: Option, + ctx: &mut TxContext + ) { + let sender = ctx.sender(); + let nft = CustomNFT { + id: object::new(ctx), + name, + description, + url, + collection_name + }; + + event::emit(CustomNFTMinted { + object_id: object::id(&nft), + creator: sender, + name: nft.name, + }); + + transfer::public_transfer(nft, sender); + } + + /// Permanently delete `nft` + public fun burn(nft: CustomNFT, _: &mut TxContext) { + let CustomNFT { id, name: _, description: _, url: _ , collection_name: _ } = nft; + id.delete() + } +} \ No newline at end of file diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 793d3ec4098..527ef87e43f 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -14,6 +14,7 @@ serde_json.workspace = true tokio.workspace = true iota-keys.workspace = true +iota-move-build.workspace = true iota-sdk.workspace = true move-core-types.workspace = true shared-crypto.workspace = true @@ -34,6 +35,10 @@ path = "stardust/alias-output-claim.rs" name = "basic-output-claim" path = "stardust/basic-output-claim.rs" +[[example]] +name = "nft-migration" +path = "stardust/nft-migration.rs" + [[example]] name = "foundry-output-claim" path = "stardust/foundry-output-claim.rs" diff --git a/docs/examples/rust/stardust/nft-migration.rs b/docs/examples/rust/stardust/nft-migration.rs new file mode 100644 index 00000000000..f131892f6c7 --- /dev/null +++ b/docs/examples/rust/stardust/nft-migration.rs @@ -0,0 +1,266 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the conversion of a stardust NFT into a custom user's +//! NFT. 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}; + +use anyhow::{anyhow, Result}; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_move_build::BuildConfig; +use iota_sdk::{ + rpc_types::{ + IotaObjectDataOptions, IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponseOptions, + }, + types::{ + base_types::{IotaAddress, 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, + }, + IotaClient, 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; +const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; + +#[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()?; + + // Derive the address of the first account and set it as default + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("{sender:?}"); + + // Publish the package of a custom NFT collection and then get the package id. + let custom_nft_package_id = + publish_custom_nft_package(sender, &mut keystore, &iota_client, CUSTOM_NFT_PACKAGE_PATH) + .await?; + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Get an NftOutput object id + let nft_output_object_id = ObjectID::from_hex_literal( + "0x6445847625cec7d1265ebb9d0da8050a2e43d2856c2746d3579df499a1a64226", + )?; + + // Get an NftOutput object + let nft_output_object = iota_client + .read_api() + .get_object_with_options( + nft_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .ok_or(anyhow!("Nft output not found"))?; + + let nft_output_object_ref = nft_output_object.object_ref(); + + // Create a PTB that extracts the stardust NFT from an NFTOutput and then calls + // the `random_nft::convert` method for converting it into a custom NFT of the + // collection just published. + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(nft_output_object_ref))?]; + // Call the nft_output::extract_assets function + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("nft_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // If the nft 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 and related nft object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let nft_asset = Argument::NestedResult(extracted_assets, 2); + + // Call conversion function in order to create custom nft from stardust nft + // asset. + builder.programmable_move_call( + custom_nft_package_id, + ident_str!("custom_nft").to_owned(), + ident_str!("convert").to_owned(), + vec![], + vec![nft_asset], + ); + + // Extract IOTA balance + let arguments = vec![extracted_base_token]; + let type_arguments = vec![GAS::type_tag()]; + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + // Transfer IOTA balance + builder.transfer_arg(sender, iota_coin); + + // Cleanup 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, + ); + } + builder.finish() + }; + + // Setup gas budget and gas price + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} + +fn setup_keystore() -> Result { + // 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(()) +} + +async fn publish_custom_nft_package( + sender: IotaAddress, + keystore: &mut FileBasedKeystore, + iota_client: &IotaClient, + package_path: &str, +) -> Result { + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Build custom nft package + let compiled_package = BuildConfig::default().build(package_path.into())?; + let modules = compiled_package + .get_modules() + .map(|module| { + let mut buf = Vec::new(); + module.serialize(&mut buf)?; + Ok(buf) + }) + .collect::>>>()?; + let dependencies = compiled_package.get_dependency_original_package_ids(); + + // Publish package + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + builder.publish_immutable(modules, dependencies); + 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?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Package publishing transaction digest: {}", + transaction_response.digest + ); + + // Extract package id from the transaction effects + let tx_effects = transaction_response + .effects + .expect("Transaction has no effects"); + let package_ref = tx_effects + .created() + .first() + .expect("There are no created objects"); + let package_id = package_ref.reference.object_id; + println!("Package ID: {}", package_id); + Ok(package_id) +} From 3a4c9434a0d6ecaabf83bed5b9666dbe66840daa Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Thu, 8 Aug 2024 12:56:54 +0200 Subject: [PATCH 22/39] feat(docs/stardust): Add docs for basic output claim (#1639) * feat(docs/stardust): add docs for basic output claim * Update docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * Update docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- docs/content/developer/stardust/claiming.mdx | 102 +++++++++++++++++- docs/examples/rust/Cargo.toml | 4 + .../check-basic-output-unlock-conditions.rs | 59 ++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index 42d36b362b7..03834605131 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -6,4 +6,104 @@ - Explains the resulting migrated state for simple coins (exchanges) - Demonstrates through examples for each output type the resulting move objects - Explains what commands need to be called in a PTB to claim the assets - - Describes transaction sponsorship from iota addresses for shimmer claiming \ No newline at end of file + - Describes transaction sponsorship from iota addresses for shimmer claiming + +## IOTA tokens automatic migration + + +## Output types to Move Objects + + +## Examples of Stardust asset claim transactions + +In the following, some examples of transactions for claiming the Stardust assets will be presented. Different commands in a [PTB](../iota-101/transactions/ptb/prog-txn-blocks.mdx) are used depending on the claiming scenario, that varies depending on the Stardust Output type and composition. + +### Basic Output + +An address can own `BasicOutput` objects that needs to be unlocked. In this case, some off-chain queries can be used to check the unlock conditions of the `BasicOutput` and asses if it can be unlocked. + + + + +```rust file=/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs#L20-L56 +``` + + + + +TODO + + + + +#### Claim of a Basic Output +Once a Basic Output can be unlocked the claim of its assets can start + +1. The first step is to fetch the `BasicOutput` object needed to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L74-L99 +``` + + + + +TODO + + + + + +2. Then we check the native tokens that possibily were held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L101-L128 +``` + + + + +TODO + + + + +3. Finally, a PTB can be created using the `basic_output` as input and the `Bag` keys for iterating the native tokens extracted. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L131-L195 +``` + + + + +TODO + + + + +### NFT Output + +#### Claim of a NFT Output + +#### Conversion of a NFT Output into a custom NFT + +### Alias Output + +#### Claim of a Alias Output + +#### Conversion of an Alias Output into a custom Object + +### Foundry Output + +#### Claim of a Foundry Output + + +## Sponsoring your Shimmer claiming + diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 527ef87e43f..4d5857cc2cc 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -35,6 +35,10 @@ path = "stardust/alias-output-claim.rs" name = "basic-output-claim" path = "stardust/basic-output-claim.rs" +[[example]] +name = "check-basic-output-unlock-conditions" +path = "stardust/check-basic-output-unlock-conditions.rs" + [[example]] name = "nft-migration" path = "stardust/nft-migration.rs" diff --git a/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs b/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs new file mode 100644 index 00000000000..205d49c6f94 --- /dev/null +++ b/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs @@ -0,0 +1,59 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating queries for checking the unlock conditions of a basic +//! output. In order to work, it requires a network with test objects +//! generated from iota-genesis-builder/src/stardust/test_outputs. + +use anyhow::anyhow; +use iota_sdk::{ + rpc_types::{IotaData, IotaObjectDataOptions}, + types::{base_types::ObjectID, stardust::output::BasicOutput}, + IotaClientBuilder, +}; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + // Build an iota client for a local network + let iota_client = IotaClientBuilder::default().build_localnet().await?; + + // 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( + "0xde09139ed46b9f5f876671e4403f312fad867c5ae5d300a252e4b6a6f1fa1fbd", + )?; + // 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"))?; + + // Convert the basic output object into its Rust representation + let basic_output = bcs::from_bytes::( + &basic_output_object + .bcs + .expect("should contain bcs") + .try_as_move() + .expect("should convert it to a move object") + .bcs_bytes, + )?; + + println!("Basic Output infos: {basic_output:?}"); + + if let Some(sdruc) = basic_output.storage_deposit_return { + println!("Storage Deposit Return Unlock Condition infos: {sdruc:?}"); + } + if let Some(tuc) = basic_output.timelock { + println!("Timelocked until: {}", tuc.unix_time); + } + if let Some(euc) = basic_output.expiration { + println!("Expiration Unlock Condition infos: {euc:?}"); + } + + Ok(()) +} From e4dd0bde188c28633ce81dd9c360c3e1d9c7de70 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Thu, 8 Aug 2024 14:10:30 +0300 Subject: [PATCH 23/39] feat(docs/content): documentation - claim NFT Output (#1654) * feat(docs/content): documentation - claim NFT Output --- docs/content/developer/stardust/claiming.mdx | 54 +++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index 03834605131..a89d2c6e27a 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -88,9 +88,59 @@ TODO -### NFT Output +### Nft Output -#### Claim of a NFT Output +An address can own `NftOutput` objects that needs to be unlocked. +In this case, some off-chain queries can be used to check the unlock conditions of the `NftOutput` and asses if it can be unlocked. +Check the example above, as it works the same as the [`Basic Output`](#basic-output). + +#### Claim of a Nft Output +Once a Nft Output can be unlocked the claim of its assets can start + +1. The first step is to fetch the `NftOutput` object needed to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L54-L78 +``` + + + + + + + + +2. Then we check the native tokens that possibily were held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L80-L105 +``` + + + + + + + +3. Finally, a PTB can be created using the `nft_output` as input and the `Bag` keys for iterating the native tokens extracted. +An Nft Output is different from a Basic Output as it contains the `Nft` object. +In fact, the main purpose of claiming is extracting the `Nft` object from the `NftOutput`. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L107-L173 +``` + + + + + + #### Conversion of a NFT Output into a custom NFT From ed4529ff3a5b460e89716ff584586ded13e0304a Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Thu, 8 Aug 2024 14:24:35 +0300 Subject: [PATCH 24/39] fear(docs/content) example of conversion of a stardust NFT into a custom user's. (#1669) * Add example ofr third party simple nft package, PTB that create custom nft from stardust::nft * feat(examples/docs): Add a conversion function for custom NFTs to allow migrating custom NFTs from Stardust NFTs * Add function that publishes random nft package via CLI. * feat(examples): replace package publishing approach with iota-move-builder * Fix chkecs * Minor refactoring * dprint fix * Fix review comments * feat(docs/content): add doc related to conversion of a NFT Output into sutom NFT * Review comment fixes * Fix Nft naming * Fix review comments --------- Co-authored-by: Mirko Zichichi --- docs/content/developer/stardust/claiming.mdx | 48 +++++++++++++++++++- docs/examples/rust/stardust/nft-migration.rs | 1 + 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index a89d2c6e27a..3e5d8edc518 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -142,7 +142,53 @@ In fact, the main purpose of claiming is extracting the `Nft` object from the `N -#### Conversion of a NFT Output into a custom NFT +#### Conversion of a Nft Output into a custom Nft +This topic outlines the process of converting an stardust Nft into a custom Nft. + +You need to have a prepared custom Nft package that you want to convert the Stardust Nft You need to have a prepared custom Nft package that you want to convert the Stardust Nft into. +The following is an example of a simple module for representing a `CustomNFT`, minting it, burning it and converting it from a Stardust `Nft`: + + + + +```move file=/docs/examples/move/custom_nft/sources/custom_nft.move +``` + + + + + + + + +1. Publish a custom Nft package, and retrieve its package ID. + + + + +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L206-L223 +``` + + + + + + + +2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::convert` method for converting it into a `CustomNFT` of the collection just published. +This conversion method extracts the Stardust `Nft` metadata and uses it for minting a new NFT. + + + + +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L83-L137 +``` + + + + + + ### Alias Output diff --git a/docs/examples/rust/stardust/nft-migration.rs b/docs/examples/rust/stardust/nft-migration.rs index f131892f6c7..adb5f1742a7 100644 --- a/docs/examples/rust/stardust/nft-migration.rs +++ b/docs/examples/rust/stardust/nft-migration.rs @@ -27,6 +27,7 @@ use iota_sdk::{ }; 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; From 86c8e3d6ff3f561ddcf79830ec00ff6ec9dc8c3e Mon Sep 17 00:00:00 2001 From: Valerii Reutov Date: Fri, 9 Aug 2024 10:55:09 +0300 Subject: [PATCH 25/39] feat(docs): Using an Alias object test scenario (#1679) * Add example ofr third party simple nft package, PTB that create custom nft from stardust::nft * feat(examples/docs): Add a conversion function for custom NFTs to allow migrating custom NFTs from Stardust NFTs * Add function that publishes random nft package via CLI. * feat(examples): replace package publishing approach with iota-move-builder * Fix chkecs * Minor refactoring * dprint fix * Fix review comments * feat(docs/content): add doc related to conversion of a NFT Output into sutom NFT * feat(docs): extended the custom_nft package with collections * Review comment fixes * Fix Nft naming * fix(docs): NFTs conversion docs were fixed after refactoring * fix(docs): spelling issues --------- Co-authored-by: Dkwcs --- docs/content/developer/stardust/claiming.mdx | 14 +- .../move/custom_nft/sources/collection.move | 140 +++++++ .../move/custom_nft/sources/custom_nft.move | 95 ----- .../examples/move/custom_nft/sources/nft.move | 136 +++++++ docs/examples/rust/Cargo.toml | 4 + .../examples/rust/stardust/alias-migration.rs | 376 ++++++++++++++++++ docs/examples/rust/stardust/nft-migration.rs | 15 +- 7 files changed, 672 insertions(+), 108 deletions(-) create mode 100644 docs/examples/move/custom_nft/sources/collection.move delete mode 100644 docs/examples/move/custom_nft/sources/custom_nft.move create mode 100644 docs/examples/move/custom_nft/sources/nft.move create mode 100644 docs/examples/rust/stardust/alias-migration.rs diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index 3e5d8edc518..5e50f0f7f53 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -56,7 +56,7 @@ TODO -2. Then we check the native tokens that possibily were held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. +2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. @@ -112,7 +112,7 @@ Once a Nft Output can be unlocked the claim of its assets can start -2. Then we check the native tokens that possibily were held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. +2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. @@ -146,12 +146,12 @@ In fact, the main purpose of claiming is extracting the `Nft` object from the `N This topic outlines the process of converting an stardust Nft into a custom Nft. You need to have a prepared custom Nft package that you want to convert the Stardust Nft You need to have a prepared custom Nft package that you want to convert the Stardust Nft into. -The following is an example of a simple module for representing a `CustomNFT`, minting it, burning it and converting it from a Stardust `Nft`: +The following is an example of a simple module for representing a custom NFT, minting it, burning it and converting it from a Stardust `Nft`: -```move file=/docs/examples/move/custom_nft/sources/custom_nft.move +```move file=/docs/examples/move/custom_nft/sources/nft.move ``` @@ -166,7 +166,7 @@ The following is an example of a simple module for representing a `CustomNFT`, m -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L206-L223 +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L210-L227 ``` @@ -175,13 +175,13 @@ The following is an example of a simple module for representing a `CustomNFT`, m -2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::convert` method for converting it into a `CustomNFT` of the collection just published. +2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::nft::convert` method for converting it into a `custom_nft::nft::Nft` of the collection just published. This conversion method extracts the Stardust `Nft` metadata and uses it for minting a new NFT. -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L83-L137 +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L84-L141 ``` diff --git a/docs/examples/move/custom_nft/sources/collection.move b/docs/examples/move/custom_nft/sources/collection.move new file mode 100644 index 00000000000..e7c675c0feb --- /dev/null +++ b/docs/examples/move/custom_nft/sources/collection.move @@ -0,0 +1,140 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +module custom_nft::collection { + use std::string::String; + + use iota::event; + + use stardust::alias::Alias; + + // ===== Errors ===== + + /// For when someone tries to drop a `Collection` with a wrong capability. + const EWrongCollectionControllerCap: u64 = 0; + + // ===== Structures ===== + + /// A capability allowing the bearer to create or drop an NFT collection. + /// A `stardust::alias::Alias` instance can be converted into `CollectionControllerCap` in this example, + /// since an alias address could be used as a collections controller in Stardust. + /// + /// NOTE: To simplify the example, `CollectionControllerCap` is publicly transferable, but to make sure that it can be created, + /// dropped and owned only by the related `stardust::alias::Alias` owner, we can remove the `store` ability and transfer a created + /// capability to the sender in the constructor. + public struct CollectionControllerCap has key, store { + id: UID, + } + + /// An NFT collection. + /// Can be created by a `CollectionControllerCap` owner and used to mint collection-related NFTs. + /// Can be dropped only by it's `CollectionControllerCap` owner. Once a collection is dropped, + /// it is impossible to mint new collection-related NFTs. + /// + /// NOTE: To simplify the example, `Collection` is publicly transferable, but to make sure that it can be created, + /// dropped and owned only by the related `CollectionControllerCap` owner, we can remove the `store` ability and transfer a created + /// capability to the sender in the constructor. + public struct Collection has key, store { + id: UID, + /// The related `CollectionControllerCap` ID. + cap_id: ID, + /// The collection name. + name: String, + } + + // ===== Events ===== + + /// Event marking when a `stardust::alias::Alias` has been converted into `CollectionControllerCap`. + public struct StardustAliasConverted has copy, drop { + /// The `stardust::alias::Alias` ID. + alias_id: ID, + /// The `CollectionControllerCap` ID. + cap_id: ID, + } + + /// Event marking when a `CollectionControllerCap` has been dropped. + public struct CollectionControllerCapDropped has copy, drop { + /// The `CollectionControllerCap` ID. + cap_id: ID, + } + + /// Event marking when a `Collection` has been created. + public struct CollectionCreated has copy, drop { + /// The collection ID. + collection_id: ID, + } + + /// Event marking when a `Collection` has been dropped. + public struct CollectionDropped has copy, drop { + /// The collection ID. + collection_id: ID, + } + + // ===== Public view functions ===== + + /// Get the Collection's `name` + public fun name(nft: &Collection): &String { + &nft.name + } + + // ===== Entrypoints ===== + + /// Convert a `stardust::alias::Alias` into `CollectionControllerCap`. + public fun convert_alias_to_collection_controller_cap(stardust_alias: Alias, ctx: &mut TxContext): CollectionControllerCap { + let cap = CollectionControllerCap { + id: object::new(ctx) + }; + + event::emit(StardustAliasConverted { + alias_id: object::id(&stardust_alias), + cap_id: object::id(&cap), + }); + + stardust::alias::destroy(stardust_alias); + + cap + } + + /// Drop a `CollectionControllerCap` instance. + public fun drop_collection_controller_cap(cap: CollectionControllerCap) { + event::emit(CollectionControllerCapDropped { + cap_id: object::id(&cap), + }); + + let CollectionControllerCap { id } = cap; + + object::delete(id) + } + + /// Create a `Collection` instance. + public fun create_collection(cap: &CollectionControllerCap, name: String, ctx: &mut TxContext): Collection { + let collection = Collection { + id: object::new(ctx), + cap_id: object::id(cap), + name, + }; + + event::emit(CollectionCreated { + collection_id: object::id(&collection), + }); + + collection + } + + /// Drop a `Collection` instance. + public fun drop_collection(cap: &CollectionControllerCap, collection: Collection) { + assert!(object::borrow_id(cap) == &collection.cap_id, EWrongCollectionControllerCap); + + event::emit(CollectionDropped { + collection_id: object::id(&collection), + }); + + let Collection { + id, + cap_id: _, + name: _ + } = collection; + + object::delete(id) + } +} diff --git a/docs/examples/move/custom_nft/sources/custom_nft.move b/docs/examples/move/custom_nft/sources/custom_nft.move deleted file mode 100644 index d2f225d4178..00000000000 --- a/docs/examples/move/custom_nft/sources/custom_nft.move +++ /dev/null @@ -1,95 +0,0 @@ -module custom_nft::custom_nft { - use iota::url::Url; - use stardust::nft::Nft; - use std::string::String; - use iota::event; - - /// An example NFT that can be minted by anybody - public struct CustomNFT has key, store { - id: UID, - /// Name for the token - name: String, - /// Description of the token - description: Option, - /// URL for the token - url: Url, - - collection_name: Option - // Allow custom attributes - } - - // ===== Events ===== - - public struct CustomNFTMinted has copy, drop { - // The Object ID of the NFT - object_id: ID, - // The creator of the NFT - creator: address, - // The name of the NFT - name: String, - } - - // ===== Public view functions ===== - - /// Get the NFT's `name` - public fun name(nft: &CustomNFT): &String { - &nft.name - } - - /// Get the NFT's `description` - public fun description(nft: &CustomNFT): &Option { - &nft.description - } - - /// Get the NFT's `url` - public fun url(nft: &CustomNFT): &Url { - &nft.url - } - - // ===== Entrypoints ===== - - /// The developer of CustomNft package could tie minting to several conditions, - /// for example only accept Stardust nfts from a certain issuer, with a certain name/collection name, NftId even. - /// Only the `immutable_issuer` and `id` fields count as proof for an Nft belonging to the original collection. - /// The developer could technically mint the same NFT on the running stardust network before the mainnet switch and fake the name and metadata. - public fun convert(stardust_nft: Nft, ctx: &mut TxContext) { - let nft_metadata = stardust_nft.immutable_metadata(); - - mint(*nft_metadata.name(), *nft_metadata.description(), *nft_metadata.uri(), *nft_metadata.collection_name(), ctx); - - stardust::nft::destroy(stardust_nft) - } - - #[allow(lint(self_transfer))] - /// Create a new CustomNFT - fun mint( - name: String, - description: Option, - url: Url, - collection_name: Option, - ctx: &mut TxContext - ) { - let sender = ctx.sender(); - let nft = CustomNFT { - id: object::new(ctx), - name, - description, - url, - collection_name - }; - - event::emit(CustomNFTMinted { - object_id: object::id(&nft), - creator: sender, - name: nft.name, - }); - - transfer::public_transfer(nft, sender); - } - - /// Permanently delete `nft` - public fun burn(nft: CustomNFT, _: &mut TxContext) { - let CustomNFT { id, name: _, description: _, url: _ , collection_name: _ } = nft; - id.delete() - } -} \ No newline at end of file diff --git a/docs/examples/move/custom_nft/sources/nft.move b/docs/examples/move/custom_nft/sources/nft.move new file mode 100644 index 00000000000..ccec1217b98 --- /dev/null +++ b/docs/examples/move/custom_nft/sources/nft.move @@ -0,0 +1,136 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +module custom_nft::nft { + use std::string::String; + + use iota::event; + use iota::url::Url; + + use custom_nft::collection::Collection; + + /// An example NFT that can be minted by anybody. + public struct Nft has key, store { + id: UID, + /// The token name. + name: String, + /// The token description. + description: Option, + /// The token URL. + url: Url, + /// The related collection name. + collection_name: Option + + // Allow custom attributes. + } + + // ===== Events ===== + + /// Event marking when an `Nft` has been minted. + public struct NftMinted has copy, drop { + /// The NFT id. + object_id: ID, + /// The NFT creator. + creator: address, + /// The NFT name. + name: String, + } + + // ===== Public view functions ===== + + /// Get the NFT's `name`. + public fun name(nft: &Nft): &String { + &nft.name + } + + /// Get the NFT's `description`. + public fun description(nft: &Nft): &Option { + &nft.description + } + + /// Get the NFT's `url`. + public fun url(nft: &Nft): &Url { + &nft.url + } + + // ===== Entrypoints ===== + + /// Convert a `stardust::nft::Nft` into `Nft`. + /// + /// The developer of the `custom_nft` package could tie minting to several conditions, for example: + /// - Only accept Stardust NFTs from a certain issuer, with a certain name/collection name, `NftId` even. + /// - Only the `immutable_issuer` and `id` fields count as proof for an NFT belonging to the original collection. + /// + /// The developer could technically mint the same NFT on the running Stardust network before the mainnet switch + /// and fake the name and metadata. + public fun convert(stardust_nft: stardust::nft::Nft, ctx: &mut TxContext): Nft { + let nft_metadata = stardust_nft.immutable_metadata(); + + let nft = mint( + *nft_metadata.name(), + *nft_metadata.description(), + *nft_metadata.uri(), + *nft_metadata.collection_name(), + ctx + ); + + stardust::nft::destroy(stardust_nft); + + nft + } + + /// Mint a collection-related NFT. + public fun mint_collection_related( + collection: &Collection, + name: String, + description: String, + url: Url, + ctx: &mut TxContext + ): Nft { + mint( + name, + option::some(description), + url, + option::some(*collection.name()), + ctx + ) + } + + /// Create a new `Nft` instance. + fun mint( + name: String, + description: Option, + url: Url, + collection_name: Option, + ctx: &mut TxContext + ): Nft { + let nft = Nft { + id: object::new(ctx), + name, + description, + url, + collection_name + }; + + event::emit(NftMinted { + object_id: object::id(&nft), + creator: ctx.sender(), + name: nft.name, + }); + + nft + } + + /// Permanently delete the `nft` instance. + public fun burn(nft: Nft) { + let Nft { + id, + name: _, + description: _, + url: _, + collection_name: _ + } = nft; + + object::delete(id) + } +} diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 4d5857cc2cc..1b0eb3192ba 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -39,6 +39,10 @@ path = "stardust/basic-output-claim.rs" name = "check-basic-output-unlock-conditions" path = "stardust/check-basic-output-unlock-conditions.rs" +[[example]] +name = "alias-migration" +path = "stardust/alias-migration.rs" + [[example]] name = "nft-migration" path = "stardust/nft-migration.rs" diff --git a/docs/examples/rust/stardust/alias-migration.rs b/docs/examples/rust/stardust/alias-migration.rs new file mode 100644 index 00000000000..562ab20f89b --- /dev/null +++ b/docs/examples/rust/stardust/alias-migration.rs @@ -0,0 +1,376 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the conversion of a stardust Alias into a custom +//! user's NFT collections controller. 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, Result}; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_move_build::BuildConfig; +use iota_sdk::{ + rpc_types::{ + IotaData, IotaObjectDataOptions, IotaTransactionBlockEffectsAPI, + IotaTransactionBlockResponseOptions, + }, + types::{ + base_types::{IotaAddress, ObjectID}, + crypto::SignatureScheme::ED25519, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + stardust::output::AliasOutput, + transaction::{Argument, CallArg, ObjectArg, Transaction, TransactionData}, + TypeTag, IOTA_FRAMEWORK_PACKAGE_ID, STARDUST_PACKAGE_ID, + }, + IotaClient, 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; +const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; + +#[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()?; + + // Derive the address of the first account and set it as default + let sender = keystore.import_from_mnemonic(MAIN_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("{sender:?}"); + + // Publish the package of a custom NFT collection and then get the package id. + let custom_nft_package_id = + publish_custom_nft_package(sender, &mut keystore, &iota_client, CUSTOM_NFT_PACKAGE_PATH) + .await?; + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Get an AliasOutput object id + let alias_output_object_id = ObjectID::from_hex_literal( + "0x354a1864c8af23fde393f7603bc133f755a9405353b30878e41b929eb7e37554", + )?; + + // Get an AliasOutput object + let alias_output_object = iota_client + .read_api() + .get_object_with_options( + alias_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .ok_or(anyhow!("Alias output not found"))?; + let alias_output_object_ref = alias_output_object.object_ref(); + + // Convert the AliasOutput object into its Rust representation. + let alias_output = bcs::from_bytes::( + &alias_output_object + .bcs + .expect("should contain bcs") + .try_as_move() + .expect("should convert it to a move object") + .bcs_bytes, + )?; + + // Extract the keys of the native_tokens bag if it is not empty; the keys + // are the type_arg of each native token, so they can be used later in the PTB. + let mut df_type_keys: Vec = vec![]; + let native_token_bag = alias_output.native_tokens; + if native_token_bag.size > 0 { + // Get the dynamic fields owned by the native tokens bag. + let dynamic_field_page = iota_client + .read_api() + .get_dynamic_fields(*native_token_bag.id.object_id(), None, None) + .await?; + // Only one page should exist. + assert!(!dynamic_field_page.has_next_page); + + // Extract the dynamic fields keys, i.e., the native token type. + df_type_keys.extend( + dynamic_field_page + .data + .into_iter() + .map(|dyi| { + dyi.name + .value + .as_str() + .expect("should be a string") + .to_string() + }) + .collect::>(), + ); + } + + // Create a PTB that extracts the related stardust Alias from the AliasOutput + // and then calls the + // `custom_nft::collection::convert_alias_to_collection_controller_cap` function + // to convert it into an NFT collection controller, create a collection and mint + // a few NFTs. + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(alias_output_object_ref))?]; + // Call the nft_output::extract_assets function + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_PACKAGE_ID, + ident_str!("alias_output").to_owned(), + ident_str!("extract_assets").to_owned(), + vec![GAS::type_tag()], + arguments, + ) { + // The alias output can always be unlocked by the governor address. So the + // command will be successful and will return a `base_token` (i.e., IOTA) + // balance, a `Bag` of the related native tokens and the related Alias object. + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let mut extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + let alias_asset = Argument::NestedResult(extracted_assets, 2); + + // Call the conversion function to create an NFT collection controller from the + // extracted alias. + let nft_collection_controller = builder.programmable_move_call( + custom_nft_package_id, + ident_str!("collection").to_owned(), + ident_str!("convert_alias_to_collection_controller_cap").to_owned(), + vec![], + vec![alias_asset], + ); + + // Create an NFT collection + let nft_collection_name = builder + .input(CallArg::Pure(bcs::to_bytes("Collection name").unwrap())) + .unwrap(); + + let nft_collection = builder.programmable_move_call( + custom_nft_package_id, + ident_str!("collection").to_owned(), + ident_str!("create_collection").to_owned(), + vec![], + vec![nft_collection_controller, nft_collection_name], + ); + + // Mint a collection-related NFT + let nft_name = builder + .input(CallArg::Pure(bcs::to_bytes("NFT name").unwrap())) + .unwrap(); + let nft_description = builder + .input(CallArg::Pure(bcs::to_bytes("NFT description").unwrap())) + .unwrap(); + let nft_url_value = builder + .input(CallArg::Pure(bcs::to_bytes("NFT URL").unwrap())) + .unwrap(); + let nft_url = builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, + ident_str!("url").to_owned(), + ident_str!("new_unsafe").to_owned(), + vec![], + vec![nft_url_value], + ); + + let nft = builder.programmable_move_call( + custom_nft_package_id, + ident_str!("nft").to_owned(), + ident_str!("mint_collection_related").to_owned(), + vec![], + vec![nft_collection, nft_name, nft_description, nft_url], + ); + + // Transfer the NFT + builder.transfer_arg(sender, nft); + + // Drop the NFT collection to make impossible to mint new related NFTs + builder.programmable_move_call( + custom_nft_package_id, + ident_str!("collection").to_owned(), + ident_str!("drop_collection").to_owned(), + vec![], + vec![nft_collection_controller, nft_collection], + ); + + // Transfer the NFT collection controller + builder.transfer_arg(sender, nft_collection_controller); + + // Extract IOTA balance + let iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + vec![GAS::type_tag()], + vec![extracted_base_token], + ); + + // Transfer IOTA balance + builder.transfer_arg(sender, iota_coin); + + // Extract the native tokens from the bag. + for type_key in df_type_keys { + let type_arguments = vec![TypeTag::from_str(&format!("0x{type_key}"))?]; + let arguments = vec![extracted_native_tokens_bag, builder.pure(sender)?]; + + // Extract a native token balance. + extracted_native_tokens_bag = builder.programmable_move_call( + STARDUST_PACKAGE_ID, + ident_str!("utilities").to_owned(), + ident_str!("extract_and_send_to").to_owned(), + type_arguments, + arguments, + ); + } + + // Cleanup bag. + builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, + ident_str!("bag").to_owned(), + ident_str!("destroy_empty").to_owned(), + vec![], + vec![extracted_native_tokens_bag], + ); + } + builder.finish() + }; + + // Setup gas budget and gas price + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} + +fn setup_keystore() -> Result { + // 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(()) +} + +async fn publish_custom_nft_package( + sender: IotaAddress, + keystore: &mut FileBasedKeystore, + iota_client: &IotaClient, + package_path: &str, +) -> Result { + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Build custom nft package + let compiled_package = BuildConfig::default().build(package_path.into())?; + let modules = compiled_package + .get_modules() + .map(|module| { + let mut buf = Vec::new(); + module.serialize(&mut buf)?; + Ok(buf) + }) + .collect::>>>()?; + let dependencies = compiled_package.get_dependency_original_package_ids(); + + // Publish package + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + builder.publish_immutable(modules, dependencies); + 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?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Package publishing transaction digest: {}", + transaction_response.digest + ); + + // Extract package id from the transaction effects + let tx_effects = transaction_response + .effects + .expect("Transaction has no effects"); + let package_ref = tx_effects + .created() + .first() + .expect("There are no created objects"); + let package_id = package_ref.reference.object_id; + println!("Package ID: {}", package_id); + Ok(package_id) +} diff --git a/docs/examples/rust/stardust/nft-migration.rs b/docs/examples/rust/stardust/nft-migration.rs index adb5f1742a7..c220aa6cd1d 100644 --- a/docs/examples/rust/stardust/nft-migration.rs +++ b/docs/examples/rust/stardust/nft-migration.rs @@ -79,8 +79,8 @@ async fn main() -> Result<(), anyhow::Error> { let nft_output_object_ref = nft_output_object.object_ref(); // Create a PTB that extracts the stardust NFT from an NFTOutput and then calls - // the `random_nft::convert` method for converting it into a custom NFT of the - // collection just published. + // the `custom_nft::nft::convert` function for converting it into a custom NFT + // of the just published package. let pt = { let mut builder = ProgrammableTransactionBuilder::new(); let type_arguments = vec![GAS::type_tag()]; @@ -93,23 +93,26 @@ async fn main() -> Result<(), anyhow::Error> { type_arguments, arguments, ) { - // If the nft output can be unlocked, the command will be succesful + // If the nft output can be unlocked, the command will be successful // and will return a `base_token` (i.e., IOTA) balance and a // `Bag` of native tokens and related nft object. let extracted_base_token = Argument::NestedResult(extracted_assets, 0); let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); let nft_asset = Argument::NestedResult(extracted_assets, 2); - // Call conversion function in order to create custom nft from stardust nft + // Call the conversion function to create a custom nft from the stardust nft // asset. - builder.programmable_move_call( + let custom_nft = builder.programmable_move_call( custom_nft_package_id, - ident_str!("custom_nft").to_owned(), + ident_str!("nft").to_owned(), ident_str!("convert").to_owned(), vec![], vec![nft_asset], ); + // Transfer the converted NFT + builder.transfer_arg(sender, custom_nft); + // Extract IOTA balance let arguments = vec![extracted_base_token]; let type_arguments = vec![GAS::type_tag()]; From baa490cf5f5665849f30d79ba8341f112bbc69fb Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Fri, 9 Aug 2024 10:38:26 +0200 Subject: [PATCH 26/39] refactor(docs/content): Split claiming docs in a tree (#1710) * refactor(docs/content): split claiming docs in a tree * feat(docs/content): add address unlock condition claim * fix(docs/content): claiming references * fix(docs/content): claiming references 2 * chore(docs): fix doc tree for claiming --------- Co-authored-by: Levente Pap --- docs/content/developer/stardust/claiming.mdx | 180 +----------------- .../stardust/claiming/address-uc.mdx | 6 + .../developer/stardust/claiming/alias.mdx | 8 + .../developer/stardust/claiming/basic.mdx | 71 +++++++ .../developer/stardust/claiming/foundry.mdx | 6 + .../developer/stardust/claiming/nft.mdx | 104 ++++++++++ .../stardust/claiming/self-sponsor.mdx | 5 + docs/content/sidebars/developer.js | 41 +++- 8 files changed, 250 insertions(+), 171 deletions(-) create mode 100644 docs/content/developer/stardust/claiming/address-uc.mdx create mode 100644 docs/content/developer/stardust/claiming/alias.mdx create mode 100644 docs/content/developer/stardust/claiming/basic.mdx create mode 100644 docs/content/developer/stardust/claiming/foundry.mdx create mode 100644 docs/content/developer/stardust/claiming/nft.mdx create mode 100644 docs/content/developer/stardust/claiming/self-sponsor.mdx diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index 5e50f0f7f53..e0cb28c72df 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -20,186 +20,26 @@ In the following, some examples of transactions for claiming the Stardust assets ### Basic Output -An address can own `BasicOutput` objects that needs to be unlocked. In this case, some off-chain queries can be used to check the unlock conditions of the `BasicOutput` and asses if it can be unlocked. - - - - -```rust file=/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs#L20-L56 -``` - - - - -TODO - - - - -#### Claim of a Basic Output -Once a Basic Output can be unlocked the claim of its assets can start - -1. The first step is to fetch the `BasicOutput` object needed to be claimed. - - - - -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L74-L99 -``` - - - - -TODO - - - - - -2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. - - - - -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L101-L128 -``` - - - - -TODO - - - - -3. Finally, a PTB can be created using the `basic_output` as input and the `Bag` keys for iterating the native tokens extracted. - - - - -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L131-L195 -``` - - - - -TODO - - - +Go to [Basic Output](claiming/basic.mdx). ### Nft Output -An address can own `NftOutput` objects that needs to be unlocked. -In this case, some off-chain queries can be used to check the unlock conditions of the `NftOutput` and asses if it can be unlocked. -Check the example above, as it works the same as the [`Basic Output`](#basic-output). - -#### Claim of a Nft Output -Once a Nft Output can be unlocked the claim of its assets can start - -1. The first step is to fetch the `NftOutput` object needed to be claimed. - - - - -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L54-L78 -``` - - - - - - - - -2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. - - - - -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L80-L105 -``` - - - - - - - -3. Finally, a PTB can be created using the `nft_output` as input and the `Bag` keys for iterating the native tokens extracted. -An Nft Output is different from a Basic Output as it contains the `Nft` object. -In fact, the main purpose of claiming is extracting the `Nft` object from the `NftOutput`. - - - - -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L107-L173 -``` - - - - - - - -#### Conversion of a Nft Output into a custom Nft -This topic outlines the process of converting an stardust Nft into a custom Nft. - -You need to have a prepared custom Nft package that you want to convert the Stardust Nft You need to have a prepared custom Nft package that you want to convert the Stardust Nft into. -The following is an example of a simple module for representing a custom NFT, minting it, burning it and converting it from a Stardust `Nft`: - - - - -```move file=/docs/examples/move/custom_nft/sources/nft.move -``` - - - - - - - - -1. Publish a custom Nft package, and retrieve its package ID. - - - - -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L210-L227 -``` - - - - - - - -2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::nft::convert` method for converting it into a `custom_nft::nft::Nft` of the collection just published. -This conversion method extracts the Stardust `Nft` metadata and uses it for minting a new NFT. - - - - -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L84-L141 -``` - - - - - - +Go to [Nft Output](claiming/nft.mdx). ### Alias Output -#### Claim of a Alias Output - -#### Conversion of an Alias Output into a custom Object +Go to [Alias Output](claiming/alias.mdx). ### Foundry Output -#### Claim of a Foundry Output +Go to [Foundry Output](claiming/foundry.mdx). + +### Output unlockable by an Alias/Nft Address +Go to [Output unlockable by an Alias/Nft Address](claiming/address-uc.mdx). ## Sponsoring your Shimmer claiming +In the case in which some assets are owned by a Shimmer address that helds no IOTA tokens (needed to pay for gas), then a claiming can benefit by the process of self-sponsoring. This means that an address owning IOTA tokens can be used by the same user to sponsor a transaction claiming Shimmer assets. + +Go to [Self-sponsor Shimmer Claimings](claiming/self-sponsor.mdx). \ No newline at end of file diff --git a/docs/content/developer/stardust/claiming/address-uc.mdx b/docs/content/developer/stardust/claiming/address-uc.mdx new file mode 100644 index 00000000000..4f8d1b309fa --- /dev/null +++ b/docs/content/developer/stardust/claiming/address-uc.mdx @@ -0,0 +1,6 @@ +--- + title: Claiming an Output unlockable by an Alias/Nft Address +--- + +## Claim of an Output owned by another Alias/Nft Output + diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx new file mode 100644 index 00000000000..3578d48373d --- /dev/null +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -0,0 +1,8 @@ +--- + title: Claiming Alias Outputs +--- + +## Claim of a Alias Output + +## Conversion of an Alias Output into a custom Object + diff --git a/docs/content/developer/stardust/claiming/basic.mdx b/docs/content/developer/stardust/claiming/basic.mdx new file mode 100644 index 00000000000..553154799c1 --- /dev/null +++ b/docs/content/developer/stardust/claiming/basic.mdx @@ -0,0 +1,71 @@ +--- + title: Claiming Basic Outputs +--- + +An address can own `BasicOutput` objects that needs to be unlocked. In this case, some off-chain queries can be used to check the unlock conditions of the `BasicOutput` and asses if it can be unlocked. + + + + +```rust file=/docs/examples/rust/stardust/check-basic-output-unlock-conditions.rs#L20-L56 +``` + + + + +TODO + + + + +## Claim of a Basic Output +Once a Basic Output can be unlocked the claim of its assets can start + +1. The first step is to fetch the `BasicOutput` object needed to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L74-L99 +``` + + + + +TODO + + + + + +2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L101-L128 +``` + + + + +TODO + + + + +3. Finally, a PTB can be created using the `basic_output` as input and the `Bag` keys for iterating the native tokens extracted. + + + + +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L131-L195 +``` + + + + +TODO + + + diff --git a/docs/content/developer/stardust/claiming/foundry.mdx b/docs/content/developer/stardust/claiming/foundry.mdx new file mode 100644 index 00000000000..0e1ab19f38d --- /dev/null +++ b/docs/content/developer/stardust/claiming/foundry.mdx @@ -0,0 +1,6 @@ +--- + title: Claiming Foundry Outputs +--- + +## Claim of a Foundry Output + diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx new file mode 100644 index 00000000000..46c2d6e6168 --- /dev/null +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -0,0 +1,104 @@ +--- + title: Claiming Nft Outputs +--- + +An address can own `NftOutput` objects that needs to be unlocked. +In this case, some off-chain queries can be used to check the unlock conditions of the `NftOutput` and asses if it can be unlocked. +Check the example above, as it works the same as the [`Basic Output`](basic.mdx). + +## Claim of an Nft Output +Once an Nft Output can be unlocked the claim of its assets can start + +1. The first step is to fetch the `NftOutput` object needed to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L54-L78 +``` + + + + + + + + +2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L80-L105 +``` + + + + + + + +3. Finally, a PTB can be created using the `nft_output` as input and the `Bag` keys for iterating the native tokens extracted. +An Nft Output is different from a Basic Output as it contains the `Nft` object. +In fact, the main purpose of claiming is extracting the `Nft` object from the `NftOutput`. + + + + +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L107-L173 +``` + + + + + + + +## Conversion of an Nft Output into a custom Nft +This topic outlines the process of converting an stardust Nft into a custom Nft. + +You need to have a prepared custom Nft package that you want to convert the Stardust Nft You need to have a prepared custom Nft package that you want to convert the Stardust Nft into. +The following is an example of a simple module for representing a custom NFT, minting it, burning it and converting it from a Stardust `Nft`: + + + + +```move file=/docs/examples/move/custom_nft/sources/nft.move +``` + + + + + + + + +1. Publish a custom Nft package, and retrieve its package ID. + + + + +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L210-L227 +``` + + + + + + + +2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::nft::convert` method for converting it into a `custom_nft::nft::Nft` of the collection just published. +This conversion method extracts the Stardust `Nft` metadata and uses it for minting a new NFT. + + + + +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L84-L141 +``` + + + + + + + diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx new file mode 100644 index 00000000000..bc0c07a60ef --- /dev/null +++ b/docs/content/developer/stardust/claiming/self-sponsor.mdx @@ -0,0 +1,5 @@ +--- + title: Self-sponsoring Shimmer Assets Claiming +--- + + diff --git a/docs/content/sidebars/developer.js b/docs/content/sidebars/developer.js index 9d1f072e7df..32535eb9bd6 100644 --- a/docs/content/sidebars/developer.js +++ b/docs/content/sidebars/developer.js @@ -731,7 +731,46 @@ const developer = [ 'developer/stardust/addresses', 'developer/stardust/units', 'developer/stardust/migration-process', - 'developer/stardust/claiming', + { + type: 'category', + label: 'Claiming Stardust Assets', + link: { + type: 'doc', + id: 'developer/stardust/claiming', + }, + items: [ + { + type: 'doc', + label: 'Basic Outputs', + id: 'developer/stardust/claiming/basic', + }, + { + type: 'doc', + label: 'Nft Outputs', + id: 'developer/stardust/claiming/nft', + }, + { + type: 'doc', + label: 'Alias Outputs', + id: 'developer/stardust/claiming/alias', + }, + { + type: 'doc', + label: 'Foundry Outputs', + id: 'developer/stardust/claiming/foundry', + }, + { + type: 'doc', + label: 'Output unlockable by an Alias/Nft Address', + id: 'developer/stardust/claiming/address-uc', + }, + { + type: 'doc', + label: 'Self-sponsor Shimmer Claiming', + id: 'developer/stardust/claiming/self-sponsor', + }, + ], + }, 'developer/stardust/vested', 'developer/stardust/testing', 'developer/stardust/if-tools', From a5f4a926e8e26e48e32e9d41ed7b1baca9e3578d Mon Sep 17 00:00:00 2001 From: Valerii Reutov Date: Mon, 12 Aug 2024 14:30:20 +0300 Subject: [PATCH 27/39] feat(docs): Alias usage documentation was added (#1723) * Add example ofr third party simple nft package, PTB that create custom nft from stardust::nft * feat(examples/docs): Add a conversion function for custom NFTs to allow migrating custom NFTs from Stardust NFTs * Add function that publishes random nft package via CLI. * feat(examples): replace package publishing approach with iota-move-builder * Fix chkecs * Minor refactoring * dprint fix * Fix review comments * feat(docs/content): add doc related to conversion of a NFT Output into sutom NFT * feat(docs): extended the custom_nft package with collections * Review comment fixes * Fix Nft naming * fix(docs): NFTs conversion docs were fixed after refactoring * fix(docs): spelling issues * feat(docs): added alias documentation * fix(docs): spelling issues * fix(docs): review comments --------- Co-authored-by: Dkwcs --- .../developer/stardust/claiming/alias.mdx | 96 ++++++++++++++++++- .../developer/stardust/claiming/basic.mdx | 2 +- .../developer/stardust/claiming/nft.mdx | 2 +- .../examples/move/custom_nft/sources/nft.move | 2 +- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx index 3578d48373d..bcafaae7c14 100644 --- a/docs/content/developer/stardust/claiming/alias.mdx +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -2,7 +2,101 @@ title: Claiming Alias Outputs --- -## Claim of a Alias Output +An address can own `AliasOutput` objects only if before the migration it was set as the Alias Governor Address. +In this case, the `AliasOutput` object is an owned object in the ledger and its owner is the Governor address. +Such address can be directly controlled by a user or by another object (either an `Alias` or `Nft` object). For the latter use case, check the [`Claiming an Output unlockable by an Alias/Nft Address`](address-uc.mdx) example. + +## Claim of an Alias Output + +A Governor address can claim the `AliasOutput` assets at any time: + +1. The first step is to fetch an `AliasOutput` object needed to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L74-L99 +``` + + + + + + + + +2. Then we check the native tokens that were possibly held by this output. +A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are bag indexes. +In the case of the native tokens, the keys are strings representing the `OTW` used for the native token declaration. + + + + +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L101-L128 +``` + + + + + + + +3. Finally, a PTB can be created using the `alias_output_object_ref` as input and the native token keys. +An `AliasOutput` is different from an `NftOutput` or a `BasicOutput` as it contains the `Alias` object. +In fact, the main purpose of claiming is extracting the `Alias` object from the `AliasOutput`. + + + + +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L130-L198 +``` + + + + + + ## Conversion of an Alias Output into a custom Object +We need to have a custom package prepared that contains a logic for converting an `Alias` into a new entity usable for your project. + +In Stardust, an alias can be used for different purposes. One of them is acting as an NFT collection controller. +In the following, an example of the process of converting a Stardust `Alias` into a `CollectionControllerCap` is outlined. + +The following example extends the one described in the [Conversion of an Nft Output into a custom Nft](nft.mdx#conversion-of-an-nft-output-into-a-custom-nft) documentation: + + + + +The `collection.move` module extends the `custom_nft` package to make it possible to work with NFT collections: + +```move file=/docs/examples/move/custom_nft/sources/collection.move +``` + +Also, the `nft.move` module was extended with the following function: + +```move file=/docs/examples/move/custom_nft/sources/nft.move#L82-L97 +``` + + + + + + + +Once the package is prepared, we can extract and use a Stardust `Alias` in a single transaction to create a `CollectionControllerCap`. +This capability is then used in later transactions for managing new collections. + + + + +```rust file=/docs/examples/rust/stardust/alias-migration.rs#L122-L247 +``` + + + + + + + diff --git a/docs/content/developer/stardust/claiming/basic.mdx b/docs/content/developer/stardust/claiming/basic.mdx index 553154799c1..f29faafbf33 100644 --- a/docs/content/developer/stardust/claiming/basic.mdx +++ b/docs/content/developer/stardust/claiming/basic.mdx @@ -2,7 +2,7 @@ title: Claiming Basic Outputs --- -An address can own `BasicOutput` objects that needs to be unlocked. In this case, some off-chain queries can be used to check the unlock conditions of the `BasicOutput` and asses if it can be unlocked. +An address can own `BasicOutput` objects that needs to be unlocked. In this case, some off-chain queries can be used to check the unlock conditions of the `BasicOutput` and assess if it can be unlocked. diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx index 46c2d6e6168..b3f40301391 100644 --- a/docs/content/developer/stardust/claiming/nft.mdx +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -3,7 +3,7 @@ --- An address can own `NftOutput` objects that needs to be unlocked. -In this case, some off-chain queries can be used to check the unlock conditions of the `NftOutput` and asses if it can be unlocked. +In this case, some off-chain queries can be used to check the unlock conditions of the `NftOutput` and assess if it can be unlocked. Check the example above, as it works the same as the [`Basic Output`](basic.mdx). ## Claim of an Nft Output diff --git a/docs/examples/move/custom_nft/sources/nft.move b/docs/examples/move/custom_nft/sources/nft.move index ccec1217b98..5377a5cb6f2 100644 --- a/docs/examples/move/custom_nft/sources/nft.move +++ b/docs/examples/move/custom_nft/sources/nft.move @@ -121,7 +121,7 @@ module custom_nft::nft { nft } - /// Permanently delete the `nft` instance. + /// Permanently delete the `Nft` instance. public fun burn(nft: Nft) { let Nft { id, From ac7900e2a480e9bc6c831d4ae56035f6e3bb2cf1 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Tue, 13 Aug 2024 10:36:20 +0300 Subject: [PATCH 28/39] feat(docs/content): docs implementation for address unlock condition example (#1706) * feat(docs/content): docs impl for address unlock condition --- docs/content/developer/stardust/claiming.mdx | 2 +- .../stardust/claiming/address-uc.mdx | 6 -- .../claiming/address-unlock-condition.mdx | 66 +++++++++++++++++++ .../developer/stardust/claiming/basic.mdx | 2 +- .../developer/stardust/claiming/nft.mdx | 2 +- docs/content/sidebars/developer.js | 2 +- 6 files changed, 70 insertions(+), 10 deletions(-) delete mode 100644 docs/content/developer/stardust/claiming/address-uc.mdx create mode 100644 docs/content/developer/stardust/claiming/address-unlock-condition.mdx diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index e0cb28c72df..bf997560e96 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -36,7 +36,7 @@ Go to [Foundry Output](claiming/foundry.mdx). ### Output unlockable by an Alias/Nft Address -Go to [Output unlockable by an Alias/Nft Address](claiming/address-uc.mdx). +Go to [Output unlockable by an Alias/Nft Address](claiming/address-unlock-condition.mdx). ## Sponsoring your Shimmer claiming diff --git a/docs/content/developer/stardust/claiming/address-uc.mdx b/docs/content/developer/stardust/claiming/address-uc.mdx deleted file mode 100644 index 4f8d1b309fa..00000000000 --- a/docs/content/developer/stardust/claiming/address-uc.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- - title: Claiming an Output unlockable by an Alias/Nft Address ---- - -## Claim of an Output owned by another Alias/Nft Output - diff --git a/docs/content/developer/stardust/claiming/address-unlock-condition.mdx b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx new file mode 100644 index 00000000000..6a669061681 --- /dev/null +++ b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx @@ -0,0 +1,66 @@ +--- + title: Claiming an Output unlockable by an Alias/Nft Address +--- + +In Stardust outputs presented an Address Unlock Condition or similarly, in the case of the Alias Output, a Governor Address Unlock Condition. In the new ledger, this mechanism is represented as an address owning the associated Output object. Most of the times the address is directly managed through a keypair by a user, but sometimes this address could represent another object. In this case, that object owns the interested Output object. Coming from the Stardust migration, only `Alias` and `Nft` objects can own other Output objects. + +## Claim of an Output owned by another Alias/Nft object + +For this example, we're using an `AliasOutput` to extract an `Alias` object that owns an `NftOutput`. + +1. The first step is to fetch the `AliasOutput` object that is needed for claiming the `NftOutput`. + + + + +```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L74-L90 +``` + + + + + + + +2. By using the dynamic field function with the "alias" dynamic field key filter, we gather the `Alias` object itself. + + + + +```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L95-L105 +``` + + + + + + + +3. Some objects are owned by the `Alias` object. In this case we filter them by type using the `NftOutput` type tag. +Applying the filter to get `NftOutput`s owned by the `Alias`. + + + + +```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L109-L130 +``` + + + + + + + +4. Create PTB that firstly extracts the assets from the `AliasOutput` and then it uses the extracted `Alias` to "address unlock" the `NftOutput` using the funсtion `unlock_alias_address_owned_nft`. + + + + +```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L132-L241 +``` + + + + + + \ No newline at end of file diff --git a/docs/content/developer/stardust/claiming/basic.mdx b/docs/content/developer/stardust/claiming/basic.mdx index f29faafbf33..c7577a6335e 100644 --- a/docs/content/developer/stardust/claiming/basic.mdx +++ b/docs/content/developer/stardust/claiming/basic.mdx @@ -21,7 +21,7 @@ TODO ## Claim of a Basic Output Once a Basic Output can be unlocked the claim of its assets can start -1. The first step is to fetch the `BasicOutput` object needed to be claimed. +1. The first step is to fetch the `BasicOutput` object that needs to be claimed. diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx index b3f40301391..0dab8f6bc5b 100644 --- a/docs/content/developer/stardust/claiming/nft.mdx +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -9,7 +9,7 @@ Check the example above, as it works the same as the [`Basic Output`](basic.mdx) ## Claim of an Nft Output Once an Nft Output can be unlocked the claim of its assets can start -1. The first step is to fetch the `NftOutput` object needed to be claimed. +1. The first step is to fetch the `NftOutput` object that needs to be claimed. diff --git a/docs/content/sidebars/developer.js b/docs/content/sidebars/developer.js index 32535eb9bd6..a753e74f8f9 100644 --- a/docs/content/sidebars/developer.js +++ b/docs/content/sidebars/developer.js @@ -762,7 +762,7 @@ const developer = [ { type: 'doc', label: 'Output unlockable by an Alias/Nft Address', - id: 'developer/stardust/claiming/address-uc', + id: 'developer/stardust/claiming/address-unlock-condition', }, { type: 'doc', From c8bb82c03b3214f38679cf98fc17185d56434b94 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Tue, 13 Aug 2024 10:36:56 +0300 Subject: [PATCH 29/39] feat(docs/content): Create example doc for claiming a Foundry Output (#1724) * feat(docs/content): add doc example of claiming foundry output --- .../developer/stardust/claiming/foundry.mdx | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/content/developer/stardust/claiming/foundry.mdx b/docs/content/developer/stardust/claiming/foundry.mdx index 0e1ab19f38d..1ac9096f253 100644 --- a/docs/content/developer/stardust/claiming/foundry.mdx +++ b/docs/content/developer/stardust/claiming/foundry.mdx @@ -2,5 +2,78 @@ title: Claiming Foundry Outputs --- +As seen in the [Move Models](../move-models) page, the Foundry Output does not have a direct representation in Move. So claiming a Foundry Output actually means claiming a `CoinManagerTreasuryCap` extracted from the `AliasOutput` originally controlling the Foundry in Stardust. This capability can be used to manage the supply of the `Coin` created during the migration to represent the native token controlled by the Foundry. + ## Claim of a Foundry Output +1. The first step is to fetch an `AliasOutput` object that needs to be claimed. + + + + +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L147-L159 +``` + + + + + + + + +2. By using the dynamic field function with the "alias" dynamic field key filter, we gather the `Alias` object itself. + + + + +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L164-L174 +``` + + + + + + + +3. Some objects are owned by the `Alias` object (check the [Output unlockable by an Alias/Nft Address](address-unlock-condition.mdx) page for more info). In this case we filter them by type using the `CoinManagerTreasuryCap` type tag. + + + + +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L178-L216 +``` + + + + + + + +4. Since each native token has its own package, a Foundry's native token as a dedicated `OTW`. Here we need to extract this `OTW` from the `CoinManagerTreasuryCap` object. + + + + +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L220-L227 +``` + + + + + + + +5. Create a PTB that claims the `CoinManagerTreasuryCap` related to the Foundry Output from the `AliasOutput` using the `unlock_alias_address_owned_coinmanager_treasury` function. + + + + + +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L231-L300 +``` + + + + + + \ No newline at end of file From 2a9fb5f5527012a4470e6144251250bb7f14e481 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 13 Aug 2024 14:38:11 +0200 Subject: [PATCH 30/39] fix(docs/content): enhance claiming and fix links (#1768) --- docs/content/developer/stardust/claiming.mdx | 9 +++--- .../developer/stardust/claiming/alias.mdx | 2 +- .../developer/stardust/move-models.mdx | 28 +++++++++---------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/content/developer/stardust/claiming.mdx b/docs/content/developer/stardust/claiming.mdx index c97db73b75d..4c8b8384552 100644 --- a/docs/content/developer/stardust/claiming.mdx +++ b/docs/content/developer/stardust/claiming.mdx @@ -3,16 +3,15 @@ title: Claiming Stardust Assets description: Describes how to access the migrated assets and tokens --- - - Explains the resulting migrated state for simple coins (exchanges) - - Demonstrates through examples for each output type the resulting move objects - - Explains what commands need to be called in a PTB to claim the assets - - Describes transaction sponsorship from iota addresses for shimmer claiming +As detailed in the [Stardust Move Models](move-models.mdx), Stardust assets are represented as Move objects within the ledger. Claiming these assets involves enabling original owners to utilize a [Programmable Transaction Block](../iota-101/transactions/ptb/prog-txn-blocks.mdx) to "unlock" assets such as IOTA, Shimmer, custom [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin)s, or even `Alias` and `Nft` objects. -## IOTA tokens automatic migration +This process takes advantage of Move's unique features to ensure that assets are transferred and unlocked securely and efficiently to their rightful owners. ## Output types to Move Objects +Stardust assets come in the form of Outputs and each Output can be of a different type. For understanding how Outputs have been transformed into Move Objects based on their types, please refer to the [Stardust Move Models](move-models.mdx#summary) page. In here, one or more claiming examples for each Output type will be shown. + ## Examples of Stardust asset claim transactions diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx index bcafaae7c14..1601b298e23 100644 --- a/docs/content/developer/stardust/claiming/alias.mdx +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -4,7 +4,7 @@ An address can own `AliasOutput` objects only if before the migration it was set as the Alias Governor Address. In this case, the `AliasOutput` object is an owned object in the ledger and its owner is the Governor address. -Such address can be directly controlled by a user or by another object (either an `Alias` or `Nft` object). For the latter use case, check the [`Claiming an Output unlockable by an Alias/Nft Address`](address-uc.mdx) example. +Such address can be directly controlled by a user or by another object (either an `Alias` or `Nft` object). For the latter use case, check the [`Claiming an Output unlockable by an Alias/Nft Address`](address-unlock-condition.mdx) example. ## Claim of an Alias Output diff --git a/docs/content/developer/stardust/move-models.mdx b/docs/content/developer/stardust/move-models.mdx index 4d909294bb7..2b3a59879f6 100644 --- a/docs/content/developer/stardust/move-models.mdx +++ b/docs/content/developer/stardust/move-models.mdx @@ -7,6 +7,19 @@ description: Describes how stardust assets are represented in the object-based M This document describes what move models the Stardust UTXOs are migrated to in the Move-based ledger in IOTA Rebased. First, the Stardust Move Package is discussed that emulates the legacy UTXO models in object-based move. + ## Summary + +| Stardust Output (UTXO) | Move Container or Migrated Object | Encapsulated Move Assets Inside the Container | +|--------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Basic Output with IOTA/SMR only | [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) | ready to be used, no need to extract any assets | +| Basic Output with IOTA/SMR and native tokens | [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) and one or more [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) | ready to be used, no need to extract any assets | +| Basic Output with unlock conditions or features | [`BasicOutput`](../../references/framework/stardust/basic_output#resource-basicoutput) | [`Balance`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"` | +| Alias Output | [`AliasOutput`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"`, [`Alias`](../../references/framework/stardust/alias#resource-alias) object | +| Nft Output | [`NftOutput`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"`, [`Nft`](../../references/framework/stardust/nft#resource-nft) object | +| Foundry Output | Migrated to a Move Package with [template](https://github.com/iotaledger/iota/blob/develop/crates/iota-genesis-builder/src/stardust/native_token/package_template/sources/native_token_template.move) and [`CoinManager`](../../references/framework/iota-framework/coin_manager). Base tokens of the output are sent to the controlling alias as `Coin` objects. | - | +| [Vested Rewards From the Stardust Upgrade](https://github.com/iotaledger/new_supply) | [`Timelock>`](../../references/framework/iota-framework/timelock.mdx#resource-timelock) objects. | - | + + ## Design Principles - The migration to IOTA Rebased aims to move away from the Stardust-based ledger concepts and transform assets into their pure @@ -518,18 +531,3 @@ public fun extract_assets(output: nft_output::NftOutput, ctx: &mut tx_cont - The caller has to decide what address to send the `Nft` object to. - The `NftOutput` container is destroyed in the process. - ## Summary - - -| Stardust Output (UTXO) | Move Container or Migrated Object | Encapsulated Move Assets Inside the Container | -|--------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Basic Output with IOTA/SMR only | [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) | ready to be used, no need to extract any assets | -| Basic Output with IOTA/SMR and native tokens | [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) and one or more [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) | ready to be used, no need to extract any assets | -| Basic Output with unlock conditions or features | [`BasicOutput`](../../references/framework/stardust/basic_output#resource-basicoutput) | [`Balance`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"` | -| Alias Output | [`AliasOutput`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"`, [`Alias`](../../references/framework/stardust/alias#resource-alias) object | -| Nft Output | [`NftOutput`](../../references/framework/iota-framework/balance#struct-balance), [`Bag`](../../references/framework/iota-framework/bag#resource-bag) of `Balance` key-ed by `"NATIVE_TOKEN"`, [`Nft`](../../references/framework/stardust/nft#resource-nft) object | -| Foundry Output | Migrated to a Move Package with [template](https://github.com/iotaledger/iota/blob/develop/crates/iota-genesis-builder/src/stardust/native_token/package_template/sources/native_token_template.move) and [`CoinManager`](../../references/framework/iota-framework/coin_manager). Base tokens of the output are sent to the controlling alias as `Coin` objects. | - | -| [Vested Rewards From the Stardust Upgrade](https://github.com/iotaledger/new_supply) | [`Timelock>`](../../references/framework/iota-framework/timelock.mdx#resource-timelock) objects. | - | - - - From c0d46d12eb956d281520d6c0a1ac52766528aaee Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 14 Aug 2024 10:24:34 +0200 Subject: [PATCH 31/39] feat(docs/examples): Add self sponsor example for Shimmer assets (#1772) * feat(docs/examples): add self sponsor example * fix(docs/content): make self sponsor example for Shimmer * fix(docs/examples): smr comment * fix(docs/examples): dprint * fix(docs/examples): remove double comment Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * fix(docs/examples): client server comments --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- docs/examples/rust/Cargo.toml | 4 + .../rust/stardust/shimmer-self-sponsor.rs | 200 ++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 docs/examples/rust/stardust/shimmer-self-sponsor.rs diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 1b0eb3192ba..4dff5785e23 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -50,3 +50,7 @@ path = "stardust/nft-migration.rs" [[example]] name = "foundry-output-claim" path = "stardust/foundry-output-claim.rs" + +[[example]] +name = "shimmer-self-sponsor" +path = "stardust/shimmer-self-sponsor.rs" diff --git a/docs/examples/rust/stardust/shimmer-self-sponsor.rs b/docs/examples/rust/stardust/shimmer-self-sponsor.rs new file mode 100644 index 00000000000..1c8a8166245 --- /dev/null +++ b/docs/examples/rust/stardust/shimmer-self-sponsor.rs @@ -0,0 +1,200 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the self-sponsor scenario for claiming a Shimmer 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, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + smr_coin::SMR, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, + }, + IotaClientBuilder, +}; +use move_core_types::ident_str; +use shared_crypto::intent::Intent; + +pub const IOTA_COIN_TYPE: u32 = 4218; +pub const SHIMMER_COIN_TYPE: u32 = 4219; + +/// 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 { + 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 and coin_types, one for sponsoring with IOTA coin type and one for + // claiming the Basic Output with Shimmer coin type. + let sponsor_derivation_path = + DerivationPath::from_str(format!("m/44'/{IOTA_COIN_TYPE}'/0'/0'/5'").as_str())?; + let sender_derivation_path = + DerivationPath::from_str(format!("m/44'/{SHIMMER_COIN_TYPE}'/0'/0'/50'").as_str())?; + + // 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( + "0xbdc4dec75098700e8e82349d9f3a9f28dcd22d2b39f5fbdf8436b05430bc3690", + )?; + // 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 Shimmer network, i.e., the + // SMR coin + let type_arguments = vec![SMR::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., SMR) 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 SMR balance + // Type argument for the SMR coin + let type_arguments = vec![SMR::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, + ); + + // Sender signs the transaction + let sender_signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // 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() +} From f528ce04d06e577dbdf3214a4275a3019c16a986 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 14 Aug 2024 10:35:14 +0200 Subject: [PATCH 32/39] feat(docs/content): Add Shimmer self-sponsorship claim docs (#1773) * feat(docs/examples): add self sponsor example * fix(docs/content): make self sponsor example for Shimmer * fix(docs/examples): smr comment * fix(docs/examples): dprint * feat(docs/content): add shimmer self-sponsorship claim * fix(docs/content): spaces Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * fix(docs/content): code lines --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- .../stardust/claiming/self-sponsor.mdx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx index bc0c07a60ef..cbb8921c788 100644 --- a/docs/content/developer/stardust/claiming/self-sponsor.mdx +++ b/docs/content/developer/stardust/claiming/self-sponsor.mdx @@ -2,4 +2,58 @@ title: Self-sponsoring Shimmer Assets Claiming --- +In the case in which an address owns some assets but no IOTA coins, the self-sponsorship can be of help for claiming those coins. +[Sponsoring a transaction](../../iota-101/transactions/sponsored-transactions) means having an IOTA address (i.e., the sponsor) to pay the transaction gas fees for another address (i.e., the user). In the case of a claim a sponsor can pay for the claiming transaction gas. +This is useful for Shimmer assets, because none of the Move objects obtained from migrating the Shimmer Stardust Outputs contain any IOTA coin. It means that addresses owning these Objects have no IOTA coins to pay for gas. + +## Claim of a Shimmer Basic Output with self-sponsorship + +1. A Shimmer derivation path uses a `coin_type` (4219) which is different from the IOTA's one (4218). A user's self-sponsoring address could be then found using the IOTA `coin_type`. + + + + +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L61-L67 +``` + + + + +TODO + + + + + +2. A PTB for claiming a `BasicOutput` owned by the derived Shimmer address can be created similarly to what is shown in [Basic Output](basic.mdx). + + + + +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L103-L152 +``` + + + + +TODO + + + + +3. In this case, the transaction must be signed by both the sender address (i.e., the objects' owner) and the sponsor address. + + + + +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L168-L194 +``` + + + + +TODO + + + \ No newline at end of file From fb392c42bef97254143faca7bcdbe89f2ba9f631 Mon Sep 17 00:00:00 2001 From: Pavlo Botnar Date: Wed, 14 Aug 2024 12:33:18 +0300 Subject: [PATCH 33/39] fix(docs/content): improvements of docs navigation by adding links (#1774) * fix(docs/content): improvements of docs navigation by adding links * fix(docs/content): add additional links, readable improvements. * fix(docs/content/../claiming): simplify navigation with additional links for OTW, Bag, Coin, Unlock Condition --- .../claiming/address-unlock-condition.mdx | 2 +- .../developer/stardust/claiming/alias.mdx | 4 ++-- .../developer/stardust/claiming/basic.mdx | 2 +- .../developer/stardust/claiming/foundry.mdx | 4 ++-- .../developer/stardust/claiming/nft.mdx | 2 +- docs/content/developer/stardust/exchanges.mdx | 4 ++-- .../developer/stardust/migration-process.mdx | 4 ++-- .../developer/stardust/move-models.mdx | 24 +++++++++---------- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/content/developer/stardust/claiming/address-unlock-condition.mdx b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx index 6a669061681..68371525101 100644 --- a/docs/content/developer/stardust/claiming/address-unlock-condition.mdx +++ b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx @@ -2,7 +2,7 @@ title: Claiming an Output unlockable by an Alias/Nft Address --- -In Stardust outputs presented an Address Unlock Condition or similarly, in the case of the Alias Output, a Governor Address Unlock Condition. In the new ledger, this mechanism is represented as an address owning the associated Output object. Most of the times the address is directly managed through a keypair by a user, but sometimes this address could represent another object. In this case, that object owns the interested Output object. Coming from the Stardust migration, only `Alias` and `Nft` objects can own other Output objects. +In Stardust outputs presented an Address Unlock Condition or similarly, in the case of the Alias Output, a [Governor Address Unlock Condition](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#governor-address-unlock-condition). In the new ledger, this mechanism is represented as an address owning the associated Output object. Most of the times the address is directly managed through a keypair by a user, but sometimes this address could represent another object. In this case, that object owns the interested Output object. Coming from the [Stardust migration](../migration-process.mdx#stardust-migration), only `Alias` and `Nft` objects can own other Output objects. ## Claim of an Output owned by another Alias/Nft object diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx index 1601b298e23..7e39ec47db1 100644 --- a/docs/content/developer/stardust/claiming/alias.mdx +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -26,8 +26,8 @@ A Governor address can claim the `AliasOutput` assets at any time: 2. Then we check the native tokens that were possibly held by this output. -A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are bag indexes. -In the case of the native tokens, the keys are strings representing the `OTW` used for the native token declaration. +A [`Bag`](../../../references/framework/iota-framework/bag) is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are bag indexes. +In the case of the native tokens, the keys are strings representing the [`OTW`](../../iota-101/iota-move-concepts/one-time-witness.mdx) used for the native token declaration. diff --git a/docs/content/developer/stardust/claiming/basic.mdx b/docs/content/developer/stardust/claiming/basic.mdx index c7577a6335e..0c75c948d91 100644 --- a/docs/content/developer/stardust/claiming/basic.mdx +++ b/docs/content/developer/stardust/claiming/basic.mdx @@ -38,7 +38,7 @@ TODO -2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. +2. Then we check the native tokens that were possibly held by this output. A [`Bag`](../../../references/framework/iota-framework/bag) is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the [`OTW`](../../iota-101/iota-move-concepts/one-time-witness.mdx) used for the native token [`Coin`](../../../references/framework/iota-framework/coin.mdx#resource-coin). diff --git a/docs/content/developer/stardust/claiming/foundry.mdx b/docs/content/developer/stardust/claiming/foundry.mdx index 1ac9096f253..c65006df758 100644 --- a/docs/content/developer/stardust/claiming/foundry.mdx +++ b/docs/content/developer/stardust/claiming/foundry.mdx @@ -2,7 +2,7 @@ title: Claiming Foundry Outputs --- -As seen in the [Move Models](../move-models) page, the Foundry Output does not have a direct representation in Move. So claiming a Foundry Output actually means claiming a `CoinManagerTreasuryCap` extracted from the `AliasOutput` originally controlling the Foundry in Stardust. This capability can be used to manage the supply of the `Coin` created during the migration to represent the native token controlled by the Foundry. +As seen in the [Move Models](../move-models) page, the Foundry Output does not have a direct representation in Move. So claiming a Foundry Output actually means claiming a [`CoinManagerTreasuryCap`](../../../references/framework/iota-framework/coin_manager.mdx#resource-coinmanagertreasurycap) extracted from the [`AliasOutput`](../../../references/framework/stardust/alias_output#resource-aliasoutput) originally controlling the Foundry in Stardust. This capability can be used to manage the supply of the [`Coin`](../../../references/framework/iota-framework/coin.mdx#resource-coin) created during the migration to represent the native token controlled by the Foundry. ## Claim of a Foundry Output @@ -49,7 +49,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -4. Since each native token has its own package, a Foundry's native token as a dedicated `OTW`. Here we need to extract this `OTW` from the `CoinManagerTreasuryCap` object. +4. Since each native token has its own package, a Foundry's native token as a dedicated [`OTW`](../../iota-101/iota-move-concepts/one-time-witness.mdx). Here we need to extract this `OTW` from the `CoinManagerTreasuryCap` object. diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx index 0dab8f6bc5b..f4c8128f2c7 100644 --- a/docs/content/developer/stardust/claiming/nft.mdx +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -24,7 +24,7 @@ Once an Nft Output can be unlocked the claim of its assets can start -2. Then we check the native tokens that were possibly held by this output. A `Bag` is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the `OTW` used for the native token `Coin`. +2. Then we check the native tokens that were possibly held by this output. A [`Bag`](../../../references/framework/iota-framework/bag) is used for holding these tokens, so in this step we are interested in obtaining the dynamic field keys that are used as bag index. In the case of the native tokens `Bag` the keys are strings representing the [`OTW`](../../iota-101/iota-move-concepts/one-time-witness.mdx) used for the native token [`Coin`](../../../references/framework/iota-framework/coin.mdx#resource-coin). diff --git a/docs/content/developer/stardust/exchanges.mdx b/docs/content/developer/stardust/exchanges.mdx index 8e0a72587b8..e90107ddd52 100644 --- a/docs/content/developer/stardust/exchanges.mdx +++ b/docs/content/developer/stardust/exchanges.mdx @@ -38,7 +38,7 @@ The most common use case for exchanges and custody providers regarding integrati - All `Coin` objects can be freely transferred by the owner of this object. - Only the owner of the `Coin` object can interact with it. -- The only restriction that can be added to a `Coin` object is the optional blocking of transfers for addresses on a `DenyList`. This only applies to `Coin` objects that have been instantiated as [Regulated Coins](../standards/coin.mdx#regulated-coins). +- The only restriction that can be added to a `Coin` object is the optional blocking of transfers for addresses on a [`DenyList`](../iota-101/create-coin/regulated.mdx#deny-list). This only applies to `Coin` objects that have been instantiated as [Regulated Coins](../standards/coin.mdx#regulated-coins). * It is not possible to add other limiting functionality to `Coin` objects directly, like vesting or time-locks; this can only be done by wrapping the unrestricted `Coin` object within another restricting object. It's safe to assume that if you receive a `Coin` object, you can use it without limitations. - A `Coin` is tied to a `CoinMetadata` object containing `name`, `symbol`, `decimals`, `description`, and an `icon_url` - The holder of the TreasuryCap handles administrative tasks of a Coin like minting new tokens or changing metadata; Without a `TreasuryCap` these actions can no longer be performed. @@ -94,4 +94,4 @@ Gas fees to interact with custom tokens are paid in IOTA, like any other interac #### Integration -Depending on your preferences, you can integrate your exchange in multiple ways. You can go from a low-level implementation to directly talking to a node's RPC server or use one of our SDKs (the official TypeScript or Rust SDK is recommended). For details on how to integrate, please check out the [Exchange Integration Guide](../exchange-integration/exchange-integration.mdx). \ No newline at end of file +Depending on your preferences, you can integrate your exchange in multiple ways. You can go from a low-level implementation to directly talking to a node's RPC server or use one of our [SDKs](../../references/iota-sdks) (the official TypeScript or Rust SDK is recommended). For details on how to integrate, please check out the [Exchange Integration Guide](../exchange-integration/exchange-integration.mdx). \ No newline at end of file diff --git a/docs/content/developer/stardust/migration-process.mdx b/docs/content/developer/stardust/migration-process.mdx index cb1455fbf8e..65849b79f6f 100644 --- a/docs/content/developer/stardust/migration-process.mdx +++ b/docs/content/developer/stardust/migration-process.mdx @@ -28,7 +28,7 @@ migration. They will only be created in the genesis ledger state via the migrati ## Foundry Outputs & Native Tokens -Foundry Outputs in Stardust represent the capability to control the supply of user-defined Native Tokens. In Move, there +[Foundry Outputs](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#foundry-output) in Stardust represent the capability to control the supply of user-defined Native Tokens. In Move, there are established ways for these operations, in particular using [`Coin`](../../references/framework/iota-framework/coin.mdx#resource-coin) and [`TreasuryCap`](../../references/framework/iota-framework/coin.mdx#resource-treasurycap). The two main goals of the @@ -54,7 +54,7 @@ The result of the foundry migration is the following: - A package representing the native token, in particular containing a one-time witness representing the unique type of the native token (to be used as `Coin`; abbreviated in the rest of this document). -- A `CoinManager` and `CoinManagerTreasuryCap` object, the latter of which is owned by the address of the alias that +- A `CoinManager` and [`CoinManagerTreasuryCap`](../../references/framework/iota-framework/coin_manager.mdx#resource-coinmanagertreasurycap) object, the latter of which is owned by the address of the alias that owned the original foundry. - A minted coin (`Coin`) containing the entire circulating supply of the native tokens owned by the `0x0` address. diff --git a/docs/content/developer/stardust/move-models.mdx b/docs/content/developer/stardust/move-models.mdx index 2b3a59879f6..b573792f87c 100644 --- a/docs/content/developer/stardust/move-models.mdx +++ b/docs/content/developer/stardust/move-models.mdx @@ -65,10 +65,10 @@ The following sections describe how the different asset types in Stardust are re You may find more information on the original Stardust ledger and asset types in [TIP-18](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md). We consider the following Stardust asset types for the migration: - - Base tokens (IOTA/SMR), - - User defined native tokens, - - Non-Fungible Tokens (NFTs), and - - Aliases + - Base tokens (IOTA/SMR). + - User defined native tokens. + - Non-Fungible Tokens (NFTs). + - Aliases. ### Base Token (IOTA/SMR) @@ -77,7 +77,7 @@ represented instead as balance structs, parameterized with the type of the token - [`Balance`](../../references/framework/iota-framework/balance#struct-balance): Represents a balance of the IOTA token. - [`Balance`](../../references/framework/iota-framework/balance#struct-balance): Represents a balance the SMR token. - The token types `IOTA` and `SMR` are defined as part of the IOTA Move Framework. The fully qualified type for them is: - - [`0x2::iota::IOTA`](../../references/framework/iota-framework/iota#struct-iota) for IOTA, and + - [`0x2::iota::IOTA`](../../references/framework/iota-framework/iota#struct-iota) for IOTA. - [`0x2::smr::SMR`](../../references/framework/iota-framework/smr#struct-smr) for SMR. Balances are simply structs in move, therefore they need to be put into an object that users can own. The IOTA Move Framework @@ -103,8 +103,8 @@ Majority of the funds on both IOTA and SMR networks are held in basic outputs, w as this is the most common way a wallet stores IOTA/SMR tokens. To make the migration as seamless as possible, all [Basic outputs](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#basic-output) from the Stardust ledger that: - contain IOTA or SMR tokens only (no native tokens), - - have only an `Address Unlock Condition` and no other [unlock conditions](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#unlock-conditions), and - - have no [output features](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#features), + - have only an `Address Unlock Condition` and no other [unlock conditions](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#unlock-conditions), + - have no [output features](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#features) will be migrated to a `Coin` or `Coin` object, respectively. The `Coin` object will be owned by the address in the `Address Unlock Condition` of the Stardust output. @@ -268,7 +268,7 @@ can be touched by anyone, however the move level code ensures that only the righ ### Deprecated Stardust Unlock Conditions The following unlock conditions from Stardust are not supported in the move ledger and are therefore not migrated: - - [State Controller Address Unlock Condition](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#state-controller-address-unlock-condition) and + - [State Controller Address Unlock Condition](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#state-controller-address-unlock-condition). - [Governor Address Unlock Condition](https://github.com/iotaledger/tips/blob/main/tips/TIP-0018/tip-0018.md#governor-address-unlock-condition): Used in Stardust exclusively for aliases. The move ledger simplifies aliases and gives ownership to the governor address of the alias, as the governor has the right to replace any state controller. @@ -374,8 +374,8 @@ There are two ways to interact with `BasicOutputs` in move: The [`AliasOutput`](../../references/framework/stardust/alias_output#resource-aliasoutput) container contains the following assets: - - Base token balance (IOTA/SMR) of the migrated Stardust output, - - A `Bag` that holds native tokens of the migrated Stardust output, and + - Base token balance (IOTA/SMR) of the migrated Stardust output. + - A `Bag` that holds native tokens of the migrated Stardust output. - The [`Alias`](../../references/framework/stardust/alias#resource-alias) object that represents the alias in the move ledger. While this latter asset is not part of the container as a field, it is added to it as a [`dynamic object field`](../iota-101/objects/dynamic-fields/dynamic-fields.mdx) during the migration process. This has the added benefit that the previous `AliasID` of the Stardust output is preserved @@ -451,8 +451,8 @@ public fun extract_assets(mut output: AliasOutput): (Balance, Bag, Alia ### NftOutput Move Model The [`NftOutput`](../../references/framework/stardust/nft_output#resource-nftoutput) container contains the: - - Base token balance (IOTA/SMR) of the migrated Stardust output, - - A `Bag` that holds native tokens of the migrated Stardust output, and + - Base token balance (IOTA/SMR) of the migrated Stardust output. + - A `Bag` that holds native tokens of the migrated Stardust output. - The [`Nft`](../../references/framework/stardust/nft#resource-nft) object that represents the NFT in the move ledger. While this latter asset is not part of the container as a field, it is added to it as a [`dynamic object field`](../iota-101/objects/dynamic-fields/dynamic-fields.mdx) during the migration process. This has the added benefit that the previous `NftID` of the Stardust output is preserved From 3a4555546ca117edc89de6d9c1bafad9d2ccde28 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 14 Aug 2024 18:19:04 +0200 Subject: [PATCH 34/39] refactor(docs/examples): Create a utility methods lib (#1821) * refactor(docs/examples): create a utility methods lib * refactor(docs/examples): update examples * refactor(docs/content): update examples code lines in docs * fix(docs/examples): move module path * fix(docs/examples): address uc name * Update docs/examples/rust/src/lib.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * Update docs/content/developer/stardust/claiming/nft.mdx Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * fix(docs/examples): move utils out of lib --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- .../claiming/address-unlock-condition.mdx | 8 +- .../developer/stardust/claiming/alias.mdx | 10 +- .../developer/stardust/claiming/basic.mdx | 6 +- .../developer/stardust/claiming/foundry.mdx | 10 +- .../developer/stardust/claiming/nft.mdx | 26 +-- .../stardust/claiming/self-sponsor.mdx | 6 +- docs/examples/rust/Cargo.toml | 17 +- docs/examples/rust/src/lib.rs | 4 + docs/examples/rust/src/utils.rs | 194 ++++++++++++++++++ ...ndition.rs => address-unlock-condition.rs} | 92 +-------- .../examples/rust/stardust/alias-migration.rs | 118 +---------- .../rust/stardust/alias-output-claim.rs | 24 +-- .../rust/stardust/basic-output-claim.rs | 24 +-- .../rust/stardust/foundry-output-claim.rs | 93 +-------- docs/examples/rust/stardust/nft-migration.rs | 117 +---------- .../rust/stardust/nft-output-claim.rs | 24 +-- .../rust/stardust/shimmer-self-sponsor.rs | 23 +-- 17 files changed, 271 insertions(+), 525 deletions(-) create mode 100644 docs/examples/rust/src/lib.rs create mode 100644 docs/examples/rust/src/utils.rs rename docs/examples/rust/stardust/{address_unlock_condition.rs => address-unlock-condition.rs} (78%) diff --git a/docs/content/developer/stardust/claiming/address-unlock-condition.mdx b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx index 68371525101..f61a5f737fa 100644 --- a/docs/content/developer/stardust/claiming/address-unlock-condition.mdx +++ b/docs/content/developer/stardust/claiming/address-unlock-condition.mdx @@ -13,7 +13,7 @@ For this example, we're using an `AliasOutput` to extract an `Alias` object that -```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L74-L90 +```rust file=/docs/examples/rust/stardust/address-unlock-condition.rs#L70-L88 ``` @@ -27,7 +27,7 @@ For this example, we're using an `AliasOutput` to extract an `Alias` object that -```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L95-L105 +```rust file=/docs/examples/rust/stardust/address-unlock-condition.rs#L90-L103 ``` @@ -42,7 +42,7 @@ Applying the filter to get `NftOutput`s owned by the `Alias`. -```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L109-L130 +```rust file=/docs/examples/rust/stardust/address-unlock-condition.rs#L105-L128 ``` @@ -56,7 +56,7 @@ Applying the filter to get `NftOutput`s owned by the `Alias`. -```rust file=/docs/examples/rust/stardust/address_unlock_condition.rs#L132-L241 +```rust file=/docs/examples/rust/stardust/address-unlock-condition.rs#L130-L239 ``` diff --git a/docs/content/developer/stardust/claiming/alias.mdx b/docs/content/developer/stardust/claiming/alias.mdx index 7e39ec47db1..16c03a6c89a 100644 --- a/docs/content/developer/stardust/claiming/alias.mdx +++ b/docs/content/developer/stardust/claiming/alias.mdx @@ -15,7 +15,7 @@ A Governor address can claim the `AliasOutput` assets at any time: -```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L74-L99 +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L56-L81 ``` @@ -32,7 +32,7 @@ In the case of the native tokens, the keys are strings representing the [`OTW`]( -```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L101-L128 +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L83-L110 ``` @@ -48,7 +48,7 @@ In fact, the main purpose of claiming is extracting the `Alias` object from the -```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L130-L198 +```rust file=/docs/examples/rust/stardust/alias-output-claim.rs#L112-L180 ``` @@ -80,9 +80,7 @@ Also, the `nft.move` module was extended with the following function: ``` - - Once the package is prepared, we can extract and use a Stardust `Alias` in a single transaction to create a `CollectionControllerCap`. @@ -91,7 +89,7 @@ This capability is then used in later transactions for managing new collections. -```rust file=/docs/examples/rust/stardust/alias-migration.rs#L122-L247 +```rust file=/docs/examples/rust/stardust/alias-migration.rs#L119-L244 ``` diff --git a/docs/content/developer/stardust/claiming/basic.mdx b/docs/content/developer/stardust/claiming/basic.mdx index 0c75c948d91..f0bdf636bed 100644 --- a/docs/content/developer/stardust/claiming/basic.mdx +++ b/docs/content/developer/stardust/claiming/basic.mdx @@ -26,7 +26,7 @@ Once a Basic Output can be unlocked the claim of its assets can start -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L74-L99 +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L56-L81 ``` @@ -43,7 +43,7 @@ TODO -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L101-L128 +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L83-L110 ``` @@ -59,7 +59,7 @@ TODO -```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L131-L195 +```rust file=/docs/examples/rust/stardust/basic-output-claim.rs#L113-L177 ``` diff --git a/docs/content/developer/stardust/claiming/foundry.mdx b/docs/content/developer/stardust/claiming/foundry.mdx index c65006df758..8826672693b 100644 --- a/docs/content/developer/stardust/claiming/foundry.mdx +++ b/docs/content/developer/stardust/claiming/foundry.mdx @@ -11,7 +11,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L147-L159 +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L60-L74 ``` @@ -26,7 +26,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L164-L174 +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L76-L89 ``` @@ -40,7 +40,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L178-L216 +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L91-L131 ``` @@ -54,7 +54,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L220-L227 +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L133-L142 ``` @@ -69,7 +69,7 @@ As seen in the [Move Models](../move-models) page, the Foundry Output does not h -```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L231-L300 +```rust file=/docs/examples/rust/stardust/foundry-output-claim.rs#L144-L215 ``` diff --git a/docs/content/developer/stardust/claiming/nft.mdx b/docs/content/developer/stardust/claiming/nft.mdx index f4c8128f2c7..875cc5fbbb9 100644 --- a/docs/content/developer/stardust/claiming/nft.mdx +++ b/docs/content/developer/stardust/claiming/nft.mdx @@ -14,7 +14,7 @@ Once an Nft Output can be unlocked the claim of its assets can start -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L54-L78 +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L56-L80 ``` @@ -29,7 +29,7 @@ Once an Nft Output can be unlocked the claim of its assets can start -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L80-L105 +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L82-L107 ``` @@ -45,7 +45,7 @@ In fact, the main purpose of claiming is extracting the `Nft` object from the `N -```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L107-L173 +```rust file=/docs/examples/rust/stardust/nft-output-claim.rs#L109-L175 ``` @@ -67,33 +67,17 @@ The following is an example of a simple module for representing a custom NFT, mi ``` - - -1. Publish a custom Nft package, and retrieve its package ID. - - - - -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L210-L227 -``` - - - - - - - -2. Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then calls the `custom_nft::nft::convert` method for converting it into a `custom_nft::nft::Nft` of the collection just published. +Create a PTB that extracts the Stardust `Nft` from an `NftOutput` and then converts it into a custom NFT of the collection just published. This conversion method extracts the Stardust `Nft` metadata and uses it for minting a new NFT. -```rust file=/docs/examples/rust/stardust/nft-migration.rs#L84-L141 +```rust file=/docs/examples/rust/stardust/nft-migration.rs#L77-L137 ``` diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx index cbb8921c788..8ecd7374180 100644 --- a/docs/content/developer/stardust/claiming/self-sponsor.mdx +++ b/docs/content/developer/stardust/claiming/self-sponsor.mdx @@ -14,7 +14,7 @@ This is useful for Shimmer assets, because none of the Move objects obtained fro -```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L61-L67 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L44-L50 ``` @@ -31,7 +31,7 @@ TODO -```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L103-L152 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L85-L135 ``` @@ -47,7 +47,7 @@ TODO -```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L168-L194 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L151-L177 ``` diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 4dff5785e23..f4741516b5f 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -6,22 +6,25 @@ edition = "2021" license = "Apache-2.0" publish = false -[dev-dependencies] +[dependencies] anyhow.workspace = true + +iota-keys.workspace = true +iota-move-build.workspace = true +iota-sdk.workspace = true +shared-crypto.workspace = true + +[dev-dependencies] bcs.workspace = true bip32.workspace = true serde_json.workspace = true tokio.workspace = true -iota-keys.workspace = true -iota-move-build.workspace = true -iota-sdk.workspace = true move-core-types.workspace = true -shared-crypto.workspace = true [[example]] -name = "address_unlock_condition" -path = "stardust/address_unlock_condition.rs" +name = "address-unlock-condition" +path = "stardust/address-unlock-condition.rs" [[example]] name = "nft-output-claim" diff --git a/docs/examples/rust/src/lib.rs b/docs/examples/rust/src/lib.rs new file mode 100644 index 00000000000..0d76a1a1e6e --- /dev/null +++ b/docs/examples/rust/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +pub mod utils; diff --git a/docs/examples/rust/src/utils.rs b/docs/examples/rust/src/utils.rs new file mode 100644 index 00000000000..b8eb717c629 --- /dev/null +++ b/docs/examples/rust/src/utils.rs @@ -0,0 +1,194 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! A set of utility functions for the examples. + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use anyhow::{anyhow, Result}; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_move_build::BuildConfig; +use iota_sdk::{ + rpc_types::{IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponseOptions}, + types::{ + base_types::{IotaAddress, ObjectID}, + crypto::SignatureScheme::ED25519, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + transaction::{Transaction, TransactionData}, + }, + IotaClient, +}; +use shared_crypto::intent::Intent; + +/// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs +const SPONSOR_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; + +/// Move Custom NFT example relative path +const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; + +/// Creates a temporary keystore. +pub fn setup_keystore() -> Result { + 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) +} + +/// Deletes the temporary keystore. +pub fn clean_keystore() -> Result<(), anyhow::Error> { + // Remove files + fs::remove_file("iotatempdb")?; + fs::remove_file("iotatempdb.aliases")?; + Ok(()) +} + +/// Utility function for funding an address using the transfer of a coin. +pub async fn fund_address( + iota_client: &IotaClient, + keystore: &mut FileBasedKeystore, + recipient: IotaAddress, +) -> Result<(), anyhow::Error> { + // Derive the address of the sponsor. + let sponsor = keystore.import_from_mnemonic(SPONSOR_ADDRESS_MNEMONIC, ED25519, None)?; + + println!("Sponsor address: {sponsor:?}"); + + // 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"))?; + + let pt = { + // Init a programmable transaction builder. + let mut builder = ProgrammableTransactionBuilder::new(); + // Pay all iotas from the gas object + builder.pay_all_iota(recipient); + builder.finish() + }; + + // Setup a gas budget and a gas price. + let gas_budget = 10_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Create a transaction data that will be sent to the network. + let tx_data = TransactionData::new_programmable( + sponsor, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction. + let signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; + + // Execute the transaction. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Funding transaction digest: {}", + transaction_response.digest + ); + + Ok(()) +} + +/// Utility function for publishing a custom NFT package found in the Move +/// examples. +pub async fn publish_custom_nft_package( + sender: IotaAddress, + keystore: &mut FileBasedKeystore, + iota_client: &IotaClient, +) -> Result { + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sender, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found"))?; + + // Build custom nft package + let package_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(CUSTOM_NFT_PACKAGE_PATH); + let compiled_package = BuildConfig::default().build(package_path)?; + let modules = compiled_package + .get_modules() + .map(|module| { + let mut buf = Vec::new(); + module.serialize(&mut buf)?; + Ok(buf) + }) + .collect::>>>()?; + let dependencies = compiled_package.get_dependency_original_package_ids(); + + // Publish package + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + builder.publish_immutable(modules, dependencies); + 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?; + + // Create the transaction data that will be sent to the network + let tx_data = TransactionData::new_programmable( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + ); + + // Sign the transaction + let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Execute transaction + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!( + "Package publishing transaction digest: {}", + transaction_response.digest + ); + + // Extract package id from the transaction effects + let tx_effects = transaction_response + .effects + .expect("Transaction has no effects"); + let package_ref = tx_effects + .created() + .first() + .expect("There are no created objects"); + let package_id = package_ref.reference.object_id; + println!("Package ID: {package_id}"); + Ok(package_id) +} diff --git a/docs/examples/rust/stardust/address_unlock_condition.rs b/docs/examples/rust/stardust/address-unlock-condition.rs similarity index 78% rename from docs/examples/rust/stardust/address_unlock_condition.rs rename to docs/examples/rust/stardust/address-unlock-condition.rs index 914ecf057a5..e3383b79de0 100644 --- a/docs/examples/rust/stardust/address_unlock_condition.rs +++ b/docs/examples/rust/stardust/address-unlock-condition.rs @@ -5,18 +5,19 @@ //! 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 std::str::FromStr; use anyhow::anyhow; use bip32::DerivationPath; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, fund_address, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{ IotaObjectDataFilter, IotaObjectDataOptions, IotaObjectResponseQuery, IotaTransactionBlockResponseOptions, }, types::{ - base_types::{IotaAddress, ObjectID}, + base_types::ObjectID, crypto::SignatureScheme::ED25519, dynamic_field::DynamicFieldName, gas_coin::GAS, @@ -26,14 +27,11 @@ use iota_sdk::{ transaction::{Argument, ObjectArg, Transaction, TransactionData}, TypeTag, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, }, - IotaClient, IotaClientBuilder, + 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 SPONSOR_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; - /// Got from iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs const MAIN_ADDRESS_MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car"; @@ -271,83 +269,3 @@ async fn main() -> Result<(), anyhow::Error> { // Finish and clean the temporary keystore file clean_keystore() } - -fn setup_keystore() -> Result { - // 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(()) -} - -async fn fund_address( - iota_client: &IotaClient, - keystore: &mut FileBasedKeystore, - recipient: IotaAddress, -) -> Result<(), anyhow::Error> { - // Derive the address of the sponsor. - let sponsor = keystore.import_from_mnemonic(SPONSOR_ADDRESS_MNEMONIC, ED25519, None)?; - - println!("Sponsor address: {sponsor:?}"); - - // 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"))?; - - let pt = { - // Init a programmable transaction builder. - let mut builder = ProgrammableTransactionBuilder::new(); - // Pay all iotas from the gas object - builder.pay_all_iota(recipient); - builder.finish() - }; - - // Setup a gas budget and a gas price. - let gas_budget = 10_000_000; - let gas_price = iota_client.read_api().get_reference_gas_price().await?; - - // Create a transaction data that will be sent to the network. - let tx_data = TransactionData::new_programmable( - sponsor, - vec![gas_coin.object_ref()], - pt, - gas_budget, - gas_price, - ); - - // Sign the transaction. - let signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; - - // Execute the transaction. - let transaction_response = iota_client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(tx_data, vec![signature]), - IotaTransactionBlockResponseOptions::full_content(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - println!( - "Funding transaction digest: {}", - transaction_response.digest - ); - - Ok(()) -} diff --git a/docs/examples/rust/stardust/alias-migration.rs b/docs/examples/rust/stardust/alias-migration.rs index 562ab20f89b..035c6de6090 100644 --- a/docs/examples/rust/stardust/alias-migration.rs +++ b/docs/examples/rust/stardust/alias-migration.rs @@ -6,18 +6,15 @@ //! with test objects generated from //! iota-genesis-builder/src/stardust/test_outputs. -use std::{fs, path::PathBuf, str::FromStr}; +use std::str::FromStr; use anyhow::{anyhow, Result}; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; -use iota_move_build::BuildConfig; +use docs_examples::utils::{clean_keystore, publish_custom_nft_package, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ - rpc_types::{ - IotaData, IotaObjectDataOptions, IotaTransactionBlockEffectsAPI, - IotaTransactionBlockResponseOptions, - }, + rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ - base_types::{IotaAddress, ObjectID}, + base_types::ObjectID, crypto::SignatureScheme::ED25519, gas_coin::GAS, programmable_transaction_builder::ProgrammableTransactionBuilder, @@ -26,14 +23,13 @@ use iota_sdk::{ transaction::{Argument, CallArg, ObjectArg, Transaction, TransactionData}, TypeTag, IOTA_FRAMEWORK_PACKAGE_ID, STARDUST_PACKAGE_ID, }, - IotaClient, IotaClientBuilder, + 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; -const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { @@ -49,9 +45,10 @@ async fn main() -> Result<(), anyhow::Error> { println!("{sender:?}"); // Publish the package of a custom NFT collection and then get the package id. + // The custom NFT module is obtained from a Move example in the docs. + // It is the same used in the Nft migration example. let custom_nft_package_id = - publish_custom_nft_package(sender, &mut keystore, &iota_client, CUSTOM_NFT_PACKAGE_PATH) - .await?; + publish_custom_nft_package(sender, &mut keystore, &iota_client).await?; // Get a gas coin let gas_coin = iota_client @@ -277,100 +274,3 @@ async fn main() -> Result<(), anyhow::Error> { // Finish and clean the temporary keystore file clean_keystore() } - -fn setup_keystore() -> Result { - // 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(()) -} - -async fn publish_custom_nft_package( - sender: IotaAddress, - keystore: &mut FileBasedKeystore, - iota_client: &IotaClient, - package_path: &str, -) -> Result { - // Get a gas coin - let gas_coin = iota_client - .coin_read_api() - .get_coins(sender, None, None, None) - .await? - .data - .into_iter() - .next() - .ok_or(anyhow!("No coins found"))?; - - // Build custom nft package - let compiled_package = BuildConfig::default().build(package_path.into())?; - let modules = compiled_package - .get_modules() - .map(|module| { - let mut buf = Vec::new(); - module.serialize(&mut buf)?; - Ok(buf) - }) - .collect::>>>()?; - let dependencies = compiled_package.get_dependency_original_package_ids(); - - // Publish package - let pt = { - let mut builder = ProgrammableTransactionBuilder::new(); - builder.publish_immutable(modules, dependencies); - 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?; - - // Create the transaction data that will be sent to the network - let tx_data = TransactionData::new_programmable( - sender, - vec![gas_coin.object_ref()], - pt, - gas_budget, - gas_price, - ); - - // Sign the transaction - let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; - - // Execute transaction - let transaction_response = iota_client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(tx_data, vec![signature]), - IotaTransactionBlockResponseOptions::full_content(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - println!( - "Package publishing transaction digest: {}", - transaction_response.digest - ); - - // Extract package id from the transaction effects - let tx_effects = transaction_response - .effects - .expect("Transaction has no effects"); - let package_ref = tx_effects - .created() - .first() - .expect("There are no created objects"); - let package_id = package_ref.reference.object_id; - println!("Package ID: {}", package_id); - Ok(package_id) -} diff --git a/docs/examples/rust/stardust/alias-output-claim.rs b/docs/examples/rust/stardust/alias-output-claim.rs index f2a710e797e..d8613caf3c0 100644 --- a/docs/examples/rust/stardust/alias-output-claim.rs +++ b/docs/examples/rust/stardust/alias-output-claim.rs @@ -5,10 +5,11 @@ //! 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 std::str::FromStr; use anyhow::anyhow; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ @@ -29,25 +30,6 @@ use shared_crypto::intent::Intent; /// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs const MAIN_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; -/// Creates a temporary keystore. -fn setup_keystore() -> Result { - // Create a temporary keystore. - let keystore_path = PathBuf::from("iotatempdb"); - if !keystore_path.exists() { - let keystore = FileBasedKeystore::new(&keystore_path)?; - keystore.save()?; - } - // Read the iota keystore. - FileBasedKeystore::new(&keystore_path) -} - -fn clean_keystore() -> Result<(), anyhow::Error> { - // Remove the keystore 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. diff --git a/docs/examples/rust/stardust/basic-output-claim.rs b/docs/examples/rust/stardust/basic-output-claim.rs index 38dd8766965..eaf0aecd916 100644 --- a/docs/examples/rust/stardust/basic-output-claim.rs +++ b/docs/examples/rust/stardust/basic-output-claim.rs @@ -5,10 +5,11 @@ //! 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 std::str::FromStr; use anyhow::anyhow; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ @@ -29,25 +30,6 @@ use shared_crypto::intent::Intent; /// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs const MAIN_ADDRESS_MNEMONIC: &str = "rain flip mad lamp owner siren tower buddy wolf shy tray exit glad come dry tent they pond wrist web cliff mixed seek drum"; -/// Creates a temporary keystore -fn setup_keystore() -> Result { - // 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 diff --git a/docs/examples/rust/stardust/foundry-output-claim.rs b/docs/examples/rust/stardust/foundry-output-claim.rs index 3c9fc0880c8..826cd6ea46d 100644 --- a/docs/examples/rust/stardust/foundry-output-claim.rs +++ b/docs/examples/rust/stardust/foundry-output-claim.rs @@ -5,16 +5,15 @@ //! foundry 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}; - use anyhow::anyhow; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, fund_address, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{ IotaObjectDataOptions, IotaObjectResponseQuery, IotaTransactionBlockResponseOptions, }, types::{ - base_types::{IotaAddress, ObjectID}, + base_types::ObjectID, coin_manager::CoinManagerTreasuryCap, crypto::SignatureScheme::ED25519, dynamic_field::DynamicFieldName, @@ -24,7 +23,7 @@ use iota_sdk::{ transaction::{Argument, ObjectArg, Transaction, TransactionData}, TypeTag, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, }, - IotaClient, IotaClientBuilder, + IotaClientBuilder, }; use move_core_types::{ident_str, language_storage::StructTag}; use shared_crypto::intent::Intent; @@ -32,90 +31,6 @@ use shared_crypto::intent::Intent; /// Got from iota-genesis-builder/src/stardust/test_outputs/alias_ownership.rs const MAIN_ADDRESS_MNEMONIC: &str = "few hood high omit camp keep burger give happy iron evolve draft few dawn pulp jazz box dash load snake gown bag draft car"; -/// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs -const SPONSOR_ADDRESS_MNEMONIC: &str = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; - -/// Creates a temporary keystore. -fn setup_keystore() -> Result { - // Create a temporary keystore. - let keystore_path = PathBuf::from("iotatempdb"); - if !keystore_path.exists() { - let keystore = FileBasedKeystore::new(&keystore_path)?; - keystore.save()?; - } - // Read the iota keystore. - FileBasedKeystore::new(&keystore_path) -} - -fn clean_keystore() -> Result<(), anyhow::Error> { - // Remove the keystore files. - fs::remove_file("iotatempdb")?; - fs::remove_file("iotatempdb.aliases")?; - Ok(()) -} - -async fn fund_address( - iota_client: &IotaClient, - keystore: &mut FileBasedKeystore, - recipient: IotaAddress, -) -> Result<(), anyhow::Error> { - // Derive the address of the sponsor. - let sponsor = keystore.import_from_mnemonic(SPONSOR_ADDRESS_MNEMONIC, ED25519, None)?; - - println!("Sponsor address: {sponsor:?}"); - - // 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"))?; - - let pt = { - // Init a programmable transaction builder. - let mut builder = ProgrammableTransactionBuilder::new(); - // Pay all iotas from the gas object - builder.pay_all_iota(recipient); - builder.finish() - }; - - // Setup a gas budget and a gas price. - let gas_budget = 10_000_000; - let gas_price = iota_client.read_api().get_reference_gas_price().await?; - - // Create a transaction data that will be sent to the network. - let tx_data = TransactionData::new_programmable( - sponsor, - vec![gas_coin.object_ref()], - pt, - gas_budget, - gas_price, - ); - - // Sign the transaction. - let signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; - - // Execute the transaction. - let transaction_response = iota_client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(tx_data, vec![signature]), - IotaTransactionBlockResponseOptions::full_content(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - println!( - "Funding transaction digest: {}", - transaction_response.digest - ); - - Ok(()) -} - #[tokio::main] async fn main() -> Result<(), anyhow::Error> { // Build an IOTA client for a local network. diff --git a/docs/examples/rust/stardust/nft-migration.rs b/docs/examples/rust/stardust/nft-migration.rs index c220aa6cd1d..f8510281131 100644 --- a/docs/examples/rust/stardust/nft-migration.rs +++ b/docs/examples/rust/stardust/nft-migration.rs @@ -5,17 +5,13 @@ //! NFT. 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}; - use anyhow::{anyhow, Result}; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; -use iota_move_build::BuildConfig; +use docs_examples::utils::{clean_keystore, publish_custom_nft_package, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ - rpc_types::{ - IotaObjectDataOptions, IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponseOptions, - }, + rpc_types::{IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ - base_types::{IotaAddress, ObjectID}, + base_types::ObjectID, crypto::SignatureScheme::ED25519, gas_coin::GAS, programmable_transaction_builder::ProgrammableTransactionBuilder, @@ -23,14 +19,13 @@ use iota_sdk::{ transaction::{Argument, ObjectArg, Transaction, TransactionData}, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, }, - IotaClient, IotaClientBuilder, + 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; -const CUSTOM_NFT_PACKAGE_PATH: &str = "../move/custom_nft"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { @@ -46,9 +41,10 @@ async fn main() -> Result<(), anyhow::Error> { println!("{sender:?}"); // Publish the package of a custom NFT collection and then get the package id. + // The custom NFT module is obtained from a Move example in the docs. + // It is the same used in the Alias migration example. let custom_nft_package_id = - publish_custom_nft_package(sender, &mut keystore, &iota_client, CUSTOM_NFT_PACKAGE_PATH) - .await?; + publish_custom_nft_package(sender, &mut keystore, &iota_client).await?; // Get a gas coin let gas_coin = iota_client @@ -171,100 +167,3 @@ async fn main() -> Result<(), anyhow::Error> { // Finish and clean the temporary keystore file clean_keystore() } - -fn setup_keystore() -> Result { - // 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(()) -} - -async fn publish_custom_nft_package( - sender: IotaAddress, - keystore: &mut FileBasedKeystore, - iota_client: &IotaClient, - package_path: &str, -) -> Result { - // Get a gas coin - let gas_coin = iota_client - .coin_read_api() - .get_coins(sender, None, None, None) - .await? - .data - .into_iter() - .next() - .ok_or(anyhow!("No coins found"))?; - - // Build custom nft package - let compiled_package = BuildConfig::default().build(package_path.into())?; - let modules = compiled_package - .get_modules() - .map(|module| { - let mut buf = Vec::new(); - module.serialize(&mut buf)?; - Ok(buf) - }) - .collect::>>>()?; - let dependencies = compiled_package.get_dependency_original_package_ids(); - - // Publish package - let pt = { - let mut builder = ProgrammableTransactionBuilder::new(); - builder.publish_immutable(modules, dependencies); - 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?; - - // Create the transaction data that will be sent to the network - let tx_data = TransactionData::new_programmable( - sender, - vec![gas_coin.object_ref()], - pt, - gas_budget, - gas_price, - ); - - // Sign the transaction - let signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; - - // Execute transaction - let transaction_response = iota_client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(tx_data, vec![signature]), - IotaTransactionBlockResponseOptions::full_content(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - println!( - "Package publishing transaction digest: {}", - transaction_response.digest - ); - - // Extract package id from the transaction effects - let tx_effects = transaction_response - .effects - .expect("Transaction has no effects"); - let package_ref = tx_effects - .created() - .first() - .expect("There are no created objects"); - let package_id = package_ref.reference.object_id; - println!("Package ID: {}", package_id); - Ok(package_id) -} diff --git a/docs/examples/rust/stardust/nft-output-claim.rs b/docs/examples/rust/stardust/nft-output-claim.rs index 78e66a31123..3d80afa83f1 100644 --- a/docs/examples/rust/stardust/nft-output-claim.rs +++ b/docs/examples/rust/stardust/nft-output-claim.rs @@ -5,10 +5,11 @@ //! 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 std::str::FromStr; use anyhow::anyhow; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{IotaData, IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ @@ -25,6 +26,7 @@ use iota_sdk::{ }; 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 = "okay pottery arch air egg very cave cash poem gown sorry mind poem crack dawn wet car pink extra crane hen bar boring salt"; @@ -203,21 +205,3 @@ async fn main() -> Result<(), anyhow::Error> { // Finish and clean the temporary keystore file clean_keystore() } - -fn setup_keystore() -> Result { - // 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(()) -} diff --git a/docs/examples/rust/stardust/shimmer-self-sponsor.rs b/docs/examples/rust/stardust/shimmer-self-sponsor.rs index 1c8a8166245..89ddf2af64f 100644 --- a/docs/examples/rust/stardust/shimmer-self-sponsor.rs +++ b/docs/examples/rust/stardust/shimmer-self-sponsor.rs @@ -5,11 +5,12 @@ //! 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 std::str::FromStr; use anyhow::anyhow; use bip32::DerivationPath; -use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use docs_examples::utils::{clean_keystore, setup_keystore}; +use iota_keys::keystore::AccountKeystore; use iota_sdk::{ rpc_types::{IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, types::{ @@ -32,24 +33,6 @@ pub const SHIMMER_COIN_TYPE: u32 = 4219; /// 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 { - 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 From eb8e503f561518174db916f28d22b51370dbdff5 Mon Sep 17 00:00:00 2001 From: Dkwcs Date: Thu, 15 Aug 2024 12:09:56 +0300 Subject: [PATCH 35/39] fix(docs/content): add links to simplify docs navigation --- docs/content/developer/iota-101/iota-move-concepts/patterns.mdx | 2 +- docs/content/developer/iota-101/objects/object-model.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/developer/iota-101/iota-move-concepts/patterns.mdx b/docs/content/developer/iota-101/iota-move-concepts/patterns.mdx index 888b59cf36c..8a891d25bab 100644 --- a/docs/content/developer/iota-101/iota-move-concepts/patterns.mdx +++ b/docs/content/developer/iota-101/iota-move-concepts/patterns.mdx @@ -11,7 +11,7 @@ A capability is a pattern that allows authorizing actions with an object. See [C ## Witness and transferrable witness -A witness is a type with `drop` that proves that its owner was present at the time of some privileged operation, for example having access to the one-time witness (OTW) for a module proves that the code is being run at the time the module was first published. See the following topics for details. +A witness is a type with `drop` that proves that its owner was present at the time of some privileged operation, for example having access to the one-time witness ([OTW](../../iota-101/iota-move-concepts/one-time-witness.mdx)) for a module proves that the code is being run at the time the module was first published. See the following topics for details. - [Witness](./patterns/witness) - [Transferrable Witness](./patterns/transferrable-witness) diff --git a/docs/content/developer/iota-101/objects/object-model.mdx b/docs/content/developer/iota-101/objects/object-model.mdx index f860eab9fea..87552f2c39f 100644 --- a/docs/content/developer/iota-101/objects/object-model.mdx +++ b/docs/content/developer/iota-101/objects/object-model.mdx @@ -31,7 +31,7 @@ There are a few different ways to concisely refer to an object without specifyin ## The transaction-object DAG: Relating objects and transactions -Transactions take objects as input, read/write/mutate these inputs, and produce mutated or newly created objects as output. Each object knows the (hash of the) last transaction that produced it as an output. Thus, a natural way to represent the relationship between objects and transactions is a directed acyclic graph (DAG) where: +Transactions take objects as input, read/write/mutate these inputs, and produce mutated or newly created objects as output. Each object knows the (hash of the) last transaction that produced it as an output. Thus, a natural way to represent the relationship between objects and transactions is a directed acyclic graph ([DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph)) where: - Nodes are transactions. - Directed edges go from transaction `A` to transaction `B` if an output object of `A` is an input object of `B`. They are labeled by the reference of the object in question (which specifies the exact version of the object created by `A` and used by `B`). From d61e6ee6d963ad0fd9588eaee33bc72e6b4d9be1 Mon Sep 17 00:00:00 2001 From: Valerii Reutov Date: Thu, 15 Aug 2024 15:46:24 +0300 Subject: [PATCH 36/39] feat(docs): Move content was fixed in the Developer/Getting Started documentation part --- .../getting-started/first-app/build-test.mdx | 283 ++++++++++-------- .../getting-started/first-app/debug.mdx | 42 +-- .../getting-started/first-app/publish.mdx | 14 +- .../first-app/write-package.mdx | 19 +- 4 files changed, 204 insertions(+), 154 deletions(-) diff --git a/docs/content/developer/getting-started/first-app/build-test.mdx b/docs/content/developer/getting-started/first-app/build-test.mdx index a1288d7f515..0877e1f00d9 100644 --- a/docs/content/developer/getting-started/first-app/build-test.mdx +++ b/docs/content/developer/getting-started/first-app/build-test.mdx @@ -38,8 +38,8 @@ $ iota move test If you execute this command for the package created in [Write a Package](write-package.mdx), you see the following output. Unsurprisingly, the test result has an `OK` status because there are no tests written yet to fail. ```shell -BUILDING Iota -BUILDING MoveStdlib +INCLUDING DEPENDENCY Iota +INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_package Running Move unit tests Test result: OK. Total tests: 0; passed: 0; failed: 0 @@ -49,24 +49,23 @@ To actually test your code, you need to add test functions. Start with adding a ```move #[test] -public fun test_sword_create() { +public fun test_sword() { + // Create a dummy TxContext for testing. + let mut ctx = tx_context::dummy(); - // Create a dummy TxContext for testing - let ctx = tx_context::dummy(); - - // Create a sword + // Create a sword. let sword = Sword { id: object::new(&mut ctx), magic: 42, strength: 7, }; - // Check if accessor functions return correct values + // Check if accessor functions return correct values. assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); } ``` -As the code shows, the unit test function (`test_sword_create()`) creates a dummy instance of the `TxContext` struct and assigns it to `ctx`. The function then creates a sword object using `ctx` to create a unique identifier and assigns `42` to the `magic` parameter and `7` to `strength`. Finally, the test calls the `magic` and `strength` accessor functions to verify that they return correct values. +As the code shows, the unit test function (`test_sword()`) creates a dummy instance of the `TxContext` struct and assigns it to `ctx`. The function then creates a sword object using `ctx` to create a unique identifier and assigns `42` to the `magic` parameter and `7` to `strength`. Finally, the test calls the `magic` and `strength` accessor functions to verify that they return correct values. The function passes the dummy context, `ctx`, to the `object::new` function as a mutable reference argument (`&mut`), but passes `sword` to its accessor functions as a read-only reference argument, `&sword`. @@ -80,21 +79,21 @@ After running the `test` command, however, you get a compilation error instead o ```shell error[E06001]: unused value without 'drop' - ┌─ ./sources/my_module.move:60:65 - │ + ┌─ sources/my_module.move:55:65 + │ 4 │ public struct Sword has key, store { - │ ----- To satisfy the constraint, the 'drop' ability would need to be added here - · -27 │ let sword = Sword { + │ ----- To satisfy the constraint, the 'drop' ability would need to be added here + · +48 │ let sword = Sword { │ ----- The local variable 'sword' still contains a value. The value does not have the 'drop' ability and must be consumed before the function returns │ ╭─────────────────────' -28 │ │ id: object::new(&mut ctx), -29 │ │ magic: 42, -30 │ │ strength: 7, -31 │ │ }; - │ ╰─────────' The type 'MyFirstPackage::my_module::Sword' does not have the ability 'drop' +49 │ │ id: object::new(&mut ctx), +50 │ │ magic: 42, +51 │ │ strength: 7, +52 │ │ }; + │ ╰─────────' The type 'my_first_package::my_module::Sword' does not have the ability 'drop' · │ -34 │ assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); +55 │ assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); │ ^ Invalid return ``` @@ -107,7 +106,7 @@ One of the solutions (as suggested in the error message), is to add the `drop` a We now transfer ownership of the `sword` to a freshly created dummy address: ```move -// Create a dummy address and transfer the sword +// Create a dummy address and transfer the sword. let dummy_address = @0xCAFE; transfer::transfer(sword, dummy_address); ``` @@ -115,11 +114,11 @@ transfer::transfer(sword, dummy_address); Run the test command again. Now the output shows a single successful test has run: ```shell -BUILDING MoveStdlib -BUILDING Iota +INCLUDING DEPENDENCY Iota +INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_package Running Move unit tests -[ PASS ] 0x0::my_module::test_sword_create +[ PASS ] 0x0::my_module::test_sword Test result: OK. Total tests: 1; passed: 1; failed: 0 ``` @@ -156,24 +155,20 @@ An instance of the `Scenario` struct contains a per-address object pool emulatin Update your `my_module.move` file to include entry functions callable from IOTA that implement `sword` creation and transfer. With these in place, you can then add a multi-transaction test that uses the `test_scenario` module to test these new capabilities. Put these functions after the accessors (Part 5 in comments). ```move -public fun sword_create(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { - - // create a sword +public fun create_sword(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { + // Create a sword. let sword = Sword { id: object::new(ctx), magic: magic, strength: strength, }; - // transfer the sword + // Transfer the sword. transfer::transfer(sword, recipient); - } public fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) { - - // transfer the sword + // Transfer the sword. transfer::public_transfer(sword, recipient); - } ``` @@ -182,49 +177,49 @@ The code of the new functions uses struct creation and IOTA-internal modules (`T With the new entry functions included, add another test function to make sure they behave as expected. ```move - #[test] - fun test_sword_transactions() { - use iota::test_scenario; - - // create test addresses representing users - let admin = @0xBABE; - let initial_owner = @0xCAFE; - let final_owner = @0xFACE; - - // first transaction to emulate module initialization - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - { - init(test_scenario::ctx(scenario)); - }; - // second transaction executed by admin to create the sword - test_scenario::next_tx(scenario, admin); - { - // create the sword and transfer it to the initial owner - sword_create(42, 7, initial_owner, test_scenario::ctx(scenario)); - }; - // third transaction executed by the initial sword owner - test_scenario::next_tx(scenario, initial_owner); - { - // extract the sword owned by the initial owner - let sword = test_scenario::take_from_sender(scenario); - // transfer the sword to the final owner - sword_transfer(sword, final_owner, test_scenario::ctx(scenario)) - }; - // fourth transaction executed by the final sword owner - test_scenario::next_tx(scenario, final_owner); - { - // extract the sword owned by the final owner - let sword = test_scenario::take_from_sender(scenario); - // verify that the sword has expected properties - assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); - // return the sword to the object pool - test_scenario::return_to_sender(scenario, sword) - // or uncomment the line below to destroy the sword instead - // test_utils::destroy(sword) - }; - test_scenario::end(scenario_val); - } +#[test] +fun test_sword_transactions() { + use iota::test_scenario; + + // Create test addresses representing users. + let admin = @0xBABE; + let initial_owner = @0xCAFE; + let final_owner = @0xFACE; + + // First transaction to emulate module initialization. + let mut scenario_val = test_scenario::begin(admin); + let scenario = &mut scenario_val; + { + init(test_scenario::ctx(scenario)); + }; + // Second transaction executed by admin to create a sword. + test_scenario::next_tx(scenario, admin); + { + // Create the sword and transfer it to the initial owner. + create_sword(42, 7, initial_owner, test_scenario::ctx(scenario)); + }; + // Third transaction executed by the initial sword owner. + test_scenario::next_tx(scenario, initial_owner); + { + // Extract the sword owned by the initial owner. + let sword = test_scenario::take_from_sender(scenario); + // Transfer the sword to the final owner. + sword_transfer(sword, final_owner, test_scenario::ctx(scenario)) + }; + // Fourth transaction executed by the final sword owner. + test_scenario::next_tx(scenario, final_owner); + { + // Extract the sword owned by the final owner. + let sword = test_scenario::take_from_sender(scenario); + // Verify that the sword has expected properties. + assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); + // Return the sword to the object pool + test_scenario::return_to_sender(scenario, sword) + // or uncomment the line below to destroy the sword instead. + // test_utils::destroy(sword) + }; + test_scenario::end(scenario_val); +} ``` There are some details of the new testing function to pay attention to. The first thing the code does is create some addresses that represent users participating in the testing scenario. The assumption is that there is one game administrator user and two regular users representing players. The test then creates a scenario by starting the first transaction on behalf of the administrator address. @@ -245,11 +240,11 @@ In the pure Move testing function, the function transfers the `sword` object to Run the test command again to see two successful tests for our module: ```shell -BUILDING Iota -BUILDING MoveStdlib +INCLUDING DEPENDENCY Iota +INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_package Running Move unit tests -[ PASS ] 0x0::my_module::test_sword_create +[ PASS ] 0x0::my_module::test_sword [ PASS ] 0x0::my_module::test_sword_transactions Test result: OK. Total tests: 2; passed: 2; failed: 0 ``` @@ -276,37 +271,20 @@ While the `iota move` command does not support publishing explicitly, you can st The `init` function for the module in the running example creates a `Forge` object. ```move - /// Module initializer to be executed when this module is published - fun init(ctx: &mut TxContext) { - let admin = Forge { - id: object::new(ctx), - swords_created: 0, - }; - - // transfer the forge object to the module/package publisher - transfer::transfer(admin, tx_context::sender(ctx)); - } -``` - -The tests you have so far call the `init` function, but the initializer function itself isn't tested to ensure it properly creates a `Forge` object. To test this functionality, add a `new_sword` function to take the forge as a parameter and to update the number of created swords at the end of the function. If this were an actual module, you'd replace the `create_sword` function with `new_sword`. To keep the existing tests from failing, however, the example uses both functions. +/// Module initializer to be executed when this module is published. +fun init(ctx: &mut TxContext) { + let admin = Forge { + id: object::new(ctx), + swords_created: 0, + }; -```move - /// Constructor for creating swords - public fun new_sword( - forge: &mut Forge, - magic: u64, - strength: u64, - ctx: &mut TxContext, - ): Sword { - forge.swords_created = forge.swords_created + 1; - Sword { - id: object::new(ctx), - magic: magic, - strength: strength, - } - } + // Transfer the forge object to the module/package publisher. + transfer::transfer(admin, tx_context::sender(ctx)); +} ``` +The tests you have so far call the `init` function, but the initializer function itself isn't tested to ensure it properly creates a `Forge` object. + Now, create a function to test the module initialization: ```move @@ -316,27 +294,25 @@ Now, create a function to test the module initialization: #[test] public fun test_module_init() { - let ts = ts::begin(@0x0); + let mut ts = ts::begin(@0x0); - // first transaction to emulate module initialization. + // First transaction to emulate module initialization. + ts::next_tx(&mut ts, ADMIN); { - ts::next_tx(&mut ts, ADMIN); init(ts::ctx(&mut ts)); }; - // second transaction to check if the forge has been created - // and has initial value of zero swords created + // Second transaction to check if the forge has been created and has initial value of zero swords created. + ts::next_tx(&mut ts, ADMIN); { - ts::next_tx(&mut ts, ADMIN); - - // extract the Forge object - let forge: Forge = ts::take_from_sender(&mut ts); + // Extract the Forge object. + let forge: Forge = ts::take_from_sender(&ts); - // verify number of created swords + // Verify number of created swords. assert!(swords_created(&forge) == 0, 1); - // return the Forge object to the object pool - ts::return_to_sender(&mut ts, forge); + // Return the Forge object to the object pool. + ts::return_to_sender(&ts, forge); }; ts::end(ts); @@ -345,6 +321,77 @@ public fun test_module_init() { As the new test function shows, the first transaction (explicitly) calls the initializer. The next transaction checks if the `Forge` object has been created and properly initialized. +Add a `new_sword` function to take the forge as a parameter and to update the number of created swords at the end of the function. If this were an actual module, you'd replace the `create_sword` function with `new_sword`. To keep the existing tests from failing, however, the example uses both functions. + +```move +/// Constructor for creating swords. +public fun new_sword(forge: &mut Forge, magic: u64, strength: u64, ctx: &mut TxContext): Sword { + // Increment the `swords_created` counter. + forge.swords_created = forge.swords_created + 1; + + // Create a sword. + Sword { + id: object::new(ctx), + magic: magic, + strength: strength, + } +} +``` + +Once this function is added, we can use it in the `test_sword_transactions` test. + +```move +#[test] +fun test_sword_transactions() { + use iota::test_scenario; + + // Create test addresses representing users. + let admin = @0xBABE; + let initial_owner = @0xCAFE; + let final_owner = @0xFACE; + + // First transaction to emulate module initialization. + let mut scenario_val = test_scenario::begin(admin); + let scenario = &mut scenario_val; + { + init(test_scenario::ctx(scenario)); + }; + // Second transaction executed by admin to create a sword. + test_scenario::next_tx(scenario, admin); + { + let mut forge = test_scenario::take_from_sender(scenario); + + // Create the sword and transfer it to the initial owner. + let sword = new_sword(&mut forge, 42, 7, test_scenario::ctx(scenario)); + transfer::public_transfer(sword, initial_owner); + + // Return the forge to the sender. + test_scenario::return_to_sender(scenario, forge); + }; + // Third transaction executed by the initial sword owner. + test_scenario::next_tx(scenario, initial_owner); + { + // Extract the sword owned by the initial owner. + let sword = test_scenario::take_from_sender(scenario); + // Transfer the sword to the final owner. + sword_transfer(sword, final_owner, test_scenario::ctx(scenario)) + }; + // Fourth transaction executed by the final sword owner. + test_scenario::next_tx(scenario, final_owner); + { + // Extract the sword owned by the final owner. + let sword = test_scenario::take_from_sender(scenario); + // Verify that the sword has expected properties. + assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); + // Return the sword to the object pool + test_scenario::return_to_sender(scenario, sword) + // or uncomment the line below to destroy the sword instead. + // test_utils::destroy(sword) + }; + test_scenario::end(scenario_val); +} +``` + You can refer to the source code for the package (with all the tests and functions properly adjusted) in the [first_package](https://github.com/iotaledger/iota/tree/develop/examples/move/first_package/sources/example.move) module in the `iota/examples` directory. ## Related links diff --git a/docs/content/developer/getting-started/first-app/debug.mdx b/docs/content/developer/getting-started/first-app/debug.mdx index 47e867b925e..35f1e3c6318 100644 --- a/docs/content/developer/getting-started/first-app/debug.mdx +++ b/docs/content/developer/getting-started/first-app/debug.mdx @@ -34,16 +34,17 @@ Alternatively, any call to abort or assertion failure also prints the stacktrace To see the module in action, update your `my_module` code to include debug calls. Specifically, update the `new_sword` function so that you print the value of `forge` before and after updating `swords_created`. Also, include a `print_stack_trace` so that the function looks like the following: ```move -public fun new_sword( - forge: &mut Forge, - magic: u64, - strength: u64, - ctx: &mut TxContext, -): Sword { +/// Constructor for creating swords. +public fun new_sword(forge: &mut Forge, magic: u64, strength: u64, ctx: &mut TxContext): Sword { debug::print(forge); + + // Increment the `swords_created` counter. forge.swords_created = forge.swords_created + 1; + debug::print(forge); debug::print_stack_trace(); + + // Create a sword. Sword { id: object::new(ctx), magic: magic, @@ -55,7 +56,7 @@ public fun new_sword( To see the results, run the module's tests. ```shell -$ iota move test +$ iota move test test_sword_transactions ``` The response prints out the expected results as the test calls the `new_sword` function. @@ -65,7 +66,6 @@ INCLUDING DEPENDENCY Iota INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_package Running Move unit tests -[ PASS ] 0x0::my_module::test_module_init [debug] 0x0::my_module::Forge { id: 0x2::object::UID { id: 0x2::object::ID { @@ -86,25 +86,29 @@ Call Stack: [0] 0000000000000000000000000000000000000000000000000000000000000000::my_module::test_sword_transactions Code: - [19] LdU64(7) - [20] MutBorrowLoc(3) - [21] Call(14) - > [22] Call(4) - [23] LdConst(1) - [24] CallGeneric(2) - [25] ImmBorrowLoc(3) + [24] LdU64(7) + [25] CopyLoc(5) + [26] Call(13) + > [27] Call(6) + [28] CopyLoc(4) + [29] CallGeneric(2) + [30] CopyLoc(5) Locals: [0] - - [1] { { { } }, 1 } - [2] - - [3] { 2, { 00000000000000000000000000000000000000000000000000000000000000ad, [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0 } } + [1] - + [2] 000000000000000000000000000000000000000000000000000000000000face + [3] { { { } }, 1 } + [4] 000000000000000000000000000000000000000000000000000000000000cafe + [5] (&) { 1, { 000000000000000000000000000000000000000000000000000000000000babe, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0 } } + [6] { 1, { 000000000000000000000000000000000000000000000000000000000000babe, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0, 0, 0 } } + [7] - Operand Stack: [ PASS ] 0x0::my_module::test_sword_transactions -Test result: OK. Total tests: 2; passed: 2; failed: 0 +Test result: OK. Total tests: 1; passed: 1; failed: 0 ``` ## Related links diff --git a/docs/content/developer/getting-started/first-app/publish.mdx b/docs/content/developer/getting-started/first-app/publish.mdx index 78fed1cea85..264214138f8 100644 --- a/docs/content/developer/getting-started/first-app/publish.mdx +++ b/docs/content/developer/getting-started/first-app/publish.mdx @@ -44,19 +44,19 @@ $ iota client objects ╭───────────────────────────────────────────────────────────────────────────────────────╮ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 10 │ │ +│ │ version │ 3 │ │ │ │ digest │ │ │ │ │ objectType │ ::my_module::Forge │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 10 │ │ +│ │ version │ 3 │ │ │ │ digest │ │ │ │ │ objectType │ 0x0000..0002::coin::Coin │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 10 │ │ +│ │ version │ 3 │ │ │ │ digest │ │ │ │ │ objectType │ 0x0000..0002::package::UpgradeCap │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ @@ -124,25 +124,25 @@ After the transaction executes, you can check the status of the `Sword` object b ╭───────────────────────────────────────────────────────────────────────────────────────╮ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 11 │ │ +│ │ version │ 4 │ │ │ │ digest │ │ │ │ │ objectType │ ::my_module::Forge │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 11 │ │ +│ │ version │ 4 │ │ │ │ digest │ │ │ │ │ objectType │ 0x0000..0002::coin::Coin │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 11 │ │ +│ │ version │ 4 │ │ │ │ digest │ │ │ │ │ objectType │ ::my_module::Sword │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ objectId │ │ │ -│ │ version │ 10 │ │ +│ │ version │ 3 │ │ │ │ digest │ │ │ │ │ objectType │ 0x0000..0002::package::UpgradeCap │ │ │ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │ diff --git a/docs/content/developer/getting-started/first-app/write-package.mdx b/docs/content/developer/getting-started/first-app/write-package.mdx index a24307805ec..5a9038936f8 100644 --- a/docs/content/developer/getting-started/first-app/write-package.mdx +++ b/docs/content/developer/getting-started/first-app/write-package.mdx @@ -14,13 +14,12 @@ Running the previous command creates a directory with the name you provide (`my_ ```move title="my_first_package/Move.toml" [package] name = "my_first_package" - -# edition = "2024.beta " # To use the Move 2024 edition, currently in beta +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move # license = "" # e.g., "MIT", "GPL", "Apache 2.0" # authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] [dependencies] -IOTA = { git = "https://github.com/iotaledger/iota.git", subdir = "crates/iota-framework/packages/iota-framework", rev = "framework/testnet" } +Iota = { git = "https://github.com/iotaledger/iota.git", subdir = "crates/iota-framework/packages/iota-framework", rev = "framework/testnet" } # For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. # Revision can be a branch, a tag, and a commit hash. @@ -54,7 +53,7 @@ my_first_package = "0x0" The manifest file contents include available sections of the manifest and comments that provide additional information. In Move, you prepend the hash mark (`#`) to a line to denote a comment. - **[package]:** Contains metadata for the package. By default, the `iota move new` command populates only the `name` value of the metadata. In this case, the example passes `my_first_package` to the command, which becomes the name of the package. You can delete the first `#` of subsequent lines of the `[package]` section to provide values for the other available metadata fields. -- **[dependencies]:** Lists the other packages that your package depends on to run. By default, the `iota move new` command lists the `IOTA` package on GitHub (Testnet version) as the lone dependency. +- **[dependencies]:** Lists the other packages that your package depends on to run. By default, the `iota move new` command lists the `Iota` package on GitHub (Testnet version) as the lone dependency. - **[addresses]:** Declares named addresses that your package uses. By default, the section includes the package you create with the `iota move new` command and an address of `0x0`. The publish process replaces the `0x0` address with an actual on-chain address. - **[dev-dependencies]:** Includes only comments that describe the section. - **[dev-addresses]:** Includes only comments that describe the section. @@ -88,7 +87,7 @@ Populate the `my_module.move` file with the following code: ```move module my_first_package::my_module { - // Part 1: Struct definitions + // Part 1: Struct definitions. public struct Sword has key, store { id: UID, magic: u64, @@ -100,17 +99,17 @@ module my_first_package::my_module { swords_created: u64, } - // Part 2: Module initializer to be executed when this module is published + // Part 2: Module initializer to be executed when this module is published. fun init(ctx: &mut TxContext) { let admin = Forge { id: object::new(ctx), swords_created: 0, }; - // Transfer the forge object to the module/package publisher + // Transfer the forge object to the module/package publisher. transfer::public_transfer(admin, tx_context::sender(ctx)); } - // Part 3: Accessors required to read the struct attributes + // Part 3: Accessors required to read the struct attributes. public fun magic(self: &Sword): u64 { self.magic } @@ -123,9 +122,9 @@ module my_first_package::my_module { self.swords_created } - // Part 4: Public/entry functions (introduced later in the tutorial) + // Part 4: Public/entry functions (introduced later in the tutorial). - // Part 5: Private functions (if any) + // Part 5: Private functions (if any). } ``` From a2ea13281a69f9282d5e7330c284150572b5df37 Mon Sep 17 00:00:00 2001 From: Dkwcs Date: Thu, 15 Aug 2024 16:09:26 +0300 Subject: [PATCH 37/39] fix(docs/context): add links, minor docs refactoring --- .../iota-101/create-coin/create-coin.mdx | 8 +-- .../transactions/sign-and-send-txn.mdx | 6 +- .../iota-101/transactions/sponsor-txn.mdx | 2 +- .../developer/iota-101/using-events.mdx | 57 +++++++++++-------- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/docs/content/developer/iota-101/create-coin/create-coin.mdx b/docs/content/developer/iota-101/create-coin/create-coin.mdx index 289b4f55f66..e60fc4d3935 100644 --- a/docs/content/developer/iota-101/create-coin/create-coin.mdx +++ b/docs/content/developer/iota-101/create-coin/create-coin.mdx @@ -66,13 +66,7 @@ Amount: ## DenyList -The IOTA framework provides a `DenyList` singleton, shared object that the bearer of a `DenyCap` can access to specify a list of addresses that are unable to use a IOTA core type. The initial use case for `DenyList`, however, focuses on limiting access to coins of a specified type. This is useful, for example, when creating a regulated coin on IOTA that requires the ability to block certain addresses from using it as inputs to transactions. Regulated coins on IOTA satisfy any regulations that require the ability to prevent known bad actors from having access to those coins. - -:::info - -The `DenyList` object is a system object that has the address `0x403`. You cannot create it yourself. - -::: +See [`DenyList`](../create-coin/regulated.mdx#deny-list). ## Create regulated coin diff --git a/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx b/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx index c0431327a75..77d08e9867a 100644 --- a/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx +++ b/docs/content/developer/iota-101/transactions/sign-and-send-txn.mdx @@ -20,7 +20,7 @@ With a signature and the transaction bytes, a transaction can be submitted to be The following high-level process describes the overall workflow for constructing, signing and executing an on-chain transaction: - Construct the transaction data by creating a `TransactionBlock` where multiple transactions are chained. See [Building Programmable Transaction Blocks](ptb/building-ptb.mdx) for more information. -- The SDK's built-in gas estimation and coin selection picks the gas coin. +- The [SDK's](../../../references/iota-sdks.mdx) built-in gas estimation and coin selection picks the gas coin. - Sign the transaction to generate a [signature](../../cryptography/transaction-auth/signatures.mdx). - Submit the `TransactionBlock` and its signature for on-chain execution. @@ -40,7 +40,7 @@ The following examples demonstrate how to sign and execute transactions using Ru -There are various ways to instantiate a key pair and to derive its public key and IOTA address using the IOTA TypeScript SDK. +There are various ways to instantiate a key pair and to derive its public key and IOTA address using the [IOTA TypeScript SDK](../../../references/ts-sdk/typescript/index.mdx#iota-typescript-sdk). ```tsx import { fromHEX } from '@iota/bcs'; @@ -108,7 +108,7 @@ console.log(res); The full code example below can be found under [crates/iota-sdk](https://github.com/iotaledger/iota/blob/main/crates/iota-sdk/examples/sign_tx_guide.rs). -There are various ways to instantiate a `IotaKeyPair` and to derive its public key and IOTA address using the IOTA Rust SDK. +There are various ways to instantiate a `IotaKeyPair` and to derive its public key and IOTA address using the [IOTA Rust SDK](../../../references/rust-sdk.mdx#iota-rust-sdk). ```rust // deterministically generate a keypair, testing only, do not use for mainnet, use the next section to randomly generate a keypair instead. diff --git a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx b/docs/content/developer/iota-101/transactions/sponsor-txn.mdx index f621a131568..c7ecf113222 100644 --- a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx +++ b/docs/content/developer/iota-101/transactions/sponsor-txn.mdx @@ -5,7 +5,7 @@ title: Sponsored Transaction Sponsored transactions are a primitive on the IOTA blockchain that enable the execution of a transaction without a user paying the gas. It also discusses the roles in Sponsored Transaction, and a few common use cases. Then it discusses the flow of Sponsored Transaction, mostly for developers who are interested in building a Gas Station or integrate with one. Finally it talks about risk considerations of Sponsored Transaction. # Overview -A transaction on IOTA takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::iota::IOTA>` objects, and paid to IOTA validators to secure the network. Although gas is a critical piece in IOTA tokenomics, it sometimes adds challenges when new users start to navigate on IOTA, especially for web 2.0 users. +A transaction on IOTA takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::iota::IOTA>` objects, and paid to IOTA validators to secure the network. Although gas is a critical piece in [IOTA tokenomics](../../../about-iota/tokenomics.mdx#iota-tokenomics), it sometimes adds challenges when new users start to navigate on IOTA, especially for web 2.0 users. Sponsored transactions can reduce the onboarding friction for users because the feature streamlines the process for end users. Using sponsored transactions, you can execute a transaction without requiring the user to pay it themselves. Instead, you can act as a sponsor of the transaction, offering your own payment gas objects for the transaction. diff --git a/docs/content/developer/iota-101/using-events.mdx b/docs/content/developer/iota-101/using-events.mdx index d1918662458..89831e1cde9 100644 --- a/docs/content/developer/iota-101/using-events.mdx +++ b/docs/content/developer/iota-101/using-events.mdx @@ -124,9 +124,40 @@ Move smart contracts can call other smart contracts that emit events. For exampl This example leverages the IOTA TypeScript SDK to subscribe to events the package with ID `` emits. Each time the event fires, the code displays the response to the console. + + + +### Rust + + + +See [Rust SDK](../../references/rust-sdk.mdx#rust-sdk). + +```rust +use futures::StreamExt; +use iota_sdk::rpc_types::EventFilter; +use iota_sdk::IOTAClientBuilder; +use anyhow::Result; + +#[tokio::main] +async fn main() -> Result<()> { + let iota = IOTAClientBuilder::default() + .ws_url("wss://fullnode.mainnet.iota.io:443") + .build("https://fullnode.mainnet.iota.io:443") + .await.unwrap(); + let mut subscribe_all = iota.event_api().subscribe_event(EventFilter::All(vec![])).await?; + loop { + println!("{:?}", subscribe_all.next().await); + } +} +``` + + + + ### TypeScript -To create the event subscription, you can use a basic Node.js app. You need the IOTA TypeScript SDK, so install the module using `npm install @iota/iota-sdk` at the root of your project. In your TypeScript code, import `JsonRpcProvider` and a connection from the library. +To create the event subscription, you can use a basic Node.js app. You need the [IOTA TypeScript SDK](../../references/ts-sdk/typescript/index.mdx#iota-typescript-sdk), so install the module using `npm install @iota/iota-sdk` at the root of your project. In your TypeScript code, import `JsonRpcProvider` and a connection from the library. ```ts import { JsonRpcProvider, testnetConnection } from '@iota/iota-sdk'; @@ -185,28 +216,8 @@ subscribeEvent { } ``` -### Rust SDK - - - -```rust -use futures::StreamExt; -use iota_sdk::rpc_types::EventFilter; -use iota_sdk::IOTAClientBuilder; -use anyhow::Result; - -#[tokio::main] -async fn main() -> Result<()> { - let iota = IOTAClientBuilder::default() - .ws_url("wss://fullnode.mainnet.iota.io:443") - .build("https://fullnode.mainnet.iota.io:443") - .await.unwrap(); - let mut subscribe_all = iota.event_api().subscribe_event(EventFilter::All(vec![])).await?; - loop { - println!("{:?}", subscribe_all.next().await); - } -} -``` + + ## Filtering event queries From 1b0e87c22790f6fa7bc31eef259e56b8c354fae2 Mon Sep 17 00:00:00 2001 From: Dkwcs Date: Thu, 15 Aug 2024 18:19:04 +0300 Subject: [PATCH 38/39] fix(docs/content): documentation code examples enhancement --- docs/content/developer/iota-101/access-time.mdx | 2 +- .../iota-101/create-coin/create-coin.mdx | 7 ++----- .../iota-101/create-coin/in-game-token.mdx | 15 +++++---------- .../developer/iota-101/create-coin/loyalty.mdx | 15 +++++---------- .../developer/iota-101/create-coin/regulated.mdx | 5 +---- .../iota-move-concepts/patterns/capabilities.mdx | 9 ++++++++- .../iota-move-concepts/patterns/id-pointer.mdx | 9 ++++----- .../objects/transfers/transfer-to-object.mdx | 2 +- 8 files changed, 27 insertions(+), 37 deletions(-) diff --git a/docs/content/developer/iota-101/access-time.mdx b/docs/content/developer/iota-101/access-time.mdx index 7ab9a6f62fb..58f649c7635 100644 --- a/docs/content/developer/iota-101/access-time.mdx +++ b/docs/content/developer/iota-101/access-time.mdx @@ -24,7 +24,7 @@ module example::clock { use iota::clock::{Self, Clock}; use iota::event; - struct TimeEvent has copy, drop, store { + public struct TimeEvent has copy, drop, store { timestamp_ms: u64 } diff --git a/docs/content/developer/iota-101/create-coin/create-coin.mdx b/docs/content/developer/iota-101/create-coin/create-coin.mdx index e60fc4d3935..a5110b803c9 100644 --- a/docs/content/developer/iota-101/create-coin/create-coin.mdx +++ b/docs/content/developer/iota-101/create-coin/create-coin.mdx @@ -8,15 +8,12 @@ Publishing a coin on IOTA is nearly as straightforward as publishing a new type. ```move module examples::mycoin { - use std::option; - use iota::coin::{Self, Coin, TreasuryCap}; - use iota::transfer; - use iota::tx_context::{Self, TxContext}; + use iota::coin::{Self, TreasuryCap}; /// The type identifier of coin. The coin will have a type /// tag of kind: `Coin` /// Make sure that the name of the type matches the module's name. - struct MYCOIN has drop {} + public struct MYCOIN has drop {} /// Module initializer is called once on module publish. A treasury /// cap is sent to the publisher, who then controls minting and burning diff --git a/docs/content/developer/iota-101/create-coin/in-game-token.mdx b/docs/content/developer/iota-101/create-coin/in-game-token.mdx index 90c6f834c55..57b9a224691 100644 --- a/docs/content/developer/iota-101/create-coin/in-game-token.mdx +++ b/docs/content/developer/iota-101/create-coin/in-game-token.mdx @@ -11,9 +11,6 @@ The following example creates an in-game currency called a GEM, which represents /// that sells swords for Gems. Gems are an in-game currency that can be bought /// with IOTA. module examples::sword { - use iota::tx_context::TxContext; - use iota::object::{Self, UID}; - use iota::token::{Self, Token, ActionRequest}; use examples::gem::GEM; @@ -24,7 +21,7 @@ module examples::sword { const SWORD_PRICE: u64 = 10; /// A game item that can be purchased with Gems. - struct Sword has key, store { id: UID } + public struct Sword has key, store { id: UID } /// Purchase a sword with Gems. public fun buy_sword( @@ -44,10 +41,8 @@ module examples::gem { use std::option::none; use std::string::{Self, String}; use iota::iota::IOTA; - use iota::transfer; - use iota::object::{Self, UID}; use iota::balance::{Self, Balance}; - use iota::tx_context::{sender, TxContext}; + use iota::tx_context::sender; use iota::coin::{Self, Coin, TreasuryCap}; use iota::token::{Self, Token, ActionRequest}; @@ -70,7 +65,7 @@ module examples::gem { #[allow(lint(coin_field))] /// Gems can be purchased through the `Store`. - struct GemStore has key { + public struct GemStore has key { id: UID, /// Profits from selling Gems. profits: Balance, @@ -79,7 +74,7 @@ module examples::gem { } /// The OTW to create the in-game currency. - struct GEM has drop {} + public struct GEM has drop {} // In the module initializer we create the in-game currency and define the // rules for different types of actions. @@ -91,7 +86,7 @@ module examples::gem { ); // create a `TokenPolicy` for GEMs - let (policy, cap) = token::new_policy(&treasury_cap, ctx); + let (mut policy, cap) = token::new_policy(&treasury_cap, ctx); token::allow(&mut policy, &cap, buy_action(), ctx); token::allow(&mut policy, &cap, token::spend_action(), ctx); diff --git a/docs/content/developer/iota-101/create-coin/loyalty.mdx b/docs/content/developer/iota-101/create-coin/loyalty.mdx index 2ecdbbb81cd..29ab2135011 100644 --- a/docs/content/developer/iota-101/create-coin/loyalty.mdx +++ b/docs/content/developer/iota-101/create-coin/loyalty.mdx @@ -15,11 +15,7 @@ The following example demonstrates the creation of a loyalty token that bearers /// Actions: /// - spend - spend the token in the shop module examples::loyalty { - use std::option; - use iota::transfer; - use iota::object::{Self, UID}; use iota::coin::{Self, TreasuryCap}; - use iota::tx_context::{Self, TxContext}; use iota::token::{Self, ActionRequest, Token}; @@ -30,15 +26,15 @@ module examples::loyalty { const GIFT_PRICE: u64 = 10; /// The OTW for the Token / Coin. - struct LOYALTY has drop {} + public struct LOYALTY has drop {} /// This is the Rule requirement for the `GiftShop`. The Rules don't need /// to be separate applications, some rules make sense to be part of the /// application itself, like this one. - struct GiftShop has drop {} + public struct GiftShop has drop {} /// The Gift object - can be purchased for 10 tokens. - struct Gift has key, store { + public struct Gift has key, store { id: UID } @@ -55,7 +51,7 @@ module examples::loyalty { ctx ); - let (policy, policy_cap) = token::new_policy(&treasury_cap, ctx); + let (mut policy, policy_cap) = token::new_policy(&treasury_cap, ctx); // but we constrain spend by this shop: token::add_rule_for_action( @@ -99,7 +95,7 @@ module examples::loyalty { assert!(token::value(&token) == GIFT_PRICE, EIncorrectAmount); let gift = Gift { id: object::new(ctx) }; - let req = token::spend(token, ctx); + let mut req = token::spend(token, ctx); // only required because we've set this rule token::add_approval(GiftShop {}, &mut req, ctx); @@ -107,5 +103,4 @@ module examples::loyalty { (gift, req) } } - ``` diff --git a/docs/content/developer/iota-101/create-coin/regulated.mdx b/docs/content/developer/iota-101/create-coin/regulated.mdx index c1e11ee827c..9df655907d7 100644 --- a/docs/content/developer/iota-101/create-coin/regulated.mdx +++ b/docs/content/developer/iota-101/create-coin/regulated.mdx @@ -9,12 +9,9 @@ Behind the scenes, `create_regulated_currency` uses the `create_currency` functi ```move title="regcoin.move" module examples::regcoin { - use std::option; use iota::coin; - use iota::transfer; - use iota::tx_context::{Self, TxContext}; - struct REGCOIN has drop {} + public struct REGCOIN has drop {} fun init(witness: REGCOIN, ctx: &mut TxContext) { let (treasury, deny_cap, metadata) = coin::create_regulated_currency(witness, 6, b"REGCOIN", b"", b"", option::none(), ctx); diff --git a/docs/content/developer/iota-101/iota-move-concepts/patterns/capabilities.mdx b/docs/content/developer/iota-101/iota-move-concepts/patterns/capabilities.mdx index fbc37855647..3ca7a8116d9 100644 --- a/docs/content/developer/iota-101/iota-move-concepts/patterns/capabilities.mdx +++ b/docs/content/developer/iota-101/iota-move-concepts/patterns/capabilities.mdx @@ -23,6 +23,13 @@ module examples::item { }, tx_context::sender(ctx)) } - /// The entry function can not be called if `AdminCap` is not passed as + /// The function can not be called if `AdminCap` is not passed as + public fun create_item(_: &AdminCap, name: String, ctx: &mut TxContext): Item { + let item = Item { + id: object::new(ctx), + name, + }; + item + } } ``` diff --git a/docs/content/developer/iota-101/iota-move-concepts/patterns/id-pointer.mdx b/docs/content/developer/iota-101/iota-move-concepts/patterns/id-pointer.mdx index 08a58b4c210..411e98ecb4f 100644 --- a/docs/content/developer/iota-101/iota-move-concepts/patterns/id-pointer.mdx +++ b/docs/content/developer/iota-101/iota-move-concepts/patterns/id-pointer.mdx @@ -14,7 +14,6 @@ The following example implements basic `Lock` and `Key` mechanics on IOTA where ```move module examples::lock_and_key { - /// Lock is empty, nothing to take. const ELockIsEmpty: u64 = 0; @@ -45,9 +44,8 @@ module examples::lock_and_key { /// Lock some content inside a shared object. A Key is created and is /// sent to the transaction sender. For example, we could turn the /// lock into a treasure chest by locking some `Coin` inside. - /// /// Sender gets the `Key` to this `Lock`. - public fun create(obj: T, ctx: &mut TxContext) { + public fun create(obj: T, ctx: &mut TxContext): Key { let id = object::new(ctx); let key_for = object::uid_to_inner(&id); @@ -56,10 +54,11 @@ module examples::lock_and_key { locked: option::some(obj), }); - transfer::transfer(Key { + let key = Key { key_for, id: object::new(ctx) - }, tx_context::sender(ctx)); + }; + key } /// Lock something inside a shared object using a Key. Aborts if diff --git a/docs/content/developer/iota-101/objects/transfers/transfer-to-object.mdx b/docs/content/developer/iota-101/objects/transfers/transfer-to-object.mdx index af51553f307..55357347d59 100644 --- a/docs/content/developer/iota-101/objects/transfers/transfer-to-object.mdx +++ b/docs/content/developer/iota-101/objects/transfers/transfer-to-object.mdx @@ -178,7 +178,7 @@ Because the `receive_object` function is generic over the object being received, ```move module examples::shared_object_auth { - use iota::transfer::{Self, Receiving}; + use iota::transfer::Receiving; const EAccessDenied: u64 = 0; const AuthorizedReceiverAddr: address = @0xB0B; From 6fdfc675f0aa355f876c83a4ce79d95e82622482 Mon Sep 17 00:00:00 2001 From: Valerii Reutov Date: Thu, 22 Aug 2024 12:12:48 +0300 Subject: [PATCH 39/39] fix(docs): fixed tokenomics link --- docs/content/developer/iota-101/transactions/sponsor-txn.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx b/docs/content/developer/iota-101/transactions/sponsor-txn.mdx index c7ecf113222..d131b37a93b 100644 --- a/docs/content/developer/iota-101/transactions/sponsor-txn.mdx +++ b/docs/content/developer/iota-101/transactions/sponsor-txn.mdx @@ -5,7 +5,7 @@ title: Sponsored Transaction Sponsored transactions are a primitive on the IOTA blockchain that enable the execution of a transaction without a user paying the gas. It also discusses the roles in Sponsored Transaction, and a few common use cases. Then it discusses the flow of Sponsored Transaction, mostly for developers who are interested in building a Gas Station or integrate with one. Finally it talks about risk considerations of Sponsored Transaction. # Overview -A transaction on IOTA takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::iota::IOTA>` objects, and paid to IOTA validators to secure the network. Although gas is a critical piece in [IOTA tokenomics](../../../about-iota/tokenomics.mdx#iota-tokenomics), it sometimes adds challenges when new users start to navigate on IOTA, especially for web 2.0 users. +A transaction on IOTA takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::iota::IOTA>` objects, and paid to IOTA validators to secure the network. Although gas is a critical piece in [IOTA tokenomics](../../../about-iota/tokenomics/tokenomics.mdx#iota-tokenomics), it sometimes adds challenges when new users start to navigate on IOTA, especially for web 2.0 users. Sponsored transactions can reduce the onboarding friction for users because the feature streamlines the process for end users. Using sponsored transactions, you can execute a transaction without requiring the user to pay it themselves. Instead, you can act as a sponsor of the transaction, offering your own payment gas objects for the transaction.