From 276df385f3093fe56c223b18fb6459aecf7d971d Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 6 Aug 2024 22:22:29 +0200 Subject: [PATCH 1/7] feat(docs/examples): add self sponsor example --- docs/examples/rust/Cargo.toml | 4 + docs/examples/rust/stardust/self-sponsor.rs | 197 ++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 docs/examples/rust/stardust/self-sponsor.rs diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index 1b0eb3192ba..e7258ad69ea 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 = "self-sponsor" +path = "stardust/self-sponsor.rs" \ No newline at end of file diff --git a/docs/examples/rust/stardust/self-sponsor.rs b/docs/examples/rust/stardust/self-sponsor.rs new file mode 100644 index 00000000000..da6c92dccb3 --- /dev/null +++ b/docs/examples/rust/stardust/self-sponsor.rs @@ -0,0 +1,197 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Example demonstrating the self-sponsor scenario for claiming a basic output. +//! In order to work, it requires a network with test objects +//! generated from iota-genesis-builder/src/stardust/test_outputs. + +use std::{fs, path::PathBuf, str::FromStr}; + +use anyhow::anyhow; +use bip32::DerivationPath; +use iota_keys::keystore::{AccountKeystore, FileBasedKeystore}; +use iota_sdk::{ + rpc_types::{IotaObjectDataOptions, IotaTransactionBlockResponseOptions}, + types::{ + base_types::ObjectID, + crypto::SignatureScheme::ED25519, + gas_coin::GAS, + programmable_transaction_builder::ProgrammableTransactionBuilder, + quorum_driver_types::ExecuteTransactionRequestType, + transaction::{Argument, ObjectArg, Transaction, TransactionData}, + IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, + }, + IotaClientBuilder, +}; +use move_core_types::ident_str; +use shared_crypto::intent::Intent; + +/// Got from iota-genesis-builder/src/stardust/test_outputs/stardust_mix.rs +const MAIN_ADDRESS_MNEMONIC: &str = "crazy drum raw dirt tooth where fee base warm beach trim rule sign silk fee fee dad large creek venue coin steel hub scale"; + +/// Creates a temporary keystore +fn setup_keystore() -> Result { + // Create a temporary keystore + let keystore_path = PathBuf::from("iotatempdb"); + if !keystore_path.exists() { + let keystore = FileBasedKeystore::new(&keystore_path)?; + keystore.save()?; + } + // Read iota keystore + FileBasedKeystore::new(&keystore_path) +} + +fn clean_keystore() -> Result<(), anyhow::Error> { + // Remove files + fs::remove_file("iotatempdb")?; + fs::remove_file("iotatempdb.aliases")?; + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + // Build an iota client for a local network + let iota_client = IotaClientBuilder::default().build_localnet().await?; + + // Setup the temporary file based keystore + let mut keystore = setup_keystore()?; + + // For this example we need to derive addresses that are not at different + // indexes, one for sponsoring and one for claiming the Basic Output. + let sponsor_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/5'")?; + let sender_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/10'")?; + + // Derive the address of the sponsor + let sponsor = keystore.import_from_mnemonic( + MAIN_ADDRESS_MNEMONIC, + ED25519, + Some(sponsor_derivation_path), + )?; + println!("Sponsor address: {sponsor:?}"); + + // Derive the address of the sender + let sender = keystore.import_from_mnemonic( + MAIN_ADDRESS_MNEMONIC, + ED25519, + Some(sender_derivation_path), + )?; + println!("Sender address: {sender:?}"); + + // This object id was fetched manually. It refers to a Basic Output object that + // contains some Native Tokens. + let basic_output_object_id = ObjectID::from_hex_literal( + "0xb9c75a53a39e82bafcb454e68b3bd265a6083f32be832632df9ade976b47c37f", + )?; + // Get Basic Output object + let basic_output_object = iota_client + .read_api() + .get_object_with_options( + basic_output_object_id, + IotaObjectDataOptions::new().with_bcs(), + ) + .await? + .data + .ok_or(anyhow!("Basic output not found"))?; + let basic_output_object_ref = basic_output_object.object_ref(); + + // Create a PTB to for claiming the assets of a basic output for the sender + let pt = { + // Init the builder + let mut builder = ProgrammableTransactionBuilder::new(); + + ////// Command #1: extract the base token and native tokens bag. + // Type argument for a Basic Output coming from the IOTA network, i.e., the IOTA + // token or Gas type tag + let type_arguments = vec![GAS::type_tag()]; + // Then pass the basic output object as input + let arguments = vec![builder.obj(ObjectArg::ImmOrOwnedObject(basic_output_object_ref))?]; + // Finally call the basic_output::extract_assets function + if let Argument::Result(extracted_assets) = builder.programmable_move_call( + STARDUST_ADDRESS.into(), + ident_str!("basic_output").to_owned(), + ident_str!("extract_assets").to_owned(), + type_arguments, + arguments, + ) { + // If the basic output can be unlocked, the command will be succesful and will + // return a `base_token` (i.e., IOTA) balance and a `Bag` of native tokens + let extracted_base_token = Argument::NestedResult(extracted_assets, 0); + let extracted_native_tokens_bag = Argument::NestedResult(extracted_assets, 1); + + ////// Command #2: delete the empty native tokens bag + let arguments = vec![extracted_native_tokens_bag]; + builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("bag").to_owned(), + ident_str!("destroy_empty").to_owned(), + vec![], + arguments, + ); + + ////// Command #3: create a coin from the extracted IOTA balance + // Type argument for the IOTA coin + let type_arguments = vec![GAS::type_tag()]; + let arguments = vec![extracted_base_token]; + let new_iota_coin = builder.programmable_move_call( + IOTA_FRAMEWORK_ADDRESS.into(), + ident_str!("coin").to_owned(), + ident_str!("from_balance").to_owned(), + type_arguments, + arguments, + ); + + ////// Command #5: send back the base token coin to the user. + builder.transfer_arg(sender, new_iota_coin) + } + builder.finish() + }; + + // Setup gas budget and gas price + let gas_budget = 50_000_000; + let gas_price = iota_client.read_api().get_reference_gas_price().await?; + + // Get a gas coin + let gas_coin = iota_client + .coin_read_api() + .get_coins(sponsor, None, None, None) + .await? + .data + .into_iter() + .next() + .ok_or(anyhow!("No coins found for sponsor"))?; + + // Create the transaction data that will be sent to the network and allow + // sponsoring + let tx_data = TransactionData::new_programmable_allow_sponsor( + sender, + vec![gas_coin.object_ref()], + pt, + gas_budget, + gas_price, + sponsor, + ); + + // Client side, i.e., the sender POV + // Sender signs the transaction + let sender_signature = keystore.sign_secure(&sender, &tx_data, Intent::iota_transaction())?; + + // Server side, i.e., the sponsor POV + // Sponsor signs the transaction + let sponsor_signature = keystore.sign_secure(&sponsor, &tx_data, Intent::iota_transaction())?; + + // Execute transaction; the transaction data is created using the signature of + // the sender and of the sponsor. + let transaction_response = iota_client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(tx_data, vec![sender_signature, sponsor_signature]), + IotaTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await?; + + println!("Transaction digest: {}", transaction_response.digest); + + // Finish and clean the temporary keystore file + clean_keystore() +} From ae4a6e65e8a6624886b38fb45b8d089a0e511b0c Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 13 Aug 2024 12:47:35 +0200 Subject: [PATCH 2/7] fix(docs/content): make self sponsor example for Shimmer --- docs/examples/rust/Cargo.toml | 4 +-- ...elf-sponsor.rs => shimmer-self-sponsor.rs} | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) rename docs/examples/rust/stardust/{self-sponsor.rs => shimmer-self-sponsor.rs} (88%) diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index e7258ad69ea..c968318d7ac 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -52,5 +52,5 @@ name = "foundry-output-claim" path = "stardust/foundry-output-claim.rs" [[example]] -name = "self-sponsor" -path = "stardust/self-sponsor.rs" \ No newline at end of file +name = "shimmer-self-sponsor" +path = "stardust/shimmer-self-sponsor.rs" \ No newline at end of file diff --git a/docs/examples/rust/stardust/self-sponsor.rs b/docs/examples/rust/stardust/shimmer-self-sponsor.rs similarity index 88% rename from docs/examples/rust/stardust/self-sponsor.rs rename to docs/examples/rust/stardust/shimmer-self-sponsor.rs index da6c92dccb3..458b618d815 100644 --- a/docs/examples/rust/stardust/self-sponsor.rs +++ b/docs/examples/rust/stardust/shimmer-self-sponsor.rs @@ -1,8 +1,8 @@ // Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Example demonstrating the self-sponsor scenario for claiming a basic output. -//! In order to work, it requires a network with test objects +//! 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}; @@ -15,9 +15,9 @@ use iota_sdk::{ types::{ base_types::ObjectID, crypto::SignatureScheme::ED25519, - gas_coin::GAS, programmable_transaction_builder::ProgrammableTransactionBuilder, quorum_driver_types::ExecuteTransactionRequestType, + smr_coin::SMR, transaction::{Argument, ObjectArg, Transaction, TransactionData}, IOTA_FRAMEWORK_ADDRESS, STARDUST_ADDRESS, }, @@ -26,6 +26,9 @@ use iota_sdk::{ 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"; @@ -57,9 +60,12 @@ async fn main() -> Result<(), anyhow::Error> { let mut keystore = setup_keystore()?; // For this example we need to derive addresses that are not at different - // indexes, one for sponsoring and one for claiming the Basic Output. - let sponsor_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/5'")?; - let sender_derivation_path = DerivationPath::from_str("m/44'/4218'/0'/0'/10'")?; + // 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( @@ -80,7 +86,7 @@ async fn main() -> Result<(), anyhow::Error> { // This object id was fetched manually. It refers to a Basic Output object that // contains some Native Tokens. let basic_output_object_id = ObjectID::from_hex_literal( - "0xb9c75a53a39e82bafcb454e68b3bd265a6083f32be832632df9ade976b47c37f", + "0xbdc4dec75098700e8e82349d9f3a9f28dcd22d2b39f5fbdf8436b05430bc3690", )?; // Get Basic Output object let basic_output_object = iota_client @@ -100,9 +106,9 @@ async fn main() -> Result<(), anyhow::Error> { 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()]; + // 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 @@ -128,9 +134,9 @@ async fn main() -> Result<(), anyhow::Error> { arguments, ); - ////// Command #3: create a coin from the extracted IOTA balance - // Type argument for the IOTA coin - let type_arguments = vec![GAS::type_tag()]; + ////// 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(), From 0138cc7d161ba9c51c12cdff5324c91678bced94 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 13 Aug 2024 13:40:05 +0200 Subject: [PATCH 3/7] fix(docs/examples): smr comment --- docs/examples/rust/stardust/shimmer-self-sponsor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/rust/stardust/shimmer-self-sponsor.rs b/docs/examples/rust/stardust/shimmer-self-sponsor.rs index 458b618d815..61c2dcc642a 100644 --- a/docs/examples/rust/stardust/shimmer-self-sponsor.rs +++ b/docs/examples/rust/stardust/shimmer-self-sponsor.rs @@ -120,7 +120,7 @@ async fn main() -> Result<(), anyhow::Error> { 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 + // 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); From ed3d4c060644829e9ab446df03d69f493d9de0c7 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 13 Aug 2024 13:48:11 +0200 Subject: [PATCH 4/7] fix(docs/examples): dprint --- docs/examples/rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/rust/Cargo.toml b/docs/examples/rust/Cargo.toml index c968318d7ac..4dff5785e23 100644 --- a/docs/examples/rust/Cargo.toml +++ b/docs/examples/rust/Cargo.toml @@ -53,4 +53,4 @@ path = "stardust/foundry-output-claim.rs" [[example]] name = "shimmer-self-sponsor" -path = "stardust/shimmer-self-sponsor.rs" \ No newline at end of file +path = "stardust/shimmer-self-sponsor.rs" From 4c8d74866cb0d3443c8082b1c04c8c27f85af381 Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Tue, 13 Aug 2024 13:38:42 +0200 Subject: [PATCH 5/7] feat(docs/content): add shimmer self-sponsorship claim --- .../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..f7be93b5a15 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#L62-L68 +``` + + + + +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#L104-L153 +``` + + + + +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#L169-L197 +``` + + + + +TODO + + + \ No newline at end of file From 300af0d3750d4fab8b1bf3ae9fa2481c68f1bdfb Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 14 Aug 2024 09:35:08 +0200 Subject: [PATCH 6/7] fix(docs/content): spaces Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> --- docs/content/developer/stardust/claiming/self-sponsor.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx index f7be93b5a15..5fbde688a52 100644 --- a/docs/content/developer/stardust/claiming/self-sponsor.mdx +++ b/docs/content/developer/stardust/claiming/self-sponsor.mdx @@ -2,10 +2,10 @@ 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. +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. +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 From 015ff1cf72bafbf12c6ed0cd9976d530440fb27e Mon Sep 17 00:00:00 2001 From: Mirko Zichichi Date: Wed, 14 Aug 2024 10:34:21 +0200 Subject: [PATCH 7/7] fix(docs/content): code lines --- docs/content/developer/stardust/claiming/self-sponsor.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/developer/stardust/claiming/self-sponsor.mdx b/docs/content/developer/stardust/claiming/self-sponsor.mdx index 5fbde688a52..cbb8921c788 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#L62-L68 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L61-L67 ``` @@ -31,7 +31,7 @@ TODO -```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L104-L153 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L103-L152 ``` @@ -47,7 +47,7 @@ TODO -```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L169-L197 +```rust file=/docs/examples/rust/stardust/shimmer-self-sponsor.rs#L168-L194 ```